-
Notifications
You must be signed in to change notification settings - Fork 59
/
runESLint.js
280 lines (249 loc) · 7.79 KB
/
runESLint.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
const { ESLint } = require('eslint');
const getESLintOptions = require('../utils/getESLintOptions');
let FlatESLint;
let shouldUseFlatConfig;
try {
// Use a dynamic require here rather than a global require because this
// import path does not exist in eslint v7 which this library still
// supports
//
// ESlint exposes the new FlatESLint API under `eslint/use-at-your-own-risk` by
// using it's [export configuration](https://tinyurl.com/2s45zh9b). However,
// the `import/no-unresolved` rule is [not aware of
// `exports`](https://tinyurl.com/469djpx3) and causes a false error here. So,
// let's ignore that rule for this import.
//
// eslint-disable-next-line global-require, import/no-unresolved
const eslintExperimental = require('eslint/use-at-your-own-risk');
FlatESLint = eslintExperimental.FlatESLint;
shouldUseFlatConfig = eslintExperimental.shouldUseFlatConfig;
} catch {
/* no-op */
}
if (shouldUseFlatConfig === undefined) {
shouldUseFlatConfig = () => Promise.resolve(false);
}
/*
* This function exists because there are issues with the `pass`, `skip`, and
* `fail` functions from `create-jest-runner`:
*
* 1. The `pass` and `skip` functions have a bug in our version of
* `create-jest-runner` where calling them will actually pass an `undefined`
* item in `testResults`, which causes the GithubActionsReporter to report an
* empty error message on every file. This has been resolved in later
* versions of `create-jest-runner`, but upgrading is a breaking change that
* is incompatible with some node versions we support.
*
* 2. The `fail` function in `create-jest-runner` does not support passing
* multiple failure messages, which makes it impossible to annotate each
* eslint failure. This has not been resolved, although presumably could be
* worked around by using the underlying `toTestResult` function instead
* (although that function isn't exposed in the public API of that library).
*
* TODO At some point, we should put a PR in to `create-jest-runner` to resolve
* point 2 above, and then should upgrade and remove this function and go back
* to using `pass`, `skip`, and `fail` from that library instead.
*/
const mkTestResults = ({
message,
start,
end,
numFailingTests,
numPassingTests,
testPath,
assertionResults,
}) => {
const startTime = new Date(start).getTime();
const endTime = new Date(end).getTime();
return {
failureMessage: message,
leaks: false,
numFailingTests,
numPassingTests,
numPendingTests: 0,
numTodoTests: 0,
openHandles: [],
perfStats: {
start: startTime,
end: endTime,
duration: endTime - startTime,
slow: false,
},
skipped: numPassingTests === 0 && numFailingTests === 0,
snapshot: {
added: 0,
fileDeleted: false,
matched: 0,
unchecked: 0,
uncheckedKeys: [],
unmatched: 0,
updated: 0,
},
testFilePath: testPath,
testResults: assertionResults.map(result => ({
duration: endTime - startTime,
ancestorTitles: [],
failureDetails: [],
failureMessages: result.message ? [result.message] : [],
fullName: result.fullName,
location: result.location,
testFilePath: testPath,
numPassingAsserts: numPassingTests,
status: result.status,
title: result.title,
})),
};
};
const mkAssertionResults = (testPath, report) =>
report[0].messages?.map(reportMessage => ({
message: [
reportMessage.message,
` at ${testPath}:${reportMessage.line}:${reportMessage.column}`,
].join('\n'),
fullName: `${reportMessage.line}:${reportMessage.column}: ${reportMessage.message} [${reportMessage.ruleId}]`,
location: {
column: reportMessage.line,
line: reportMessage.column,
},
status: 'failed',
title: reportMessage.ruleId,
})) ?? [];
const getComputedFixValue = ({ fix, quiet, fixDryRun }) => {
if (fix || fixDryRun) {
return quiet ? ({ severity }) => severity === 2 : true;
}
return undefined;
};
const getESLintConstructor = async () => {
if (await shouldUseFlatConfig()) {
return FlatESLint;
}
return ESLint;
};
// Remove options that are not constructor args.
const getESLintConstructorArgs = async cliOptions => {
// these are not constructor args for either the legacy or the flat ESLint
// api
const { fixDryRun, format, maxWarnings, quiet, ...legacyConstructorArgs } =
cliOptions;
if (await shouldUseFlatConfig()) {
// these options are supported by the legacy ESLint api but aren't
// supported by the ESLintFlat api
const {
extensions,
ignorePath,
rulePaths,
resolvePluginsRelativeTo,
useEslintrc,
overrideConfig,
...flatConstructorArgs
} = legacyConstructorArgs;
return flatConstructorArgs;
}
return legacyConstructorArgs;
};
let cachedValues;
const getCachedValues = async (config, extraOptions) => {
if (!cachedValues) {
const { cliOptions: baseCliOptions } = getESLintOptions(config);
const cliOptions = {
...baseCliOptions,
fix: getComputedFixValue(baseCliOptions),
...extraOptions,
};
const ESLintConstructor = await getESLintConstructor();
const cli = new ESLintConstructor(
await getESLintConstructorArgs(cliOptions),
);
cachedValues = {
isPathIgnored: cli.isPathIgnored.bind(cli),
lintFiles: (...args) => cli.lintFiles(...args),
formatter: async (...args) => {
const formatter = await cli.loadFormatter(cliOptions.format);
return formatter.format(...args);
},
cliOptions,
ESLintConstructor,
};
}
return cachedValues;
};
const runESLint = async ({ testPath, config, extraOptions }) => {
const start = Date.now();
if (config.setupTestFrameworkScriptFile) {
// eslint-disable-next-line import/no-dynamic-require,global-require
require(config.setupTestFrameworkScriptFile);
}
if (config.setupFilesAfterEnv) {
// eslint-disable-next-line import/no-dynamic-require,global-require
config.setupFilesAfterEnv.forEach(require);
}
const { isPathIgnored, lintFiles, formatter, cliOptions, ESLintConstructor } =
await getCachedValues(config, extraOptions);
if (await isPathIgnored(testPath)) {
return mkTestResults({
start,
end: Date.now(),
testPath,
numFailingTests: 0,
numPassingTests: 0,
assertionResults: [
{
title: 'ESLint',
status: 'skipped',
},
],
});
}
const report = await lintFiles([testPath]);
if (cliOptions.fix && !cliOptions.fixDryRun) {
await ESLintConstructor.outputFixes(report);
}
const end = Date.now();
const message = await formatter(
cliOptions.quiet ? ESLintConstructor.getErrorResults(report) : report,
);
if (report[0]?.errorCount > 0) {
return mkTestResults({
message,
start,
end,
testPath,
numFailingTests: report[0].errorCount,
numPassingTests: 0,
assertionResults: mkAssertionResults(testPath, report),
});
}
const tooManyWarnings =
cliOptions.maxWarnings >= 0 &&
report[0]?.warningCount > cliOptions.maxWarnings;
if (tooManyWarnings) {
return mkTestResults({
message: `${message}\nESLint found too many warnings (maximum: ${cliOptions.maxWarnings}).`,
start,
end,
testPath,
numFailingTests: 1,
numPassingTests: 0,
assertionResults: mkAssertionResults(testPath, report),
});
}
const result = mkTestResults({
start,
end,
testPath,
numFailingTests: 0,
numPassingTests: 1,
assertionResults: [
{
title: 'ESLint',
status: 'passed',
},
],
});
if (!cliOptions.quiet && report[0]?.warningCount > 0) {
result.console = [{ message, origin: '', type: 'warn' }];
}
return result;
};
module.exports = runESLint;