Skip to content

Commit 978ad9a

Browse files
committed
Add autofixing of lint warnings for specific packages/files
Currently, there are a ton of lint warnings, and it would be good to address them sooner rather than later. The good thing is that many of these warnings can be autofixed — if they are turned into errors first. So, what we _could_ do is open `eslint.config.mjs`, change all of the rules that are configured to produce warnings to produce errors instead (`"warn"` -> `"error"`), then run `yarn lint:eslint --fix` and take care of them in one fell swoop. However, if we took this approach, approving such a PR would likely take a while since the changes would touch a bunch of packages in this monorepo and require codeowner approval from a bunch of teams. Instead, it would be better if we batched lint violation fixes by codeowner. That is, open PRs progressively by addressing all lint violations for packages owned by the Wallet Framework team first, then the Accounts team, then the Confirmations team, etc. To do this, we would need a way to run ESLint on specific directories. Again, that seems easy on its own, but we'd have to repeat the step that modifies `eslint.config.mjs` to change warning-producing rules to produce errors instead each time we wanted to make a new PR. Instead, if would be better if we could ask our ESLint script to not only allow custom file paths to be passed in, but also convert warnings into errors for us. That's what this PR does. For instance, you could say: ``` yarn lint:eslint packages/network-controller --treat-warnings-as-errors --fix ``` and now ESLint will run just on `network-controller` files, and autofix any warnings automatically.
1 parent e5945d2 commit 978ad9a

File tree

1 file changed

+94
-14
lines changed

1 file changed

+94
-14
lines changed

scripts/run-eslint.ts

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,34 @@ const EXISTING_WARNINGS_FILE = path.resolve(
88
'../eslint-warning-thresholds.json',
99
);
1010

11+
/**
12+
* The parsed command-line arguments.
13+
*/
14+
type CommandLineArguments = {
15+
/**
16+
* Whether to cache results to speed up future runs (true) or not (false).
17+
*/
18+
cache: boolean;
19+
/**
20+
* A list of specific files to lint.
21+
*/
22+
files: string[];
23+
/**
24+
* Whether to automatically fix lint errors (true) or not (false).
25+
*/
26+
fix: boolean;
27+
/**
28+
* Whether to only report errors, disabling the warnings quality gate in the
29+
* process (true) or not (false).
30+
*/
31+
quiet: boolean;
32+
/**
33+
* Whether to treat all warnings as errors so that they can be autofixed
34+
* (true) or not (false).
35+
*/
36+
treatWarningsAsErrors: boolean;
37+
};
38+
1139
/**
1240
* An object mapping rule IDs to their warning counts.
1341
*/
@@ -28,9 +56,14 @@ type WarningComparison = {
2856
};
2957

3058
/**
31-
* The warning severity of level of an ESLint rule.
59+
* The severity level for an ESLint message.
3260
*/
33-
const WARNING = 1;
61+
enum ESLintMessageSeverity {
62+
Warning = 1,
63+
// This isn't a variable.
64+
// eslint-disable-next-line @typescript-eslint/no-shadow
65+
Error = 2,
66+
}
3467

