From d31ac24a4bcdc940aca5a9177385c42268028850 Mon Sep 17 00:00:00 2001 From: Connor Prussin Date: Sat, 20 May 2023 09:16:44 -0700 Subject: [PATCH] Support eslint flat config --- .../flat-config/__eslint__/file.js | 3 + .../__fixtures__/flat-config/eslint.config.js | 7 ++ .../__fixtures__/flat-config/jest.config.js | 4 + .../__eslint__/file.js | 3 + .../eslint.config.js | 5 ++ .../jest-runner-eslint.config.js | 3 + .../jest.config.js | 4 + .../__snapshots__/flat-config.test.js.snap | 19 +++++ ...cy-config-at-eslint.config.js.test.js.snap | 22 +++++ integrationTests/flat-config.test.js | 10 +++ .../legacy-config-at-eslint.config.js.test.js | 12 +++ src/runner/runESLint.js | 83 ++++++++++++++++--- yarn.lock | 32 +++---- 13 files changed, 175 insertions(+), 32 deletions(-) create mode 100644 integrationTests/__fixtures__/flat-config/__eslint__/file.js create mode 100644 integrationTests/__fixtures__/flat-config/eslint.config.js create mode 100644 integrationTests/__fixtures__/flat-config/jest.config.js create mode 100644 integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js create mode 100644 integrationTests/__fixtures__/legacy-config-at-eslint.config.js/eslint.config.js create mode 100644 integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest-runner-eslint.config.js create mode 100644 integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest.config.js create mode 100644 integrationTests/__snapshots__/flat-config.test.js.snap create mode 100644 integrationTests/__snapshots__/legacy-config-at-eslint.config.js.test.js.snap create mode 100644 integrationTests/flat-config.test.js create mode 100644 integrationTests/legacy-config-at-eslint.config.js.test.js diff --git a/integrationTests/__fixtures__/flat-config/__eslint__/file.js b/integrationTests/__fixtures__/flat-config/__eslint__/file.js new file mode 100644 index 0000000..7720ff4 --- /dev/null +++ b/integrationTests/__fixtures__/flat-config/__eslint__/file.js @@ -0,0 +1,3 @@ +const a = 1; + +console.log('a', a); diff --git a/integrationTests/__fixtures__/flat-config/eslint.config.js b/integrationTests/__fixtures__/flat-config/eslint.config.js new file mode 100644 index 0000000..2d743d4 --- /dev/null +++ b/integrationTests/__fixtures__/flat-config/eslint.config.js @@ -0,0 +1,7 @@ +module.exports = [ + { + rules: { + 'no-console': 'error', + }, + }, +]; diff --git a/integrationTests/__fixtures__/flat-config/jest.config.js b/integrationTests/__fixtures__/flat-config/jest.config.js new file mode 100644 index 0000000..94f5944 --- /dev/null +++ b/integrationTests/__fixtures__/flat-config/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + runner: '../../../', + testMatch: ['**/__eslint__/**/*.js'], +}; diff --git a/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js new file mode 100644 index 0000000..7720ff4 --- /dev/null +++ b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/__eslint__/file.js @@ -0,0 +1,3 @@ +const a = 1; + +console.log('a', a); diff --git a/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/eslint.config.js b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/eslint.config.js new file mode 100644 index 0000000..c4f6207 --- /dev/null +++ b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/eslint.config.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + 'no-console': 'error', + }, +}; diff --git a/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest-runner-eslint.config.js b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest-runner-eslint.config.js new file mode 100644 index 0000000..1dbf367 --- /dev/null +++ b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest-runner-eslint.config.js @@ -0,0 +1,3 @@ +module.exports = { + config: './eslint.config.js', +}; diff --git a/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest.config.js b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest.config.js new file mode 100644 index 0000000..94f5944 --- /dev/null +++ b/integrationTests/__fixtures__/legacy-config-at-eslint.config.js/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + runner: '../../../', + testMatch: ['**/__eslint__/**/*.js'], +}; diff --git a/integrationTests/__snapshots__/flat-config.test.js.snap b/integrationTests/__snapshots__/flat-config.test.js.snap new file mode 100644 index 0000000..3e54162 --- /dev/null +++ b/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. +" +`; diff --git a/integrationTests/__snapshots__/legacy-config-at-eslint.config.js.test.js.snap b/integrationTests/__snapshots__/legacy-config-at-eslint.config.js.test.js.snap new file mode 100644 index 0000000..b4c905e --- /dev/null +++ b/integrationTests/__snapshots__/legacy-config-at-eslint.config.js.test.js.snap @@ -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. +" +`; diff --git a/integrationTests/flat-config.test.js b/integrationTests/flat-config.test.js new file mode 100644 index 0000000..55d716d --- /dev/null +++ b/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(); + }, +); diff --git a/integrationTests/legacy-config-at-eslint.config.js.test.js b/integrationTests/legacy-config-at-eslint.config.js.test.js new file mode 100644 index 0000000..59fad05 --- /dev/null +++ b/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(); + }, +); diff --git a/src/runner/runESLint.js b/src/runner/runESLint.js index f94424f..144bb25 100644 --- a/src/runner/runESLint.js +++ b/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`: @@ -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 = { @@ -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, }; } @@ -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({ @@ -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) { diff --git a/yarn.lock b/yarn.lock index 480ab10..a6261e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -988,10 +988,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.40.0": - version "8.40.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" - integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== +"@eslint/js@8.41.0": + version "8.41.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3" + integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== "@humanwhocodes/config-array@^0.11.8": version "0.11.8" @@ -2194,14 +2194,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== "eslint@^7 || ^8": - version "8.40.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4" - integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ== + version "8.41.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.41.0.tgz#3062ca73363b4714b16dbc1e60f035e6134b6f1c" + integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.3" - "@eslint/js" "8.40.0" + "@eslint/js" "8.41.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2221,13 +2221,12 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: find-up "^5.0.0" glob-parent "^6.0.2" globals "^13.19.0" - grapheme-splitter "^1.0.4" + graphemer "^1.4.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" @@ -2550,10 +2549,10 @@ graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" @@ -3254,11 +3253,6 @@ jest-worker@^29.5.0: import-local "^3.0.2" jest-cli "^29.5.0" -js-sdsl@^4.1.4: - version "4.4.0" - resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" - integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"