Skip to content

Commit

Permalink
Support eslint flat config
Browse files Browse the repository at this point in the history
  • Loading branch information
cprussin committed Jun 1, 2023
1 parent 4edaf5b commit b8bd2ad
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 14 deletions.
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;

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

0 comments on commit b8bd2ad

Please sign in to comment.