Skip to content

Commit aae6acd

Browse files
authored
Support eslint flat config (#176)
1 parent 4edaf5b commit aae6acd

File tree

13 files changed

+163
-14
lines changed

13 files changed

+163
-14
lines changed

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
node-version: [12.x, 14.x, 16.x, 18.x, 19.x]
22-
eslint-version: [7, 8]
22+
eslint-version: [7, "8.40", 8]
2323
jest-version: [27, 28, 29]
2424
jest-watch-typeahead-version: [1, 2]
2525
exclude:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const a = 1;
2+
3+
console.log('a', a);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = [
2+
{
3+
rules: {
4+
'no-console': 'error',
5+
},
6+
},
7+
];
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
runner: '../../../',
3+
testMatch: ['**/__eslint__/**/*.js'],
4+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const a = 1;
2+
3+
console.log('a', a);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
rules: {
3+
'no-console': 'error',
4+
},
5+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
config: './eslint.config.js',
3+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
runner: '../../../',
3+
testMatch: ['**/__eslint__/**/*.js'],
4+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Works with the new flat config format 1`] = `
4+
"FAIL __eslint__/file.js
5+
✕ no-console
6+
7+
8+
/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/flat-config/__eslint__/file.js
9+
3:1 error Unexpected console statement no-console
10+
11+
✖ 1 problem (1 error, 0 warnings)
12+
13+
Test Suites: 1 failed, 1 total
14+
Tests: 1 failed, 1 total
15+
Snapshots: 0 total
16+
Time:
17+
Ran all test suites.
18+
"
19+
`;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Does not try to use flat config on eslint versions that don't support it 1`] = `
4+
"PASS __eslint__/file.js
5+
● Console
6+
7+
console.warn
8+
9+
/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js
10+
3:1 warning Unexpected console statement no-console
11+
12+
✖ 1 problem (0 errors, 1 warning)
13+
14+
✓ ESLint
15+
16+
Test Suites: 1 passed, 1 total
17+
Tests: 1 passed, 1 total
18+
Snapshots: 0 total
19+
Time:
20+
Ran all test suites.
21+
"
22+
`;

