Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support eslint's new flat config #176

Merged
merged 1 commit into from Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Expand Up @@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
node-version: [12.x, 14.x, 16.x, 18.x, 19.x]
eslint-version: [7, 8]
eslint-version: [7, "8.40", 8]
jest-version: [27, 28, 29]
jest-watch-typeahead-version: [1, 2]
exclude:
Expand Down
3 changes: 3 additions & 0 deletions integrationTests/__fixtures__/flat-config/__eslint__/file.js
@@ -0,0 +1,3 @@
const a = 1;

console.log('a', a);
7 changes: 7 additions & 0 deletions integrationTests/__fixtures__/flat-config/eslint.config.js
@@ -0,0 +1,7 @@
module.exports = [
{
rules: {
'no-console': 'error',
},
},
];
4 changes: 4 additions & 0 deletions integrationTests/__fixtures__/flat-config/jest.config.js
@@ -0,0 +1,4 @@
module.exports = {
runner: '../../../',
testMatch: ['**/__eslint__/**/*.js'],
};
@@ -0,0 +1,3 @@
const a = 1;

console.log('a', a);
@@ -0,0 +1,5 @@
module.exports = {
rules: {
'no-console': 'error',
},
};
@@ -0,0 +1,3 @@
module.exports = {
config: './eslint.config.js',
};
@@ -0,0 +1,4 @@
module.exports = {
runner: '../../../',
testMatch: ['**/__eslint__/**/*.js'],
};
19 changes: 19 additions & 0 deletions integrationTests/__snapshots__/flat-config.test.js.snap
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Works with the new flat config format 1`] = `
"FAIL __eslint__/file.js
✕ no-console
/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/flat-config/__eslint__/file.js
3:1 error Unexpected console statement no-console
✖ 1 problem (1 error, 0 warnings)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time:
Ran all test suites.
"
`;
@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Does not try to use flat config on eslint versions that don't support it 1`] = `
"PASS __eslint__/file.js
● Console
console.warn
/mocked-path-to-jest-runner-mocha/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js
3:1 warning Unexpected console statement no-console
✖ 1 problem (0 errors, 1 warning)
✓ ESLint
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time:
Ran all test suites.
"
`;
10 changes: 10 additions & 0 deletions integrationTests/flat-config.test.js
@@ -0,0 +1,10 @@
const { version } = require('eslint/package.json');
const semver = require('semver');
const runJest = require('./runJest');

(semver.satisfies(version, '>=8.41') ? it : it.skip)(
'Works with the new flat config format',
async () => {
expect(await runJest('flat-config')).toMatchSnapshot();
},
);
12 changes: 12 additions & 0 deletions integrationTests/legacy-config-at-eslint.config.js.test.js
@@ -0,0 +1,12 @@
const { version } = require('eslint/package.json');
const semver = require('semver');
const runJest = require('./runJest');

(semver.satisfies(version, '<8.41') ? it : it.skip)(
"Does not try to use flat config on eslint versions that don't support it",
async () => {
expect(
await runJest('legacy-config-at-eslint.config.js'),
).toMatchSnapshot();
},
);
83 changes: 70 additions & 13 deletions src/runner/runESLint.js
@@ -1,6 +1,32 @@
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`:
Expand Down Expand Up @@ -96,8 +122,41 @@ const getComputedFixValue = ({ fix, quiet, fixDryRun }) => {
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;
cprussin marked this conversation as resolved.
Show resolved Hide resolved

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 = (config, extraOptions) => {
const getCachedValues = async (config, extraOptions) => {
if (!cachedValues) {
const { cliOptions: baseCliOptions } = getESLintOptions(config);
const cliOptions = {
Expand All @@ -106,20 +165,20 @@ const getCachedValues = (config, extraOptions) => {
...extraOptions,
};

// these are not constructor args, so remove them
const { fixDryRun, format, maxWarnings, quiet, ...eslintOptions } =
cliOptions;

const cli = new ESLint(eslintOptions);
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(format);
const formatter = await cli.loadFormatter(cliOptions.format);
return formatter.format(...args);
},
cliOptions,
ESLintConstructor,
};
}

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

const { isPathIgnored, lintFiles, formatter, cliOptions } = getCachedValues(
config,
extraOptions,
);
const { isPathIgnored, lintFiles, formatter, cliOptions, ESLintConstructor } =
await getCachedValues(config, extraOptions);

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

if (cliOptions.fix && !cliOptions.fixDryRun) {
await ESLint.outputFixes(report);
await ESLintConstructor.outputFixes(report);
}

const end = Date.now();

const message = await formatter(
cliOptions.quiet ? ESLint.getErrorResults(report) : report,
cliOptions.quiet ? ESLintConstructor.getErrorResults(report) : report,
);

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