3568
// Run the script.
3669
main().catch((error) => {
@@ -42,14 +75,20 @@ main().catch((error) => {
4275
* The entrypoint to this script.
4376
*/
4477
async function main() {
45-
const { cache, fix, quiet } = parseCommandLineArguments();
78+
const args = parseCommandLineArguments();
79+
const { cache, fix, quiet, treatWarningsAsErrors } = args;
4680

4781
const eslint = new ESLint({ cache, fix });
48-
const results = await runESLint(eslint, { fix, quiet });
82+
const files = args.files.length > 0 ? args.files : ['.'];
83+
const results = await runESLint(eslint, files, {
84+
fix,
85+
quiet,
86+
treatWarningsAsErrors,
87+
});
4988
const hasErrors = results.some((result) => result.errorCount > 0);
5089

51-
if (!quiet && !hasErrors) {
52-
evaluateWarnings(results);
90+
if (args.files.length === 0 && !args.quiet && !hasErrors) {
91+
checkAgainstAndUpdateWarningThresholds(results);
5392
}
5493
}
5594

@@ -58,8 +97,10 @@ async function main() {
5897
*
5998
* @returns The parsed arguments.
6099
*/
61-
function parseCommandLineArguments() {
62-
return yargs(process.argv.slice(2))
100+
function parseCommandLineArguments(): CommandLineArguments {
101+
const { cache, fix, quiet, treatWarningsAsErrors, ...rest } = yargs(
102+
process.argv.slice(2),
103+
)
63104
.option('cache', {
64105
type: 'boolean',
65106
description: 'Cache results to speed up future runs',
@@ -76,24 +117,40 @@ function parseCommandLineArguments() {
76117
'Only report errors, disabling the warnings quality gate in the process',
77118
default: false,
78119
})
79-
.help().argv;
120+
.option('treatWarningsAsErrors', {
121+
type: 'boolean',
122+
description: 'Treat all warnings as errors so that they can be autofixed',
123+
default: false,
124+
})
125+
.help()
126+
.string('_').argv;
127+
128+
// Type assertion: The types for `yargs`'s `string` method are wrong.
129+
const files = rest._ as string[];
130+
131+
return { cache, fix, quiet, treatWarningsAsErrors, files };
80132
}
81133

82134
/**
83-
* Runs ESLint on the project files.
135+
* Runs ESLint on the given files.
84136
*
85137
* @param eslint - The ESLint instance.
138+
* @param files - The list of files to lint.
86139
* @param options - The options for running ESLint.
87140
* @param options.quiet - Whether to only report errors (true) or not (false).
88141
* @param options.fix - Whether to automatically fix problems (true) or not
89142
* (false).
143+
* @param options.treatWarningsAsErrors - Whether to treat all warnings as
144+
* errors (true) or not (false). This is most useful when combined with `files`
145+
* so that all warnings for a single package can be autofixed in a single go.
90146
* @returns A promise that resolves to the lint results.
91147
*/
92148
async function runESLint(
93149
eslint: ESLint,
94-
options: { quiet: boolean; fix: boolean },
150+
files: string[],
151+
options: { quiet: boolean; fix: boolean; treatWarningsAsErrors: boolean },
95152
): Promise<ESLint.LintResult[]> {
96-
let results = await eslint.lintFiles(['.']);
153+
let results = await eslint.lintFiles(files);
97154
const errorResults = ESLint.getErrorResults(results);
98155

99156
if (errorResults.length > 0) {
@@ -102,6 +159,26 @@ async function runESLint(
102159

103160
if (options.quiet) {
104161
results = errorResults;
162+
} else if (options.treatWarningsAsErrors) {
163+
results = results.map((result) => {
164+
return {
165+
...result,
166+
messages: result.messages.map((message) => {
167+
return {
168+
...message,
169+
severity:
170+
message.severity === ESLintMessageSeverity.Warning
171+
? ESLintMessageSeverity.Error
172+
: message.severity,
173+
};
174+
}),
175+
errorCount: result.errorCount + result.warningCount,
176+
warningCount: 0,
177+
fixableErrorCount:
178+
result.fixableErrorCount + result.fixableWarningCount,
179+
fixableWarningCount: 0,
180+
};
181+
});
105182
}
106183

107184
const formatter = await eslint.loadFormatter('stylish');
@@ -129,7 +206,7 @@ async function runESLint(
129206
*
130207
* @param results - The results of running ESLint.
131208
*/
132-
function evaluateWarnings(results: ESLint.LintResult[]) {
209+
function checkAgainstAndUpdateWarningThresholds(results: ESLint.LintResult[]) {
133210
const warningThresholds = loadWarningThresholds();
134211
const warningCounts = getWarningCounts(results);
135212

@@ -216,7 +293,10 @@ function saveWarningThresholds(warningCounts: WarningCounts): void {
216293
function getWarningCounts(results: ESLint.LintResult[]): WarningCounts {
217294
const warningCounts = results.reduce((acc, result) => {
218295
for (const message of result.messages) {
219-
if (message.severity === WARNING && message.ruleId) {
296+
if (
297+
message.severity === ESLintMessageSeverity.Warning &&
298+
message.ruleId
299+
) {
220300
acc[message.ruleId] = (acc[message.ruleId] ?? 0) + 1;
221301
}
222302
}

0 commit comments

Comments
 (0)