diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 00f9c2f0bb..0482d0d8fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: npm install + - run: npm install --legacy-peer-deps - run: npx c8 ava - uses: codecov/codecov-action@v5 with: @@ -42,14 +42,14 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install + - run: npm install --legacy-peer-deps - run: npm run lint run-rules-on-codebase: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install + - run: npm install --legacy-peer-deps - run: npm run run-rules-on-codebase integration: name: Integration test (${{ matrix.group }}) @@ -72,5 +72,5 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install + - run: npm install --legacy-peer-deps - run: npm run integration -- --group ${{ matrix.group }} diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 80b39d78bc..d1326b0cfe 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - - run: npm install + - run: npm install --legacy-peer-deps - uses: AriPerkkio/eslint-remote-tester-run-action@v5 with: issue-title: "Results of weekly scheduled smoke test" diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 69d4835859..bf6e6c6226 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -15,7 +15,7 @@ jobs: with: # Locked due to the difference of `zlib.gzipSync()` between Node.js versions node-version: 24 - - run: npm install + - run: npm install --legacy-peer-deps - run: npm run lint - run: rm -rf test/snapshots # Force update snapshots, https://github.com/avajs/ava/discussions/2754 diff --git a/package.json b/package.json index c041fbeee8..eec4568b75 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "ava": "^6.4.1", "c8": "^10.1.3", "enquirer": "^2.4.1", - "eslint": "^9.38.0", + "eslint": "^10.0.0-beta.0", "eslint-ava-rule-tester": "^5.0.1", "eslint-config-xo": "^0.49.0", "eslint-doc-generator": "^2.3.0", @@ -109,7 +109,7 @@ "yaml": "^2.8.1" }, "peerDependencies": { - "eslint": ">=9.38.0" + "eslint": ">=9.38.0 || >=10.0.0-0" }, "ava": { "files": [ diff --git a/rules/custom-error-definition.js b/rules/custom-error-definition.js index 08e5ad7832..5c367e7eb4 100644 --- a/rules/custom-error-definition.js +++ b/rules/custom-error-definition.js @@ -1,4 +1,7 @@ -import {upperFirst} from './utils/index.js'; +import { + upperFirst, + getParenthesizedText, +} from './utils/index.js'; const MESSAGE_ID_INVALID_EXPORT = 'invalidExport'; const messages = { @@ -121,7 +124,12 @@ function * customErrorDefinition(context, node) { if (superExpression.expression.arguments.length === 0) { const rhs = expression.expression.right; const [start] = sourceCode.getRange(superExpression); - yield fixer.insertTextAfterRange([start, start + 6], rhs.raw || rhs.name); + // This part crashes on ESLint 10, but it's still not correct. + // There can be spaces, comments after `super` + yield fixer.insertTextAfterRange( + [start, start + 6], + getParenthesizedText(rhs, context), + ); } const start = messageExpressionIndex === 0 diff --git a/rules/prefer-string-slice.js b/rules/prefer-string-slice.js index 71c77fceb1..5912ec59d6 100644 --- a/rules/prefer-string-slice.js +++ b/rules/prefer-string-slice.js @@ -48,7 +48,7 @@ function * fixSubstrArguments({node, fixer, context, abort}) { } if (typeof getNumericValue(secondArgument) === 'number') { - yield replaceSecondArgument(Math.max(0, getNumericValue(secondArgument))); + yield replaceSecondArgument(String(Math.max(0, getNumericValue(secondArgument)))); return; } @@ -58,7 +58,7 @@ function * fixSubstrArguments({node, fixer, context, abort}) { } if (argumentNodes.every(node => isNumericLiteral(node))) { - yield replaceSecondArgument(firstArgument.value + secondArgument.value); + yield replaceSecondArgument(String(firstArgument.value + secondArgument.value)); return; } @@ -78,7 +78,7 @@ function * fixSubstringArguments({node, fixer, context, abort}) { } if (firstNumber !== undefined) { - yield replaceFirstArgument(Math.max(0, firstNumber)); + yield replaceFirstArgument(String(Math.max(0, firstNumber))); return; } @@ -99,18 +99,18 @@ function * fixSubstringArguments({node, fixer, context, abort}) { } if (argumentsValue[0] !== firstNumber) { - yield replaceFirstArgument(argumentsValue[0]); + yield replaceFirstArgument(String(argumentsValue[0])); } if (argumentsValue[1] !== secondNumber) { - yield replaceSecondArgument(argumentsValue[1]); + yield replaceSecondArgument(String(argumentsValue[1])); } return; } if (firstNumber === 0 || secondNumber === 0) { - yield replaceFirstArgument(0); + yield replaceFirstArgument('0'); yield replaceSecondArgument(`Math.max(0, ${firstNumber === 0 ? secondArgumentText : firstArgumentText})`); return; } diff --git a/scripts/parsers.js b/scripts/parsers.js new file mode 100644 index 0000000000..e2a45dc905 --- /dev/null +++ b/scripts/parsers.js @@ -0,0 +1,97 @@ +/* +Based on https://github.com/eslint/eslint/pull/20132 +Workaround for https://github.com/typescript-eslint/typescript-eslint/issues/11762 +*/ + +import {Variable} from 'eslint-scope'; +import typescriptEslintParserOriginal from '@typescript-eslint/parser'; +import babelEslintParserOriginal from '@babel/eslint-parser'; +import vueEslintParserOriginal from 'vue-eslint-parser'; + +function addGlobals(names) { + const globalScope = this.scopes[0]; + for (const name of names) { + let variable = globalScope.set.get(name); + + if (variable) { + continue; + } + + variable = new Variable(name, globalScope); + + globalScope.variables.push(variable); + globalScope.set.set(name, variable); + } + + /* + * "through" contains all references which definitions cannot be found. + * Since we augment the global scope we need to update references + * and remove the ones that were added. + * + * Also, typescript-eslint's scope manager doesn't resolve references + * to global `var` and `function` variables, so we'll resolve _all_ + * references to variables that exist in the global scope. + */ + globalScope.through = globalScope.through.filter(reference => { + const {name} = reference.identifier; + const variable = globalScope.set.get(name); + + if (variable) { + /* + * Links the variable and the reference. + * And this reference is removed from `Scope#through`. + */ + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); + + /* + * "implicit" contains information about implicit global variables (those created + * implicitly by assigning values to undeclared variables in non-strict code). + * Since we augment the global scope, we need to remove the ones that were added. + */ + const {implicit} = globalScope; + implicit.variables = implicit.variables.filter(variable => { + const {name} = variable; + if (globalScope.set.has(name)) { + implicit.set.delete(name); + return false; + } + + return true; + }); + + // Typescript-eslint's scope manager doesn't produce "implicit.left" +} + +function fixParse(parse) { + return function parseForESLint(...arguments_) { + const result = parse(...arguments_); + + if (result.scopeManager) { + result.scopeManager.addGlobals ??= addGlobals; + } + + return result; + }; +} + +export const typescriptEslintParser = { + ...typescriptEslintParserOriginal, + parseForESLint: fixParse(typescriptEslintParserOriginal.parseForESLint), +}; + +export const babelEslintParser = { + ...babelEslintParserOriginal, + parseForESLint: fixParse(babelEslintParserOriginal.parseForESLint), +}; + +export const vueEslintParser = { + ...vueEslintParserOriginal, + parseForESLint: fixParse(vueEslintParserOriginal.parseForESLint), +}; diff --git a/test/custom-error-definition.js b/test/custom-error-definition.js index ed380f677f..20b5e550da 100644 --- a/test/custom-error-definition.js +++ b/test/custom-error-definition.js @@ -457,6 +457,28 @@ const tests = { `, errors: [passMessageToSuperError], }, + { + code: outdent` + class FooError extends Error { + constructor() { + super(); + this.message = foo.error; + this.name = 'FooError'; + } + } + `, + output: outdent` + class FooError extends Error { + constructor() { + super(foo.error); + this.name = 'FooError'; + } + } + `, + errors: [ + passMessageToSuperError, + ], + }, ], }; diff --git a/test/expiring-todo-comments.js b/test/expiring-todo-comments.js index 57728e3cb6..c3590b6993 100644 --- a/test/expiring-todo-comments.js +++ b/test/expiring-todo-comments.js @@ -55,7 +55,6 @@ test({ '// FIXME [2200-12-12] (lubien): Too long... Can you feel it?', { code: '// Expire Condition [2200-12-12]: new term name', - errors: [], options: [{terms: ['Expire Condition']}], }, '// Expire Condition [2000-01-01]: new term name', @@ -80,19 +79,15 @@ test({ */`, { code: '// TODO', - errors: [], }, { code: '// TODO [invalid]', - errors: [], }, { code: '// TODO [] might have [some] that [try [to trick] me]', - errors: [], }, { code: '// TODO [but [it will]] [fallback] [[[ to the default ]]] rule [[', - errors: [], }, { code: '// TODO ISSUE-123 fix later', diff --git a/test/integration/projects.js b/test/integration/projects.js index 83e937b69a..7b15a55bae 100644 --- a/test/integration/projects.js +++ b/test/integration/projects.js @@ -38,7 +38,13 @@ export default [ 'test-tap/fixture/report/edgecases/ast-syntax-error.cjs', ], }, - 'https://github.com/chalk/chalk', + { + repository: 'https://github.com/chalk/chalk', + ignore: [ + // Uses `/* eslint-env */` + 'source/vendor/supports-color/browser.js', + ], + }, 'https://github.com/chalk/wrap-ansi', 'https://github.com/sindresorhus/np', 'https://github.com/sindresorhus/ora', @@ -113,7 +119,15 @@ export default [ 'importAssertions', ], }, - 'https://github.com/ReactTraining/react-router', + { + repository: 'https://github.com/ReactTraining/react-router', + ignore: [ + // Uses `/* eslint-env */` + 'packages/react-router/lib/dom/node-main.js', + 'packages/react-router/node-main-dom-export.js', + 'packages/react-router/node-main.js', + ], + }, // #902 { repository: 'https://github.com/reakit/reakit', @@ -146,6 +160,13 @@ export default [ { repository: 'https://github.com/TheThingsNetwork/lorawan-stack', babelPlugins: ['decorators'], + ignore: [ + // Uses `/* eslint-env */` + 'config/storybook/webpack.config.js', + 'config/webpack.config.babel.js', + 'config/webpack.dll.babel.js', + 'pkg/webui/lib/get-by-path_test.js', + ], }, 'https://github.com/zloirock/core-js', { @@ -184,6 +205,10 @@ export default [ 'test/**', '**/tests/**', '**/compiled/**', + + // Uses `/* eslint-env */` + 'packages/next/src/server/lib/router-utils/decode-path-params.test.ts', + 'packages/next/src/server/node-polyfill-crypto.test.ts', ], }, // #903 @@ -204,6 +229,10 @@ export default [ // Global return 'scripts/cypress.js', + + // Uses `/* eslint-env */` + 'packages/charts/chart-web-components/scripts/e2e.js', + 'packages/web-components/scripts/e2e.js', ], }, { @@ -236,6 +265,8 @@ export default [ 'src/vs/platform/files/test/node/fixtures/**', 'src/vs/workbench/services/search/test/node/fixtures/examples/**', 'extensions/vscode-colorize-perf-tests/test/**', + // Uses `/* eslint-env */` + 'test/unit/electron/renderer.js', ], }, ].flatMap((projectOrProjects, index) => diff --git a/test/integration/run-eslint.js b/test/integration/run-eslint.js index b253a3192f..b9425f946f 100644 --- a/test/integration/run-eslint.js +++ b/test/integration/run-eslint.js @@ -5,9 +5,11 @@ import {codeFrameColumns} from '@babel/code-frame'; import {ESLint} from 'eslint'; import styleText from 'node-style-text'; import {outdent} from 'outdent'; -import babelParser from '@babel/eslint-parser'; -import typescriptParser from '@typescript-eslint/parser'; -import vueParser from 'vue-eslint-parser'; +import { + babelEslintParser, + typescriptEslintParser, + vueEslintParser, +} from '../../scripts/parsers.js'; import prettyMilliseconds from 'pretty-ms'; import eslintPluginUnicorn from '../../index.js'; @@ -61,7 +63,7 @@ const basicConfigs = [ { files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'], languageOptions: { - parser: typescriptParser, + parser: typescriptEslintParser, parserOptions: { project: [], }, @@ -70,7 +72,7 @@ const basicConfigs = [ { files: ['**/*.vue'], languageOptions: { - parser: vueParser, + parser: vueEslintParser, parserOptions: { parser: '@typescript-eslint/parser', ecmaFeatures: { @@ -86,7 +88,7 @@ function getBabelParserConfig(project) { return { languageOptions: { sourceType: 'module', - parser: babelParser, + parser: babelEslintParser, parserOptions: { requireConfigFile: false, babelOptions: { diff --git a/test/smoke/eslint-remote-tester.config.js b/test/smoke/eslint-remote-tester.config.js index 35b8c09c3e..e76c3ea907 100644 --- a/test/smoke/eslint-remote-tester.config.js +++ b/test/smoke/eslint-remote-tester.config.js @@ -2,8 +2,10 @@ import { getRepositories, getPathIgnorePattern, } from 'eslint-remote-tester-repositories'; -import typescriptParser from '@typescript-eslint/parser'; -import vueParser from 'vue-eslint-parser'; +import { + typescriptEslintParser, + vueEslintParser, +} from '../../scripts/parsers.js'; import eslintPluginUnicorn from '../../index.js'; /** @type {import('eslint-remote-tester').Config} */ @@ -53,7 +55,7 @@ const config = { '**/*.tsx', ], languageOptions: { - parser: typescriptParser, + parser: typescriptEslintParser, parserOptions: { project: [], }, @@ -64,7 +66,7 @@ const config = { '**/*.vue', ], languageOptions: { - parser: vueParser, + parser: vueEslintParser, parserOptions: { parser: '@typescript-eslint/parser', ecmaFeatures: { diff --git a/test/utils/parsers.js b/test/utils/parsers.js index 59e4d43bfa..603e21039a 100644 --- a/test/utils/parsers.js +++ b/test/utils/parsers.js @@ -1,6 +1,8 @@ -import babelEslintParser from '@babel/eslint-parser'; -import typescriptEslintParser from '@typescript-eslint/parser'; -import vueEslintParser from 'vue-eslint-parser'; +import { + typescriptEslintParser, + babelEslintParser, + vueEslintParser, +} from '../../scripts/parsers.js'; const babelParser = { name: 'babel',