integrationTests/flat-config.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const { version } = require('eslint/package.json');
2+
const semver = require('semver');
3+
const runJest = require('./runJest');
4+
5+
(semver.satisfies(version, '>=8.41') ? it : it.skip)(
6+
'Works with the new flat config format',
7+
async () => {
8+
expect(await runJest('flat-config')).toMatchSnapshot();
9+
},
10+
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { version } = require('eslint/package.json');
2+
const semver = require('semver');
3+
const runJest = require('./runJest');
4+
5+
(semver.satisfies(version, '<8.41') ? it : it.skip)(
6+
"Does not try to use flat config on eslint versions that don't support it",
7+
async () => {
8+
expect(
9+
await runJest('legacy-config-at-eslint.config.js'),
10+
).toMatchSnapshot();
11+
},
12+
);

src/runner/runESLint.js

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
const { ESLint } = require('eslint');
22
const getESLintOptions = require('../utils/getESLintOptions');
33

4+
let FlatESLint;
5+
let shouldUseFlatConfig;
6+
7+
try {
8+
// Use a dynamic require here rather than a global require because this
9+
// import path does not exist in eslint v7 which this library still
10+
// supports
11+
//
12+
// ESlint exposes the new FlatESLint API under `eslint/use-at-your-own-risk` by
13+
// using it's [export configuration](https://tinyurl.com/2s45zh9b). However,
14+
// the `import/no-unresolved` rule is [not aware of
15+
// `exports`](https://tinyurl.com/469djpx3) and causes a false error here. So,
16+
// let's ignore that rule for this import.
17+
//
18+
// eslint-disable-next-line global-require, import/no-unresolved
19+
const eslintExperimental = require('eslint/use-at-your-own-risk');
20+
FlatESLint = eslintExperimental.FlatESLint;
21+
shouldUseFlatConfig = eslintExperimental.shouldUseFlatConfig;
22+
} catch {
23+
/* no-op */
24+
}
25+
26+
if (shouldUseFlatConfig === undefined) {
27+
shouldUseFlatConfig = () => Promise.resolve(false);
28+
}
29+
430
/*
531
* This function exists because there are issues with the `pass`, `skip`, and
632
* `fail` functions from `create-jest-runner`:
@@ -96,8 +122,41 @@ const getComputedFixValue = ({ fix, quiet, fixDryRun }) => {
96122
return undefined;
97123
};
98124

125+
const getESLintConstructor = async () => {
126+
if (await shouldUseFlatConfig()) {
127+
return FlatESLint;
128+
}
129+
130+
return ESLint;
131+
};
132+
133+
// Remove options that are not constructor args.
134+
const getESLintConstructorArgs = async cliOptions => {
135+
// these are not constructor args for either the legacy or the flat ESLint
136+
// api
137+
const { fixDryRun, format, maxWarnings, quiet, ...legacyConstructorArgs } =
138+
cliOptions;
139+
140+
if (await shouldUseFlatConfig()) {
141+
// these options are supported by the legacy ESLint api but aren't
142+
// supported by the ESLintFlat api
143+
const {
144+
extensions,
145+
ignorePath,
146+
rulePaths,
147+
resolvePluginsRelativeTo,
148+
useEslintrc,
149+
overrideConfig,
150+
...flatConstructorArgs
151+
} = legacyConstructorArgs;
152+
return flatConstructorArgs;
153+
}
154+
155+
return legacyConstructorArgs;
156+
};
157+
99158
let cachedValues;
100-
const getCachedValues = (config, extraOptions) => {
159+
const getCachedValues = async (config, extraOptions) => {
101160
if (!cachedValues) {
102161
const { cliOptions: baseCliOptions } = getESLintOptions(config);
103162
const cliOptions = {
@@ -106,20 +165,20 @@ const getCachedValues = (config, extraOptions) => {
106165
...extraOptions,
107166
};
108167

109-
// these are not constructor args, so remove them
110-
const { fixDryRun, format, maxWarnings, quiet, ...eslintOptions } =
111-
cliOptions;
112-
113-
const cli = new ESLint(eslintOptions);
168+
const ESLintConstructor = await getESLintConstructor();
169+
const cli = new ESLintConstructor(
170+
await getESLintConstructorArgs(cliOptions),
171+
);
114172

115173
cachedValues = {
116174
isPathIgnored: cli.isPathIgnored.bind(cli),
117175
lintFiles: (...args) => cli.lintFiles(...args),
118176
formatter: async (...args) => {
119-
const formatter = await cli.loadFormatter(format);
177+
const formatter = await cli.loadFormatter(cliOptions.format);
120178
return formatter.format(...args);
121179
},
122180
cliOptions,
181+
ESLintConstructor,
123182
};
124183
}
125184

@@ -139,10 +198,8 @@ const runESLint = async ({ testPath, config, extraOptions }) => {
139198
config.setupFilesAfterEnv.forEach(require);
140199
}
141200

142-
const { isPathIgnored, lintFiles, formatter, cliOptions } = getCachedValues(
143-
config,
144-
extraOptions,
145-
);
201+
const { isPathIgnored, lintFiles, formatter, cliOptions, ESLintConstructor } =
202+
await getCachedValues(config, extraOptions);
146203

147204
if (await isPathIgnored(testPath)) {
148205
return mkTestResults({
@@ -163,13 +220,13 @@ const runESLint = async ({ testPath, config, extraOptions }) => {
163220
const report = await lintFiles([testPath]);
164221

165222
if (cliOptions.fix && !cliOptions.fixDryRun) {
166-
await ESLint.outputFixes(report);
223+
await ESLintConstructor.outputFixes(report);
167224
}
168225

169226
const end = Date.now();
170227

171228
const message = await formatter(
172-
cliOptions.quiet ? ESLint.getErrorResults(report) : report,
229+
cliOptions.quiet ? ESLintConstructor.getErrorResults(report) : report,
173230
);
174231

175232
if (report[0]?.errorCount > 0) {

0 commit comments

Comments
 (0)