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..b17a2b3 --- /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') ? 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..c07d135 --- /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') ? 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/package.json b/package.json index 2550c8d..24a190b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "chalk": "^4.0.0", "cosmiconfig": "^7.0.0", "create-jest-runner": "^0.11.2", - "dot-prop": "^6.0.1" + "dot-prop": "^6.0.1", + "find-up": "^5.0.0", + "semver": "^7.3.8" }, "devDependencies": { "@babel/cli": "^7.10.4", @@ -46,8 +48,7 @@ "jest-watch-select-projects": "^2.0.0", "jest-watch-typeahead": "^1.1.0 || ^2.1.1", "prettier": "^2.8.4", - "rimraf": "^3.0.2", - "semver": "^7.3.8" + "rimraf": "^3.0.2" }, "peerDependencies": { "eslint": "^7 || ^8", diff --git a/src/runner/runESLint.js b/src/runner/runESLint.js index f94424f..c59f334 100644 --- a/src/runner/runESLint.js +++ b/src/runner/runESLint.js @@ -1,6 +1,13 @@ const { ESLint } = require('eslint'); +const { version } = require('eslint/package.json'); +const findUp = require('find-up'); +const semver = require('semver'); + const getESLintOptions = require('../utils/getESLintOptions'); +const ESLINT_SUPPORTS_FLAT_CONFIG = semver.satisfies(version, '>=8'); +const ESLINT_FLAT_CONFIG_DEFAULT_FILENAME = 'eslint.config.js'; + /* * This function exists because there are issues with the `pass`, `skip`, and * `fail` functions from `create-jest-runner`: @@ -96,8 +103,70 @@ const getComputedFixValue = ({ fix, quiet, fixDryRun }) => { return undefined; }; +const useFlatConfig = async () => { + if (ESLINT_SUPPORTS_FLAT_CONFIG) { + switch (process.env.ESLINT_USE_FLAT_CONFIG) { + case 'true': + return true; + case 'false': + return false; + default: { + const eslintFlatConfig = await findUp( + ESLINT_FLAT_CONFIG_DEFAULT_FILENAME, + { cwd: process.cwd() }, + ); + return eslintFlatConfig !== undefined; + } + } + } + return false; +}; +const getESLintConstructor = async () => { + if (await useFlatConfig()) { + // 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 + return require('eslint/use-at-your-own-risk').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 useFlatConfig()) { + // 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 +175,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 +208,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 +230,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) {