From 598f57bb9f40727f50e113dda4a561d001251cc5 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 9 Apr 2024 18:51:41 +0800 Subject: [PATCH] Update ESLint to v9, drop support for Node.js v16 (#2250) --- .github/workflows/main.yml | 17 +- package.json | 7 +- test/better-regex.mjs | 23 +- test/catch-error-name.mjs | 15 +- test/consistent-destructuring.mjs | 6 +- test/consistent-function-scoping.mjs | 18 +- test/empty-brace-spaces.mjs | 29 +-- test/explicit-length-check.mjs | 37 +-- test/filename-case.mjs | 11 +- test/import-style.mjs | 38 +-- test/new-for-builtins.mjs | 6 +- test/no-abusive-eslint-disable.mjs | 37 +-- test/no-anonymous-default-export.mjs | 32 +-- test/no-array-callback-reference.mjs | 71 ++++-- test/no-array-for-each.mjs | 37 ++- test/no-array-method-this-argument.mjs | 8 +- test/no-array-reduce.mjs | 11 +- test/no-console-spaces.mjs | 1 - test/no-for-loop.mjs | 2 +- test/no-instanceof-array.mjs | 2 +- test/no-null.mjs | 6 +- test/no-static-only-class.mjs | 10 +- test/no-unnecessary-await.mjs | 8 +- test/no-useless-undefined.mjs | 17 +- test/number-literal-case.mjs | 10 +- test/numeric-separators-style.mjs | 4 +- test/prefer-array-find.mjs | 2 +- test/prefer-array-flat.mjs | 2 +- test/prefer-array-some.mjs | 12 +- test/prefer-dom-node-append.mjs | 4 - test/prefer-export-from.mjs | 2 +- test/prefer-keyboard-event-key.mjs | 18 -- test/prefer-module.mjs | 8 +- test/prefer-regexp-test.mjs | 10 +- test/prefer-set-has.mjs | 20 +- test/prefer-spread.mjs | 2 +- test/prefer-string-slice.mjs | 19 -- test/prefer-string-starts-ends-with.mjs | 4 +- test/prefer-ternary.mjs | 14 -- test/prefer-top-level-await.mjs | 4 +- test/prevent-abbreviations.mjs | 321 +++++++++++------------- test/string-content.mjs | 10 +- test/text-encoding-identifier-case.mjs | 8 +- test/utils/default-options.mjs | 11 +- test/utils/language-options.mjs | 61 +++++ test/utils/parsers.mjs | 52 ++-- test/utils/snapshot-rule-tester.mjs | 108 ++++---- test/utils/test.mjs | 124 ++++----- 48 files changed, 615 insertions(+), 664 deletions(-) create mode 100644 test/utils/language-options.mjs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 019a493deb..1d33c82ba1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,19 +16,20 @@ jobs: matrix: node-version: - 20 - - 16 + - 18 os: - ubuntu-latest - windows-latest - include: - - os: ubuntu-latest - node-version: 18 + # Even numbers of node-version + # include: + # - os: ubuntu-latest + # node-version: 18 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: npm install + - run: npm install --legacy-peer-deps - run: npx ava lint-test: runs-on: ${{ matrix.os }} @@ -43,7 +44,7 @@ jobs: with: # Locked due to the difference of `zlib.gzipSync()` between Node.js versions node-version: 20 - - run: npm install + - run: npm install --legacy-peer-deps - run: npm run lint - run: npx del-cli test/snapshots --verbose # Force update snapshots, https://github.com/avajs/ava/discussions/2754 @@ -61,7 +62,7 @@ jobs: 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 }}) @@ -84,5 +85,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/package.json b/package.json index 9c9111feba..4580b291b9 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "@eslint-community/eslint-utils": "^4.4.0", - "@eslint/eslintrc": "^2.1.4", + "@eslint/eslintrc": "^3.0.0", "ci-info": "^4.0.0", "clean-regexp": "^1.0.0", "core-js-compat": "^3.34.0", @@ -69,20 +69,21 @@ "devDependencies": { "@babel/code-frame": "^7.23.5", "@babel/core": "^7.23.6", - "@babel/eslint-parser": "^7.23.3", + "@babel/eslint-parser": "^7.24.1", "@lubien/fixture-beta-package": "^1.0.0-beta.1", "@typescript-eslint/parser": "^7.5.0", "ava": "^6.0.1", "c8": "^8.0.1", "chalk": "^5.3.0", "enquirer": "^2.4.1", - "eslint": "^8.56.0", + "eslint": "^9.0.0", "eslint-ava-rule-tester": "^5.0.1", "eslint-doc-generator": "^1.7.0", "eslint-plugin-eslint-plugin": "^5.2.1", "eslint-plugin-internal-rules": "file:./scripts/internal-rules/", "eslint-remote-tester": "^3.0.1", "eslint-remote-tester-repositories": "^1.0.1", + "espree": "^10.0.0", "execa": "^8.0.1", "listr": "^0.14.3", "lodash-es": "^4.17.21", diff --git a/test/better-regex.mjs b/test/better-regex.mjs index 5675d04fba..e976120a54 100644 --- a/test/better-regex.mjs +++ b/test/better-regex.mjs @@ -1,8 +1,6 @@ -import {createRequire} from 'node:module'; -import {getTester} from './utils/test.mjs'; +import {getTester, parsers} from './utils/test.mjs'; const {test} = getTester(import.meta); -const require = createRequire(import.meta.url); const MESSAGE_ID = 'better-regex'; @@ -220,21 +218,6 @@ test({ errors: createError('[0-9]', '\\d'), output: 'const foo = new RegExp(\'\\\\d\', \'ig\')', }, - { - code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', - }, - { - code: 'const foo = new RegExp(/[0-9]/, \'ig\')', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, \'ig\')', - }, - { - code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', - }, { code: 'const foo = new RegExp(/[0-9]/, \'ig\')', errors: createError('/[0-9]/', '/\\d/'), @@ -310,7 +293,9 @@ test({ message: 'Problem parsing /(/: \n\n/(/\n ^\nUnexpected token: "/" at 1:2.', }, ], - parser: require.resolve('@typescript-eslint/parser'), + languageOptions: { + parser: parsers.typescript, + }, }, // Not fixable diff --git a/test/catch-error-name.mjs b/test/catch-error-name.mjs index 41b8f14dd3..2c0bed9fde 100644 --- a/test/catch-error-name.mjs +++ b/test/catch-error-name.mjs @@ -29,7 +29,6 @@ function invalidTestCase(options) { test({ valid: [ - 'try {} catch (error) {}', { code: 'try {} catch (err) {}', options: [{name: 'err'}], @@ -900,12 +899,14 @@ test.typescript({ test.babel({ testerOptions: { - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, diff --git a/test/consistent-destructuring.mjs b/test/consistent-destructuring.mjs index ebe0526631..9bc1af5072 100644 --- a/test/consistent-destructuring.mjs +++ b/test/consistent-destructuring.mjs @@ -444,7 +444,7 @@ test({ b } } = foo; - console.log(foo.a.c); + console.log(foo.a.c); // 2 `, errors: [{ message: 'Use destructured variables over properties.', @@ -453,7 +453,7 @@ test({ { code: outdent` const {a} = foo; - console.log(foo.a); + console.log(foo.a); // 2 `, errors: [{ message: 'Use destructured variables over properties.', @@ -461,7 +461,7 @@ test({ desc: 'Replace `foo.a` with destructured property `a`.', output: outdent` const {a} = foo; - console.log(a); + console.log(a); // 2 `, }], }], diff --git a/test/consistent-function-scoping.mjs b/test/consistent-function-scoping.mjs index 5f0ed084df..6ec71dc2dd 100644 --- a/test/consistent-function-scoping.mjs +++ b/test/consistent-function-scoping.mjs @@ -15,9 +15,11 @@ const createError = (functionNameWithKind, loc) => ({ test({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -473,12 +475,6 @@ test({ `, errors: [createError('arrow function \'doBar\'')], }, - { - code: outdent` - const doFoo = () => bar => bar; - `, - errors: [createError('arrow function')], - }, // `this` { code: outdent` @@ -628,9 +624,9 @@ test({ }, // Actual message { - code: 'function foo() { async function* bar() {} }', + code: 'function foo() { async function* baz() {} }', errors: [{ - message: 'Move async generator function \'bar\' to the outer scope.', + message: 'Move async generator function \'baz\' to the outer scope.', }], }, // React Hooks diff --git a/test/empty-brace-spaces.mjs b/test/empty-brace-spaces.mjs index f925ad7b0e..6200a92363 100644 --- a/test/empty-brace-spaces.mjs +++ b/test/empty-brace-spaces.mjs @@ -16,13 +16,12 @@ const cases = [ 'switch (foo) {case bar: {/* */}}', 'switch (foo) {default: {/* */}}', 'try {/* */} catch(foo){}', - 'try {} catch(foo){/* */}', + 'try {} catch(bar){/* */}', 'try {} catch(foo){} finally {/* */}', 'do {/* */} while (foo)', 'while (foo){/* */}', 'foo = () => {/* */}', 'foo = function (){/* */}', - 'function foo(){/* */}', 'foo = {/* */}', 'class Foo {bar() {/* */}}', 'foo = class {bar() {/* */}}', @@ -49,11 +48,11 @@ test({ ].flatMap(body => allCases.map(code => code.replace(SPACES_PLACEHOLDER, body))), // Not empty ...cases.map(code => code.replace(SPACES_PLACEHOLDER, 'unicorn')), - ...classBodyCases.map(code => code.replace(SPACES_PLACEHOLDER, 'bar() {}')), + ...classBodyCases.map(code => code.replace(SPACES_PLACEHOLDER, 'baz() {}')), // `with` { code: 'with (foo) {}', - parserOptions: {ecmaVersion: 5, sourceType: 'script'}, + languageOptions: {ecmaVersion: 5, sourceType: 'script'}, }, // We don't check these cases ...ignoredCases.map(code => code.replace(SPACES_PLACEHOLDER, ' ')), @@ -75,7 +74,7 @@ test({ code: 'with (foo) { }', output: 'with (foo) {}', errors: 1, - parserOptions: {ecmaVersion: 5, sourceType: 'script'}, + languageOptions: {ecmaVersion: 5, sourceType: 'script'}, }, ], }); @@ -94,9 +93,11 @@ test.snapshot({ }); const enableBabelPlugins = plugins => ({ - babelOptions: { - parserOpts: { - plugins, + parserOptions: { + babelOptions: { + parserOpts: { + plugins, + }, }, }, }); @@ -110,19 +111,19 @@ test.babel({ }; `, output: 'const foo = do {};', - parserOptions: enableBabelPlugin('doExpressions'), + languageOptions: enableBabelPlugin('doExpressions'), errors: 1, }, { code: 'const record = #{ };', output: 'const record = #{};', - parserOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'hash'}]), + languageOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'hash'}]), errors: 1, }, { code: 'const record = {| |};', output: 'const record = {||};', - parserOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'bar'}]), + languageOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'bar'}]), errors: 1, }, { @@ -137,14 +138,14 @@ test.babel({ static {} } `, - parserOptions: enableBabelPlugin('classStaticBlock'), + languageOptions: enableBabelPlugin('classStaticBlock'), errors: 1, }, // ESLint can't parse this now // { // code: 'const foo = module { };', // output: 'const foo = module {};', - // parserOptions: enableBabelPlugin('moduleBlocks'), + // languageOptions: enableBabelPlugin('moduleBlocks'), // errors: 1 // }, { @@ -153,7 +154,7 @@ test.babel({ }; `, output: 'const foo = async do {};', - parserOptions: enableBabelPlugins(['doExpressions', 'asyncDoExpressions']), + languageOptions: enableBabelPlugins(['doExpressions', 'asyncDoExpressions']), errors: 1, }, ], diff --git a/test/explicit-length-check.mjs b/test/explicit-length-check.mjs index f923935aa1..6dbd49558d 100644 --- a/test/explicit-length-check.mjs +++ b/test/explicit-length-check.mjs @@ -3,20 +3,18 @@ import {getTester, parsers} from './utils/test.mjs'; const {test} = getTester(import.meta); -const suggestionCase = ({code, output, desc, options = []}) => { - const suggestion = {output}; - if (desc) { - suggestion.desc = desc; - } +const TYPE_NON_ZERO = 'non-zero'; - return { - code, - options, - errors: [ - {suggestions: [suggestion]}, - ], - }; -}; +const suggestionCase = ({code, messageId, output, desc, options = []}) => ({ + code, + options, + errors: [ + { + messageId, + suggestions: [{desc, output}], + }, + ], +}); const nonZeroCases = [ 'foo.length', @@ -110,37 +108,46 @@ test({ invalid: [ suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || bar()', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const x = foo.length || unknown', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || unknown', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const NON_NUMBER = "2"; const x = foo.length || NON_NUMBER', + messageId: TYPE_NON_ZERO, output: 'const NON_NUMBER = "2"; const x = foo.length > 0 || NON_NUMBER', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length !== 0 || bar()', desc: 'Replace `.length` with `.length !== 0`.', options: [{'non-zero': 'not-equal'}], }), suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || bar()', desc: 'Replace `.length` with `.length > 0`.', options: [{'non-zero': 'greater-than'}], }), suggestionCase({ code: '() => foo.length && bar()', + messageId: TYPE_NON_ZERO, + desc: 'Replace `.length` with `.length > 0`.', output: '() => foo.length > 0 && bar()', }), suggestionCase({ code: 'alert(foo.length && bar())', + messageId: TYPE_NON_ZERO, + desc: 'Replace `.length` with `.length > 0`.', output: 'alert(foo.length > 0 && bar())', }), ], @@ -223,7 +230,9 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: { + parser: parsers.vue, + }, }, valid: [ '
', diff --git a/test/filename-case.mjs b/test/filename-case.mjs index 26c3969b83..69e51ddbfc 100644 --- a/test/filename-case.mjs +++ b/test/filename-case.mjs @@ -19,9 +19,8 @@ function testManyCases(filename, chosenCases, errorMessage) { } function testCaseWithOptions(filename, errorMessage, options = []) { - return { - code: `/* Filename ${filename} */`, - filename, + const testCase = { + code: `/* Filename: ${filename} */`, options, errors: errorMessage && [ { @@ -29,6 +28,12 @@ function testCaseWithOptions(filename, errorMessage, options = []) { }, ], }; + + if (filename !== undefined) { + testCase.filename = filename; + } + + return testCase; } test({ diff --git a/test/import-style.mjs b/test/import-style.mjs index cd0a821c1b..7ed675d94c 100644 --- a/test/import-style.mjs +++ b/test/import-style.mjs @@ -141,14 +141,6 @@ test({ code: 'import {default as chalk} from \'chalk\'', options: [], }, - { - code: 'const {inspect} = require(\'util\')', - options: [], - }, - { - code: 'const {inspect} = require(\'node:util\')', - options: [], - }, { code: 'export {promisify, callbackify} from \'util\'', options: [], @@ -260,10 +252,6 @@ test({ `, errors: [unassignedError], }, - { - code: 'const {x} = require(\'unassigned\')', - errors: [unassignedError], - }, { code: 'const {x: y} = require(\'unassigned\')', errors: [unassignedError], @@ -552,62 +540,62 @@ test({ { code: 'import util from \'util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import util from \'node:util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import * as util from \'util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import * as util from \'node:util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'const util = require(\'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'const util = require(\'node:util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'node:util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'ut\' + \'il\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'node:\' + \'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'import {red} from \'chalk\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import {red as green} from \'chalk\'', options: [], - errors: [{}], + errors: 1, }, { code: outdent` @@ -616,7 +604,7 @@ test({ } `, options: [], - errors: [{}], + errors: 1, }, { diff --git a/test/new-for-builtins.mjs b/test/new-for-builtins.mjs index 6ebc585371..fa042643da 100644 --- a/test/new-for-builtins.mjs +++ b/test/new-for-builtins.mjs @@ -101,7 +101,7 @@ test.snapshot({ { code: 'new Symbol("")', - globals: {Symbol: 'off'}, + languageOptions: {globals: {Symbol: 'off'}}, }, ], invalid: [ @@ -190,14 +190,14 @@ test.snapshot({ `, { code: 'globalThis.Array()', - globals: {Array: 'off'}, + languageOptions: {globals: {Array: 'off'}}, }, { code: outdent` const {Array} = globalThis; Array(); `, - globals: {Array: 'off'}, + languageOptions: {globals: {Symbol: 'off'}}, }, 'const foo = Object()', 'const foo = Array()', diff --git a/test/no-abusive-eslint-disable.mjs b/test/no-abusive-eslint-disable.mjs index 111450c748..96bc0db7fa 100644 --- a/test/no-abusive-eslint-disable.mjs +++ b/test/no-abusive-eslint-disable.mjs @@ -4,16 +4,12 @@ import {getTester} from './utils/test.mjs'; const {test} = getTester(import.meta); test({ - beforeAll(tester) { - // Define rules for test - for (const rule of [ - 'plugin/rule', - '@scope/plugin/rule-name', - '@scope/rule-name', - '@scopewithoutplugin', - ]) { - tester.linter.defineRule(rule, {}); - } + testerOptions: { + plugins: Object.fromEntries([ + ['plugin-name', 'rule-name'], + ['@scope/plugin', 'rule-name'], + ['@scope', 'rule-name'], + ].map(([pluginName, ruleName]) => [pluginName, {rules: {[ruleName]: {}}}])), }, valid: [ 'eval();', @@ -23,7 +19,7 @@ test({ 'eval(); // eslint-disable-line no-eval', 'eval(); //\teslint-disable-line no-eval', 'eval(); /* eslint-disable-line no-eval */', - 'eval(); // eslint-disable-line plugin/rule', + 'eval(); // eslint-disable-line plugin-name/rule-name', 'eval(); // eslint-disable-line @scope/plugin/rule-name', 'eval(); // eslint-disable-line no-eval, @scope/plugin/rule-name', 'eval(); // eslint-disable-line @scope/rule-name', @@ -31,10 +27,11 @@ test({ 'eval(); // eslint-line-disable', 'eval(); // some comment', '/* eslint-disable no-eval */', - outdent` - /* eslint-disable no-abusive-eslint-disable */ - eval(); // eslint-disable-line - `, + // TODO[@fisker]: Figure out how to test this + // outdent` + // /* eslint-disable no-abusive-eslint-disable */ + // eval(); // eslint-disable-line + // `, outdent` foo(); // eslint-disable-line no-eval @@ -51,15 +48,7 @@ test({ eval(); `, ], - invalid: [ - { - code: outdent` - // eslint-disable-next-line @scopewithoutplugin - eval(); - `, - errors: 1, - }, - ], + invalid: [], }); test.snapshot({ diff --git a/test/no-anonymous-default-export.mjs b/test/no-anonymous-default-export.mjs index c9be26d50d..f60bf32d2e 100644 --- a/test/no-anonymous-default-export.mjs +++ b/test/no-anonymous-default-export.mjs @@ -269,25 +269,29 @@ test.snapshot({ // Decorators const decoratorsBeforeExportOptions = { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, }; const decoratorsAfterExportOptions = { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: false}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: false}], + ], + }, }, }, }, diff --git a/test/no-array-callback-reference.mjs b/test/no-array-callback-reference.mjs index a9f3bd9e97..bca779d286 100644 --- a/test/no-array-callback-reference.mjs +++ b/test/no-array-callback-reference.mjs @@ -6,6 +6,8 @@ const {test} = getTester(import.meta); const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name'; const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name'; +const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name'; +const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name'; const simpleMethods = [ 'every', @@ -35,7 +37,8 @@ const generateError = (method, name) => ({ }); // Only test output is good enough -const suggestionOutput = output => ({ +const suggestionOutput = (output, name) => ({ + messageId: name ? REPLACE_WITH_NAME_MESSAGE_ID : REPLACE_WITHOUT_NAME_MESSAGE_ID, output, }); @@ -44,7 +47,7 @@ const invalidTestCase = (({code, method, name, suggestions}) => ({ errors: [ { ...generateError(method, name), - suggestions: suggestions.map(output => suggestionOutput(output)), + suggestions: suggestions.map(output => suggestionOutput(output, name)), }, ], })); @@ -278,27 +281,45 @@ test({ // Actual messages { - code: 'foo.map(fn)', + code: 'bar.map(fn)', errors: [ { message: 'Do not pass function `fn` directly to `.map(…)`.', suggestions: [ - {desc: 'Replace function `fn` with `… => fn(element)`.'}, - {desc: 'Replace function `fn` with `… => fn(element, index)`.'}, - {desc: 'Replace function `fn` with `… => fn(element, index, array)`.'}, + { + desc: 'Replace function `fn` with `… => fn(element)`.', + output: 'bar.map((element) => fn(element))', + }, + { + desc: 'Replace function `fn` with `… => fn(element, index)`.', + output: 'bar.map((element, index) => fn(element, index))', + }, + { + desc: 'Replace function `fn` with `… => fn(element, index, array)`.', + output: 'bar.map((element, index, array) => fn(element, index, array))', + }, ], }, ], }, { - code: 'foo.reduce(fn)', + code: 'bar.reduce(fn)', errors: [ { message: 'Do not pass function `fn` directly to `.reduce(…)`.', suggestions: [ - {desc: 'Replace function `fn` with `… => fn(accumulator, element)`.'}, - {desc: 'Replace function `fn` with `… => fn(accumulator, element, index)`.'}, - {desc: 'Replace function `fn` with `… => fn(accumulator, element, index, array)`.'}, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element)`.', + output: 'bar.reduce((accumulator, element) => fn(accumulator, element))', + }, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element, index)`.', + output: 'bar.reduce((accumulator, element, index) => fn(accumulator, element, index))', + }, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element, index, array)`.', + output: 'bar.reduce((accumulator, element, index, array) => fn(accumulator, element, index, array))', + }, ], }, ], @@ -309,9 +330,18 @@ test({ { message: 'Do not pass function directly to `.map(…)`.', suggestions: [ - {desc: 'Replace function with `… => …(element)`.'}, - {desc: 'Replace function with `… => …(element, index)`.'}, - {desc: 'Replace function with `… => …(element, index, array)`.'}, + { + desc: 'Replace function with `… => …(element)`.', + output: 'foo.map((element) => lib.fn(element))', + }, + { + desc: 'Replace function with `… => …(element, index)`.', + output: 'foo.map((element, index) => lib.fn(element, index))', + }, + { + desc: 'Replace function with `… => …(element, index, array)`.', + output: 'foo.map((element, index, array) => lib.fn(element, index, array))', + }, ], }, ], @@ -322,9 +352,18 @@ test({ { message: 'Do not pass function directly to `.reduce(…)`.', suggestions: [ - {desc: 'Replace function with `… => …(accumulator, element)`.'}, - {desc: 'Replace function with `… => …(accumulator, element, index)`.'}, - {desc: 'Replace function with `… => …(accumulator, element, index, array)`.'}, + { + desc: 'Replace function with `… => …(accumulator, element)`.', + output: 'foo.reduce((accumulator, element) => lib.fn(accumulator, element))', + }, + { + desc: 'Replace function with `… => …(accumulator, element, index)`.', + output: 'foo.reduce((accumulator, element, index) => lib.fn(accumulator, element, index))', + }, + { + desc: 'Replace function with `… => …(accumulator, element, index, array)`.', + output: 'foo.reduce((accumulator, element, index, array) => lib.fn(accumulator, element, index, array))', + }, ], }, ], diff --git a/test/no-array-for-each.mjs b/test/no-array-for-each.mjs index 1d16ba62cc..e5f494f945 100644 --- a/test/no-array-for-each.mjs +++ b/test/no-array-for-each.mjs @@ -547,21 +547,21 @@ test({ } `, errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, { code: 'foo.forEach(function(element, element) {})', errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, { code: 'foo.forEach(function element(element, element) {})', errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, @@ -622,21 +622,22 @@ test.typescript({ ], }); -const globalReturnOptions = { - sourceType: 'script', - ecmaFeatures: { - globalReturn: true, - }, -}; test({ - valid: [ - { - code: outdent` - foo.notForEach(element => bar(element)); - while (true) return; - `, - parserOptions: globalReturnOptions, + testerOptions: { + languageOptions: { + sourceType: 'script', + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, }, + }, + valid: [ + outdent` + foo.notForEach(element => bar(element)); + while (true) return; + `, ], invalid: [ { @@ -649,7 +650,6 @@ test({ for (const element of foo) bar(element); `, errors: 1, - parserOptions: globalReturnOptions, }, { code: outdent` @@ -660,12 +660,10 @@ test({ while (true) return; `, errors: 1, - parserOptions: globalReturnOptions, }, { code: 'return foo.forEach(element => {bar(element)});', errors: 1, - parserOptions: globalReturnOptions, }, { code: outdent` @@ -679,7 +677,6 @@ test({ } `, errors: 1, - parserOptions: globalReturnOptions, }, ], }); diff --git a/test/no-array-method-this-argument.mjs b/test/no-array-method-this-argument.mjs index 6cb708b5c9..c50143a9f9 100644 --- a/test/no-array-method-this-argument.mjs +++ b/test/no-array-method-this-argument.mjs @@ -90,19 +90,19 @@ test.snapshot({ 'Array.from(iterableOrArrayLike, function callback () {}, thisArgument)', { code: 'array.map( foo as bar, (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, { code: 'Array.from(iterableOrArrayLike, foo as bar, (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, { code: 'array.map( (( foo as bar )), (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, { code: 'Array.from(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, 'array.map( (( 0, callback )), (( thisArgument )),)', 'Array.from(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),)', diff --git a/test/no-array-reduce.mjs b/test/no-array-reduce.mjs index 6b370522c0..9b425a4ee4 100644 --- a/test/no-array-reduce.mjs +++ b/test/no-array-reduce.mjs @@ -30,7 +30,7 @@ test({ // Computed 'foo[reduce](fn);', // Not listed method or property - 'foo.notListed(fn);', + 'foo.notListed(fn);// reduce', // More or less argument(s) 'foo.reduce();', 'foo.reduce(fn, extraArgument1, extraArgument2);', @@ -40,7 +40,7 @@ test({ // Not `CallExpression` 'new [].reduce.call(foo, fn);', // Not `MemberExpression` - 'call(foo, fn);', + 'call(foo, fn);// reduce', 'reduce.call(foo, fn);', // `callee.property` is not a `Identifier` '[].reduce["call"](foo, fn);', @@ -50,7 +50,7 @@ test({ '[][reduce].call(foo, fn);', // Not listed method or property '[].reduce.notListed(foo, fn);', - '[].notListed.call(foo, fn);', + '[].notListed.call(foo, fn);// reduce', // Not empty '[1].reduce.call(foo, fn)', // Not ArrayExpression @@ -61,9 +61,6 @@ test({ // Test `Array.prototype.{call,apply}` // Not `CallExpression` 'new Array.prototype.reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', // `callee.property` is not a `Identifier` 'Array.prototype.reduce["call"](foo, fn);', 'Array.prototype["reduce"].call(foo, fn);', @@ -75,7 +72,7 @@ test({ 'Array[prototype].reduce.call(foo, fn);', // Not listed method 'Array.prototype.reduce.notListed(foo, fn);', - 'Array.prototype.notListed.call(foo, fn);', + 'Array.prototype.notListed.call(foo, fn);// reduce', 'Array.notListed.reduce.call(foo, fn);', // Not `Array` 'NotArray.prototype.reduce.call(foo, fn);', diff --git a/test/no-console-spaces.mjs b/test/no-console-spaces.mjs index ce8f692b97..810bd4dbc4 100644 --- a/test/no-console-spaces.mjs +++ b/test/no-console-spaces.mjs @@ -16,7 +16,6 @@ test({ 'console.log("abc", "def");', 'console.log(\'abc\', "def");', 'console.log(`abc`, "def");', - 'console.log("abc", "def");', 'console.log(`\nabc\ndef\n`);', 'console.log(\' \', "def");', diff --git a/test/no-for-loop.mjs b/test/no-for-loop.mjs index 424b73ad62..38107887c5 100644 --- a/test/no-for-loop.mjs +++ b/test/no-for-loop.mjs @@ -730,7 +730,7 @@ test({ test(avoidTestTitleConflict({ testerOptions: { - parserOptions: { + languageOptions: { sourceType: 'script', ecmaVersion: 5, }, diff --git a/test/no-instanceof-array.mjs b/test/no-instanceof-array.mjs index 2f7e394df3..aeba5420ff 100644 --- a/test/no-instanceof-array.mjs +++ b/test/no-instanceof-array.mjs @@ -64,7 +64,7 @@ test.snapshot({ '', '', '', - ].map(code => ({code, parser: parsers.vue})), + ].map(code => ({code, languageOptions: {parser: parsers.vue}})), ], }); diff --git a/test/no-null.mjs b/test/no-null.mjs index 466df09529..40ce5d02c6 100644 --- a/test/no-null.mjs +++ b/test/no-null.mjs @@ -106,8 +106,10 @@ test.snapshot({ // #1146 test({ testerOptions: { - parserOptions: { - ecmaVersion: 2019, + languageOptions: { + parserOptions: { + ecmaVersion: 2019, + }, }, }, valid: [ diff --git a/test/no-static-only-class.mjs b/test/no-static-only-class.mjs index e8d244046e..a74d41c37d 100644 --- a/test/no-static-only-class.mjs +++ b/test/no-static-only-class.mjs @@ -211,10 +211,12 @@ test.typescript({ test.babel({ testerOptions: { - parserOptions: { - babelOptions: { - parserOpts: { - plugins: ['classStaticBlock'], + languageOptions: { + parserOptions: { + babelOptions: { + parserOpts: { + plugins: ['classStaticBlock'], + }, }, }, }, diff --git a/test/no-unnecessary-await.mjs b/test/no-unnecessary-await.mjs index 9ed31094b6..17cb005170 100644 --- a/test/no-unnecessary-await.mjs +++ b/test/no-unnecessary-await.mjs @@ -5,9 +5,11 @@ const {test} = getTester(import.meta); test.snapshot({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, diff --git a/test/no-useless-undefined.mjs b/test/no-useless-undefined.mjs index 110099c05f..c95c7dde35 100644 --- a/test/no-useless-undefined.mjs +++ b/test/no-useless-undefined.mjs @@ -36,7 +36,6 @@ test({ 'assert.notPropertyVal(foo, "bar", undefined, message)', 'expect(foo).not(undefined)', 'expect(foo).to.have.property("bar", undefined)', - 'expect(foo).to.have.property("bar", undefined)', 'expect(foo).toBe(undefined)', 'expect(foo).toContain(undefined)', 'expect(foo).toContainEqual(undefined)', @@ -263,10 +262,12 @@ test({ code: 'return undefined;', output: 'return;', errors, - parserOptions: { - sourceType: 'script', - ecmaFeatures: { - globalReturn: true, + languageOptions: { + parserOptions: { + sourceType: 'script', + ecmaFeatures: { + globalReturn: true, + }, }, }, }, @@ -303,8 +304,6 @@ test.typescript({ 'const foo = (): string => undefined;', 'const foo = function (): undefined {return undefined}', 'export function foo(): undefined {return undefined}', - 'createContext(undefined);', - 'React.createContext(undefined);', outdent` const object = { method(): undefined { @@ -452,7 +451,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: {parser: parsers.vue}, }, valid: [ outdent` @@ -485,7 +484,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, valid: [], invalid: [ diff --git a/test/number-literal-case.mjs b/test/number-literal-case.mjs index 04268887ed..f633fc951b 100644 --- a/test/number-literal-case.mjs +++ b/test/number-literal-case.mjs @@ -12,9 +12,11 @@ const error = { // Legacy octal literals test({ testerOptions: { - parserOptions: { - ecmaVersion: 5, - sourceType: 'script', + languageOptions: { + parserOptions: { + ecmaVersion: 5, + sourceType: 'script', + }, }, }, valid: [ @@ -169,7 +171,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: {parser: parsers.vue}, }, valid: [ '', diff --git a/test/numeric-separators-style.mjs b/test/numeric-separators-style.mjs index d18060ba3e..4f3dc71be8 100644 --- a/test/numeric-separators-style.mjs +++ b/test/numeric-separators-style.mjs @@ -7,7 +7,7 @@ const error = { messageId: 'numeric-separators-style', }; -const legacyOctalParserOptions = {ecmaVersion: 6, sourceType: 'script'}; +const legacyOctalLanguageOptions = {ecmaVersion: 6, sourceType: 'script'}; // Most of these test cases copied from: // https://github.com/eslint/eslint/blob/master/tests/lib/rules/camelcase.js @@ -34,7 +34,7 @@ test({ 'const foo = 0777777', 'var foo = 0999999', 'let foo = 0111222', - ].map(code => ({code, parserOptions: legacyOctalParserOptions})), + ].map(code => ({code, languageOptions: legacyOctalLanguageOptions})), // Binary 'const foo = 0b1010_0001_1000_0101', diff --git a/test/prefer-array-find.mjs b/test/prefer-array-find.mjs index 0ceee26232..e886d293ee 100644 --- a/test/prefer-array-find.mjs +++ b/test/prefer-array-find.mjs @@ -567,7 +567,7 @@ test({ 'const foo = array.find(bar), first = foo[0];', 'const foo = array.filter(bar), first = notFoo[0];', 'const foo = array.filter(bar), first = foo[+0];', - 'const foo = array.filter(bar); first = foo;', + 'const foo2 = array.filter(bar); first = foo;', 'const foo = array.filter(bar), first = a[foo][0];', 'const foo = array.filter(bar), first = foo[-0];', 'const foo = array.filter(bar), first = foo[1-1];', diff --git a/test/prefer-array-flat.mjs b/test/prefer-array-flat.mjs index b60151c879..873e0753e7 100644 --- a/test/prefer-array-flat.mjs +++ b/test/prefer-array-flat.mjs @@ -229,7 +229,7 @@ test.snapshot({ // #1146 test({ testerOptions: { - parserOptions: { + languageOptions: { ecmaVersion: 2019, }, }, diff --git a/test/prefer-array-some.mjs b/test/prefer-array-some.mjs index 4c8f288758..8dadeabcd5 100644 --- a/test/prefer-array-some.mjs +++ b/test/prefer-array-some.mjs @@ -37,12 +37,12 @@ test({ 'find(fn)', // `callee.property` is not a `Identifier` 'foo["find"](fn)', - 'foo["fi" + "nd"](fn)', + 'foo["fi" + "nd"](fn) /* find */', 'foo[`find`](fn)', // Computed 'foo[find](fn)', // Not `.find` - 'foo.notFind(fn)', + 'foo.notFind(fn) /* find */', // More or less argument(s) 'foo.find()', 'foo.find(fn, thisArgument, extraArgument)', @@ -88,28 +88,28 @@ test({ }), // Actual messages { - code: 'if (foo.find(fn)) {}', + code: 'if (bar.find(fn)) {}', errors: [ { message: 'Prefer `.some(…)` over `.find(…)`.', suggestions: [ { desc: 'Replace `.find(…)` with `.some(…)`.', - output: 'if (foo.some(fn)) {}', + output: 'if (bar.some(fn)) {}', }, ], }, ], }, { - code: 'if (foo.findLast(fn)) {}', + code: 'if (bar.findLast(fn)) {}', errors: [ { message: 'Prefer `.some(…)` over `.findLast(…)`.', suggestions: [ { desc: 'Replace `.findLast(…)` with `.some(…)`.', - output: 'if (foo.some(fn)) {}', + output: 'if (bar.some(fn)) {}', }, ], }, diff --git a/test/prefer-dom-node-append.mjs b/test/prefer-dom-node-append.mjs index ad307f13d1..04033ced88 100644 --- a/test/prefer-dom-node-append.mjs +++ b/test/prefer-dom-node-append.mjs @@ -83,10 +83,6 @@ test({ code: 'node.appendChild(child) + 0;', errors: [error], }, - { - code: 'node.appendChild(child) + 0;', - errors: [error], - }, { code: '+node.appendChild(child);', errors: [error], diff --git a/test/prefer-export-from.mjs b/test/prefer-export-from.mjs index 5ee7584630..cfeb8939d0 100644 --- a/test/prefer-export-from.mjs +++ b/test/prefer-export-from.mjs @@ -320,7 +320,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, valid: [ // #1579 diff --git a/test/prefer-keyboard-event-key.mjs b/test/prefer-keyboard-event-key.mjs index 440cac3947..f98c63c405 100644 --- a/test/prefer-keyboard-event-key.mjs +++ b/test/prefer-keyboard-event-key.mjs @@ -525,24 +525,6 @@ test({ `, errors: [error('charCode'), error('keyCode')], }, - { - code: outdent` - const e = {} - foo.addEventListener('click', (e, r, fg) => { - function a() { - if (true) { - { - { - const { charCode } = e; - console.log(e.keyCode, charCode); - } - } - } - } - }); - `, - errors: [error('charCode'), error('keyCode')], - }, { code: outdent` foo123.addEventListener('click', event => { diff --git a/test/prefer-module.mjs b/test/prefer-module.mjs index 81b7836fff..2e1497c0d7 100644 --- a/test/prefer-module.mjs +++ b/test/prefer-module.mjs @@ -57,10 +57,12 @@ test({ } `, errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', - ecmaFeatures: { - globalReturn: true, + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, }, }, }, diff --git a/test/prefer-regexp-test.mjs b/test/prefer-regexp-test.mjs index 459e38c781..fbda161dbd 100644 --- a/test/prefer-regexp-test.mjs +++ b/test/prefer-regexp-test.mjs @@ -207,7 +207,15 @@ test({ : { code, errors: [ - {suggestions: [{output}]}, + { + message: 'Prefer `.test(…)` over `.exec(…)`.', + suggestions: [ + { + desc: 'Switch to `RegExp#test(…)`.', + output, + }, + ], + }, ], }, ), diff --git a/test/prefer-set-has.mjs b/test/prefer-set-has.mjs index 54733f26dc..05f7b829fb 100644 --- a/test/prefer-set-has.mjs +++ b/test/prefer-set-has.mjs @@ -624,13 +624,15 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, @@ -659,7 +661,9 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: { + parser: parsers.typescript, + }, }, valid: [ // https://github.com/TheThingsNetwork/lorawan-stack/blob/1dab30227e632ceade425e0c67d5f84316e830da/pkg/webui/console/containers/device-importer/index.js#L74 diff --git a/test/prefer-spread.mjs b/test/prefer-spread.mjs index 83edafc383..42783a8867 100644 --- a/test/prefer-spread.mjs +++ b/test/prefer-spread.mjs @@ -274,7 +274,7 @@ test.snapshot({ 'do foo.concat(1); while (test)', { code: 'with (foo) foo.concat(1)', - parserOptions: {ecmaVersion: 6, sourceType: 'script'}, + languageOptions: {parserOptions: {ecmaVersion: 6, sourceType: 'script'}}, }, // Code from example in docs outdent` diff --git a/test/prefer-string-slice.mjs b/test/prefer-string-slice.mjs index 4fd035da3b..7776d86c55 100644 --- a/test/prefer-string-slice.mjs +++ b/test/prefer-string-slice.mjs @@ -82,25 +82,11 @@ test({ output: '"foo".slice()', errors: errorsSubstr, }, - { - code: '"foo".substr(1)', - output: '"foo".slice(1)', - errors: errorsSubstr, - }, - { - code: '"foo".substr(1, 2)', - output: '"foo".slice(1, 3)', - errors: errorsSubstr, - }, { code: '"foo".substr(bar.length, Math.min(baz, 100))', output: '"foo".slice(bar.length, bar.length + Math.min(baz, 100))', errors: errorsSubstr, }, - { - code: '"foo".substr(1, length)', - errors: errorsSubstr, - }, { code: '"foo".substr(1, "abc".length)', output: '"foo".slice(1, 1 + "abc".length)', @@ -268,11 +254,6 @@ test({ output: 'foo.slice(Math.max(0, start))', errors: errorsSubstring, }, - { - code: '"foo".substring(1)', - output: '"foo".slice(1)', - errors: errorsSubstring, - }, { code: 'foo.substring(start, end)', errors: errorsSubstring, diff --git a/test/prefer-string-starts-ends-with.mjs b/test/prefer-string-starts-ends-with.mjs index 4af56909e5..8afcb78f5e 100644 --- a/test/prefer-string-starts-ends-with.mjs +++ b/test/prefer-string-starts-ends-with.mjs @@ -95,11 +95,11 @@ test({ // String in variable. Don't autofix known, non-strings which don't have a startsWith/endsWith function. { code: 'const foo = {}; /^abc/.test(foo);', - errors: [{messageId: MESSAGE_STARTS_WITH}], + errors: [{messageId: MESSAGE_STARTS_WITH, suggestions: 3}], }, { code: 'const foo = 123; /^abc/.test(foo);', - errors: [{messageId: MESSAGE_STARTS_WITH}], + errors: [{messageId: MESSAGE_STARTS_WITH, suggestions: 3}], }, { code: 'const foo = "hello"; /^abc/.test(foo);', diff --git a/test/prefer-ternary.mjs b/test/prefer-ternary.mjs index cc71982549..f8247d77a7 100644 --- a/test/prefer-ternary.mjs +++ b/test/prefer-ternary.mjs @@ -1214,20 +1214,6 @@ test({ `, errors, }, - { - code: outdent` - function unicorn() { - if (test) return a; - else return b; - } - `, - output: outdent` - function unicorn() { - return test ? a : b; - } - `, - errors, - }, // Precedence { diff --git a/test/prefer-top-level-await.mjs b/test/prefer-top-level-await.mjs index f7a5ecc62d..4c8e973598 100644 --- a/test/prefer-top-level-await.mjs +++ b/test/prefer-top-level-await.mjs @@ -143,7 +143,7 @@ test.snapshot({ async function foo() {} foo(); `, - parserOptions: {sourceType: 'script'}, + languageOptions: {parserOptions: {sourceType: 'script'}}, }, { code: outdent` @@ -151,7 +151,7 @@ test.snapshot({ async function foo() {} async function foo() {} `, - parserOptions: {sourceType: 'script'}, + languageOptions: {parserOptions: {sourceType: 'script'}}, }, outdent` const program = {async run () {}}; diff --git a/test/prevent-abbreviations.mjs b/test/prevent-abbreviations.mjs index 6a81db822a..aced656e0b 100644 --- a/test/prevent-abbreviations.mjs +++ b/test/prevent-abbreviations.mjs @@ -3,15 +3,7 @@ import {getTester, avoidTestTitleConflict} from './utils/test.mjs'; const {test} = getTester(import.meta); -const createErrors = message => { - const error = {}; - - if (message) { - error.message = message; - } - - return [error]; -}; +const createErrors = message => [{message}]; const extendedOptions = [ { @@ -105,6 +97,15 @@ const noExtendDefaultAllowListOptions = [ ]; const tests = { + testerOptions: { + languageOptions: { + globals: { + document: 'readonly', + event: 'readonly', + Response: 'readonly', + }, + }, + }, valid: [ 'let x', '({x: 1})', @@ -172,7 +173,6 @@ const tests = { `, 'foo.err = 1', 'foo.bar.err = 1', - 'this.err = 1', outdent` class C { err() {} @@ -341,30 +341,30 @@ const tests = { { code: 'let successCb', output: 'let successCallback', - errors: createErrors(), + errors: 1, }, { code: 'let btnColor', output: 'let buttonColor', - errors: createErrors(), + errors: 1, }, { code: 'this.successCb = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'this.btnColor = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, // This tests that the rule does not hang up on combinatoric explosion of possible replacements { code: 'let ' + 'CbE'.repeat(1024), output: 'let ' + 'CallbackE'.repeat(1024), - errors: createErrors(), + errors: 1, }, { @@ -387,113 +387,86 @@ const tests = { { code: 'let args', output: 'let arguments_', - errors: createErrors(), + errors: 1, }, { code: 'let args', output: 'let arguments_', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let args', output: 'let arguments_', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let c', output: 'let custom', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'function cb() {}', output: 'function callback() {}', - errors: createErrors(), + errors: 1, }, { code: 'class cb {}', output: 'class circuitBreacker {}', options: extendedOptions, - errors: createErrors(), - }, - - { - code: 'let e', - errors: createErrors(), + errors: 1, }, { code: 'let e', options: customOptions, - errors: createErrors(), + errors: 1, }, - { - code: 'let err', - output: 'let error', - errors: createErrors(), - }, { code: 'let err', output: 'let error', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let err', output: 'let error', options: customOptions, - errors: createErrors(), - }, - - { - code: '({err: 1})', - options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let errorCallback', - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let errorCircuitBreacker', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let handleError', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let ErrCb', output: 'let HandleError', options: customOptions, - errors: createErrors(), - }, - { - code: 'let ErrCb', - output: 'let ErrorCallback', - errors: createErrors(), + errors: 1, }, { code: 'let ErrCb', output: 'let ErrorCircuitBreacker', options: extendedOptions, - errors: createErrors(), - }, - { - code: 'let ErrCb', - output: 'let HandleError', - options: customOptions, - errors: createErrors(), + errors: 1, }, // `errCb` should not match this @@ -501,19 +474,13 @@ const tests = { code: 'let fooErrCb', output: 'let fooErrorCb', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCbFoo', output: 'let errorCbFoo', options: customOptions, - errors: createErrors(), - }, - - { - code: 'class Err {}', - output: 'class Error_ {}', - errors: createErrors(), + errors: 1, }, { @@ -521,7 +488,7 @@ const tests = { let e; console.log(e); `, - errors: createErrors(), + errors: 1, }, { @@ -533,7 +500,7 @@ const tests = { let error; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -549,7 +516,7 @@ const tests = { let error_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -566,7 +533,7 @@ const tests = { let error__; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -583,7 +550,7 @@ const tests = { console.log(error); } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -600,13 +567,13 @@ const tests = { console.log(error, error_); } `, - errors: createErrors(), + errors: 1, }, { code: 'err => err', output: 'error => error', - errors: createErrors(), + errors: 1, }, { @@ -618,7 +585,7 @@ const tests = { const options = {}; console.log(options); `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -631,7 +598,7 @@ const tests = { var options = 2; console.log(options); `, - errors: createErrors(), + errors: 1, }, { @@ -643,7 +610,7 @@ const tests = { const error = {}; const foo = {err: error}; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -662,32 +629,32 @@ const tests = { b: 2 }; `, - errors: createErrors(), + errors: 1, }, { code: '({err}) => err', output: '({err: error}) => error', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'err => ({err})', output: 'error => ({err: error})', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'const {err} = foo;', output: 'const {err: error} = foo;', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'const foo = {err: 1}', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -696,22 +663,17 @@ const tests = { }; `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'foo.err = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'foo.bar.err = 1', options: checkPropertiesOptions, - errors: createErrors(), - }, - { - code: 'this.err = 1', - options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -721,7 +683,7 @@ const tests = { } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -743,12 +705,12 @@ const tests = { { code: 'let err_', output: 'let error_', - errors: createErrors(), + errors: 1, }, { code: 'let __err__', output: 'let __error__', - errors: createErrors(), + errors: 1, }, { code: 'let _e', @@ -772,12 +734,12 @@ const tests = { { code: 'const Err = 1;', output: 'const Error_ = 1;', - errors: createErrors(), + errors: 1, }, { code: 'const _Err_ = 1;', output: 'const _Error_ = 1;', - errors: createErrors(), + errors: 1, }, { code: '({Err: 1})', @@ -793,7 +755,7 @@ const tests = { { code: 'let doc', output: 'let document_', - errors: createErrors(), + errors: 1, }, // This test need run eslint 3 times to get the correct result @@ -880,7 +842,7 @@ const tests = { { code: 'let pkg', output: 'let package_', - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -931,7 +893,7 @@ const tests = { code: 'let y', output: 'let yield_', options: customOptions, - errors: createErrors(), + errors: 1, }, { @@ -963,7 +925,7 @@ const tests = { console.log(errorCallback, errorCallback_); } `, - errors: createErrors().concat(createErrors()) + errors: 1.concat(1) }, */ { @@ -993,7 +955,7 @@ const tests = { let error; ({a: error = 1} = 2); `, - errors: createErrors(), + errors: 1, }, // Renaming to `arguments` would result in a `SyntaxError`, so it should rename to `arguments_` @@ -1006,7 +968,7 @@ const tests = { 'use strict'; let arguments_; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1023,7 +985,7 @@ const tests = { } } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1040,7 +1002,7 @@ const tests = { } } `, - errors: createErrors(), + errors: 1, }, { @@ -1054,7 +1016,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1069,7 +1031,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, // Renaming to `arguments` whould result in `f` returning it's arguments instead of the outer variable @@ -1086,7 +1048,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1101,7 +1063,7 @@ const tests = { return arguments + arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1116,7 +1078,7 @@ const tests = { return g.apply(this, arguments) + arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1131,7 +1093,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { @@ -1181,7 +1143,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1200,7 +1162,7 @@ const tests = { }; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1217,7 +1179,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1234,7 +1196,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1251,7 +1213,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, // `extendDefaultAllowList` option @@ -1259,40 +1221,40 @@ const tests = { code: 'const propTypes = 2;const err = 2;', output: 'const propertyTypes = 2;const err = 2;', options: noExtendDefaultAllowListOptions, - errors: createErrors(), + errors: 1, }, // #1937 { code: 'const expectedRetVal = "that should be ok";', output: 'const expectedReturnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const retVal = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const retValue = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const returnVal = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const sendDmMessage = () => {};', output: 'const sendDirectMessage = () => {};', options: [{replacements: {dm: {directMessage: true}}}], - errors: createErrors(), + errors: 1, }, { code: 'const ret_val = "that should be ok";', output: 'const returnValue_value = "that should be ok";', - errors: createErrors(), + errors: 1, }, ], }; @@ -1303,12 +1265,13 @@ test.typescript(avoidTestTitleConflict(tests, 'typescript')); test({ testerOptions: { - parserOptions: { + languageOptions: { sourceType: 'script', ecmaVersion: 5, - }, - env: { - browser: true, + globals: { + document: 'readonly', + event: 'readonly', + }, }, }, valid: [], @@ -1316,7 +1279,7 @@ test({ { code: 'var doc', output: 'var document_', - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1327,14 +1290,14 @@ test({ var document_; document.querySelector(document_); `, - errors: createErrors(), + errors: 1, }, { code: 'var y', output: 'var yield_', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1346,23 +1309,23 @@ test({ var yield_; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'function a() {try {} catch(args) {}}', output: 'function a() {try {} catch(arguments_) {}}', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'var one', options: [{replacements: {one: {1: true}}}], - errors: createErrors(), + errors: 1, }, { code: 'var one_two', options: [{replacements: {one: {first: true, 1: true}}}], - errors: createErrors(), + errors: 1, }, ], }); @@ -1478,7 +1441,7 @@ const importExportTests = { import error from 'err'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1488,13 +1451,13 @@ const importExportTests = { import {err as error} from 'err'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'import {err as err} from "err";', output: 'import {err as error} from "err";', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1512,7 +1475,7 @@ const importExportTests = { } from 'foo'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { @@ -1522,7 +1485,7 @@ const importExportTests = { output: outdent` import {err as callback} from 'err'; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1531,54 +1494,54 @@ const importExportTests = { output: outdent` const {err: callback} = foo; `, - errors: createErrors(), + errors: 1, }, // Internal import { code: 'const err = require("../err")', output: 'const error = require("../err")', - errors: createErrors(), + errors: 1, }, { code: 'const err = require("/err")', output: 'const error = require("/err")', - errors: createErrors(), + errors: 1, }, { code: 'import err from "./err"', output: 'import error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import err, {foo as bar} from "./err"', output: 'import error, {foo as bar} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {default as err, foo as bar} from "./err"', output: 'import {default as error, foo as bar} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import * as err from "./err"', output: 'import * as error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import foo, * as err from "./err"', output: 'import foo, * as error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {err} from "./err"', output: 'import {err as error} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {default as foo, err} from "./err"', output: 'import {default as foo, err as error} from "./err"', - errors: createErrors(), + errors: 1, }, { @@ -1590,7 +1553,7 @@ const importExportTests = { let error; export {error as err}; `, - errors: createErrors(), + errors: 1, }, { @@ -1602,28 +1565,28 @@ const importExportTests = { let error; export {error as err}; `, - errors: createErrors(), + errors: 1, }, { code: 'export const err = {}', - errors: createErrors(), + errors: 1, }, { code: 'export let err', - errors: createErrors(), + errors: 1, }, { code: 'export var err', - errors: createErrors(), + errors: 1, }, { code: 'export function err() {}', - errors: createErrors(), + errors: 1, }, { code: 'export class err {}', - errors: createErrors(), + errors: 1, }, { @@ -1635,7 +1598,7 @@ const importExportTests = { const error_ = {}; export const error = error_; `, - errors: createErrors(), + errors: 1, }, { @@ -1647,7 +1610,7 @@ const importExportTests = { class error {}; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -1667,7 +1630,7 @@ const importExportTests = { }; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -1676,7 +1639,7 @@ const importExportTests = { export {foo as err}; `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, ], @@ -1724,24 +1687,24 @@ test.babel({ return property; } `, - errors: createErrors(), + errors: 1, }, { code: '({err}) => err;', output: '({err: error}) => error;', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'err => ({err});', output: 'error => ({err: error});', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'Foo.customProps = {}', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1750,7 +1713,7 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1759,7 +1722,7 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -1779,17 +1742,25 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, ], }); test.typescript({ + testerOptions: { + languageOptions: { + globals: { + document: 'readonly', + event: 'readonly', + Response: 'readonly', + }, + }, + }, valid: [], invalid: [ // Types ...[ - 'declare const prop: string;', 'declare const prop: string;', 'declare var prop: number;', 'declare let prop: any;', @@ -1798,14 +1769,14 @@ test.typescript({ ].map(code => ({ code, output: code.replace('prop', 'property'), - errors: createErrors(), + errors: 1, })), // #763 { code: 'const foo = (extraParams?: string) => {}', output: 'const foo = (extraParameters?: string) => {}', - errors: createErrors(), + errors: 1, }, { code: 'const foo = (extr\u0061Params ? : string) => {}', @@ -1839,7 +1810,7 @@ test.typescript({ // #1102 { code: 'export type Props = string', - errors: createErrors(), + errors: 1, }, // #347 @@ -1864,7 +1835,7 @@ test.typescript({ }, }, ], - errors: createErrors(), + errors: 1, }, // https://github.com/facebook/relay/blob/597d2a17aa29d401830407b6814a5f8d148f632d/packages/relay-experimental/EntryPointTypes.flow.js#L138 @@ -1875,7 +1846,7 @@ test.typescript({ output: outdent` export type PreloadProps = {} `, - errors: [...createErrors(), ...createErrors()], + errors: 2, }, // https://github.com/facebook/relay/blob/597d2a17aa29d401830407b6814a5f8d148f632d/packages/relay-experimental/EntryPointTypes.flow.js#L138 @@ -1886,7 +1857,7 @@ test.typescript({ output: outdent` export type PreloadProps = {}; `, - errors: [...createErrors(), ...createErrors()], + errors: 2, }, ], }); @@ -1894,9 +1865,11 @@ test.typescript({ // JSX test.typescript({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -1958,12 +1931,12 @@ test({ { code: 'foo();', filename: 'err/http-err.js', - errors: createErrors(), + errors: 1, }, { code: 'foo();', filename: 'http-err.js', - errors: createErrors(), + errors: 1, }, { code: 'foo();', diff --git a/test/string-content.mjs b/test/string-content.mjs index c52f33fa0e..2d48f449db 100644 --- a/test/string-content.mjs +++ b/test/string-content.mjs @@ -37,8 +37,8 @@ const createSuggestionError = (match, suggest, output) => [ data: { match, suggest, - output, }, + output, }, ], }, @@ -46,9 +46,11 @@ const createSuggestionError = (match, suggest, output) => [ test({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, diff --git a/test/text-encoding-identifier-case.mjs b/test/text-encoding-identifier-case.mjs index a3cd946bce..abac9b390b 100644 --- a/test/text-encoding-identifier-case.mjs +++ b/test/text-encoding-identifier-case.mjs @@ -39,9 +39,11 @@ test.snapshot({ // JSX test.snapshot({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, diff --git a/test/utils/default-options.mjs b/test/utils/default-options.mjs index abff0e52e0..acfd2144b7 100644 --- a/test/utils/default-options.mjs +++ b/test/utils/default-options.mjs @@ -1,14 +1,7 @@ -import eslintPluginUnicorn from '../../index.js'; - -const {env, parserOptions} = eslintPluginUnicorn.configs.recommended; - const defaultOptions = { - env: { - node: true, - browser: true, - ...env, + languageOptions: { + sourceType: 'module', }, - parserOptions, }; export default defaultOptions; diff --git a/test/utils/language-options.mjs b/test/utils/language-options.mjs new file mode 100644 index 0000000000..dca65a3a5e --- /dev/null +++ b/test/utils/language-options.mjs @@ -0,0 +1,61 @@ +import {Legacy} from '@eslint/eslintrc'; +import * as espree from 'espree'; + +const DEFAULT_LANGUAGE_OPTIONS = { + // When `parser` in `undefined`, `languageOptions` seems has no effect + parser: espree, + globals: Object.fromEntries( + ['es2024', 'node', 'browser'] + .flatMap(environment => Object.entries(Legacy.environments.get(environment).globals)), + ), +}; + +function cleanLanguageOptions(languageOptions) { + if (!languageOptions.parser) { + delete languageOptions.parser; + } + + if (!languageOptions.parserOptions) { + delete languageOptions.parserOptions; + } + + return languageOptions; +} + +function normalizeLanguageOptions(languageOptions) { + languageOptions ??= {}; + + const {parser, parserOptions} = languageOptions; + + const { + implementation: parserImplementation, + mergeParserOptions, + } = parser ?? {}; + + return cleanLanguageOptions({ + ...languageOptions, + parser: parserImplementation ?? parser, + parserOptions: mergeParserOptions?.(parserOptions) ?? parserOptions, + }); +} + +function mergeLanguageOptions(languageOptionsA, languageOptionsB) { + languageOptionsA ??= {}; + languageOptionsB ??= {}; + + return normalizeLanguageOptions({ + ...languageOptionsA, + ...languageOptionsB, + parser: languageOptionsB.parser ?? languageOptionsA.parser, + globals: { + ...languageOptionsA.globals, + ...languageOptionsB.globals, + }, + parserOptions: { + ...languageOptionsA.parserOptions, + ...languageOptionsB.parserOptions, + }, + }); +} + +export {DEFAULT_LANGUAGE_OPTIONS, normalizeLanguageOptions, mergeLanguageOptions}; diff --git a/test/utils/parsers.mjs b/test/utils/parsers.mjs index 6f1e4cb959..613516f331 100644 --- a/test/utils/parsers.mjs +++ b/test/utils/parsers.mjs @@ -1,13 +1,10 @@ -import {createRequire} from 'node:module'; -import defaultOptions from './default-options.mjs'; +import babelEslintParser from '@babel/eslint-parser'; +import typescriptEslintParser from '@typescript-eslint/parser'; +import vueEslintParser from 'vue-eslint-parser'; -const require = createRequire(import.meta.url); - -const babel = { - name: '@babel/eslint-parser', - get parser() { - return require.resolve(this.name); - }, +const babelParser = { + name: 'babel', + implementation: babelEslintParser, mergeParserOptions(options) { options = options || {}; options.babelOptions = options.babelOptions || {}; @@ -22,9 +19,7 @@ const babel = { ]; return { - ...defaultOptions.parserOptions, requireConfigFile: false, - sourceType: 'module', allowImportExportEverywhere: true, ...options, babelOptions: { @@ -40,37 +35,28 @@ const babel = { }, }; -const typescript = { - name: '@typescript-eslint/parser', - get parser() { - return require.resolve(this.name); - }, +const typescriptParser = { + name: 'typescript', + implementation: typescriptEslintParser, mergeParserOptions(options) { return { - ...defaultOptions.parserOptions, project: [], ...options, }; }, }; -const vue = { - name: 'vue-eslint-parser', - get parser() { - return require.resolve(this.name); - }, - mergeParserOptions(options) { - return { - ...defaultOptions.parserOptions, - ...options, - }; - }, +const vueParser = { + name: 'vue', + implementation: vueEslintParser, }; -const parsers = { - babel, - typescript, - vue, -}; +const parsers = Object.fromEntries( + [ + babelParser, + typescriptParser, + vueParser, + ].map(parser => [parser.name, parser]), +); export default parsers; diff --git a/test/utils/snapshot-rule-tester.mjs b/test/utils/snapshot-rule-tester.mjs index 46cc903095..2847be3f14 100644 --- a/test/utils/snapshot-rule-tester.mjs +++ b/test/utils/snapshot-rule-tester.mjs @@ -1,9 +1,9 @@ -import {createRequire} from 'node:module'; +import path from 'node:path'; import {Linter} from 'eslint'; import {codeFrameColumns} from '@babel/code-frame'; import outdent from 'outdent'; +import {mergeLanguageOptions} from './language-options.mjs'; -const require = createRequire(import.meta.url); const codeFrameColumnsOptions = {linesAbove: Number.POSITIVE_INFINITY, linesBelow: Number.POSITIVE_INFINITY}; // A simple version of `SourceCodeFixer.applyFixes` // https://github.com/eslint/eslint/issues/14936#issuecomment-906746754 @@ -60,7 +60,7 @@ function normalizeTests(tests) { const additionalProperties = getAdditionalProperties( testCase, - ['code', 'options', 'filename', 'parserOptions', 'parser', 'globals', 'only'], + ['code', 'options', 'filename', 'languageOptions', 'only'], ); if (additionalProperties.length > 0) { @@ -72,56 +72,47 @@ function normalizeTests(tests) { return tests; } -function getVerifyConfig(ruleId, testerConfig, testCase) { +function getVerifyConfig(ruleId, rule, testerConfig, testCase) { const { - options, - parserOptions, - parser = testerConfig.parser, - env, - globals, + languageOptions = {}, + options = [], } = testCase; - return { - ...testerConfig, - parser, - parserOptions: { - ...testerConfig.parserOptions, - ...parserOptions, - }, - env: { - ...testerConfig.env, - ...env, - }, - globals: { - ...testerConfig.globals, - ...globals, - }, - rules: { - [ruleId]: ['error', ...(Array.isArray(options) ? options : [])], + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/rule-tester/rule-tester.js#L501 + const pluginName = 'rule-to-test'; + + return [ + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/rule-tester/rule-tester.js#L524 + {files: ['**']}, + { + ...testerConfig, + languageOptions: mergeLanguageOptions(testerConfig.languageOptions, languageOptions), + rules: { + [`${pluginName}/${ruleId}`]: ['error', ...options], + }, + plugins: { + [pluginName]: { + rules: { + [ruleId]: rule, + }, + }, + }, + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/config/default-config.js#L46-L48 + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, }, - }; + ]; } -const parsers = new WeakMap(); -function defineParser(linter, parser) { - if (!parser) { - return; - } - - if (!parsers.has(linter)) { - parsers.set(linter, new Set()); - } - - const defined = parsers.get(linter); - if (defined.has(parser)) { - return; +function verify(code, verifyConfig, {filename}) { + // https://github.com/eslint/eslint/pull/17989 + const linterOptions = {}; + if (filename) { + linterOptions.cwd = path.parse(filename).root; } - defined.add(parser); - linter.defineParser(parser, require(parser)); -} - -function verify(linter, code, verifyConfig, {filename}) { + const linter = new Linter(linterOptions); const messages = linter.verify(code, verifyConfig, {filename}); // Missed `message`, #1923 @@ -136,32 +127,32 @@ function verify(linter, code, verifyConfig, {filename}) { throw new SyntaxError('\n' + codeFrameColumns(code, {start: {line, column}}, {message})); } - return messages; + return { + linter, + messages, + }; } class SnapshotRuleTester { - constructor(test, config) { + constructor(test, testerConfig) { this.test = test; - this.config = config; + this.testerConfig = testerConfig; } run(ruleId, rule, tests) { - const {test, config} = this; + const {test, testerConfig} = this; const fixable = rule.meta && rule.meta.fixable; - const linter = new Linter(); - linter.defineRule(ruleId, rule); const {valid, invalid} = normalizeTests(tests); for (const [index, testCase] of valid.entries()) { const {code, filename, only} = testCase; - const verifyConfig = getVerifyConfig(ruleId, config, testCase); - defineParser(linter, verifyConfig.parser); + const verifyConfig = getVerifyConfig(ruleId, rule, testerConfig, testCase); (only ? test.only : test)( `valid(${index + 1}): ${code}`, t => { - const messages = verify(linter, code, verifyConfig, {filename}); + const {messages} = verify(code, verifyConfig, {filename}); t.deepEqual(messages, [], 'Valid case should not have errors.'); }, ); @@ -169,16 +160,15 @@ class SnapshotRuleTester { for (const [index, testCase] of invalid.entries()) { const {code, options, filename, only} = testCase; - const verifyConfig = getVerifyConfig(ruleId, config, testCase); - defineParser(linter, verifyConfig.parser); - const runVerify = code => verify(linter, code, verifyConfig, {filename}); + const verifyConfig = getVerifyConfig(ruleId, rule, testerConfig, testCase); + const runVerify = code => verify(code, verifyConfig, {filename}); (only ? test.only : test)( `invalid(${index + 1}): ${code}`, t => { - const messages = runVerify(code); - t.notDeepEqual(messages, [], 'Invalid case should have at least one error.'); + const {linter, messages} = runVerify(code); + t.notDeepEqual(messages, [], 'Invalid case should have at least one error.'); const {fixed, output} = fixable ? linter.verifyAndFix(code, verifyConfig, {filename}) : {fixed: false}; t.snapshot(`\n${printCode(code)}\n`, 'Input'); diff --git a/test/utils/test.mjs b/test/utils/test.mjs index cf929647db..25856e9091 100644 --- a/test/utils/test.mjs +++ b/test/utils/test.mjs @@ -4,11 +4,19 @@ import test from 'ava'; import AvaRuleTester from 'eslint-ava-rule-tester'; import {loadRule} from '../../rules/utils/rule.js'; import SnapshotRuleTester from './snapshot-rule-tester.mjs'; -import defaultOptions from './default-options.mjs'; import parsers from './parsers.mjs'; +import {DEFAULT_LANGUAGE_OPTIONS, normalizeLanguageOptions, mergeLanguageOptions} from './language-options.mjs'; -function normalizeTestCase(testCase) { - return typeof testCase === 'string' ? {code: testCase} : {...testCase}; +function normalizeTestCase(testCase, shouldNormalizeLanguageOptions = true) { + if (typeof testCase === 'string') { + testCase = {code: testCase}; + } + + if (shouldNormalizeLanguageOptions && testCase.languageOptions) { + testCase = {...testCase, languageOptions: normalizeLanguageOptions(testCase.languageOptions)}; + } + + return testCase; } function normalizeInvalidTest(test, rule) { @@ -33,25 +41,6 @@ function normalizeInvalidTest(test, rule) { }; } -function normalizeParser(options) { - let { - parser, - parserOptions, - } = options; - - if (parser) { - if (parser.mergeParserOptions) { - parserOptions = parser.mergeParserOptions(parserOptions); - } - - if (parser.name) { - parser = parser.name; - } - } - - return {...options, parser, parserOptions}; -} - // https://github.com/tc39/proposal-array-is-template-object const isTemplateObject = value => Array.isArray(value?.raw); // https://github.com/tc39/proposal-string-cooked @@ -72,7 +61,7 @@ function only(...arguments_) { only('code'); only({code: 'code'}); */ - return {...normalizeTestCase(arguments_[0]), only: true}; + return {...normalizeTestCase(arguments_[0], /* shouldNormalizeLanguageOptions */ false), only: true}; } class Tester { @@ -82,64 +71,41 @@ class Tester { } runTest(tests) { - const {beforeAll, testerOptions = {}, valid, invalid} = tests; - const tester = new AvaRuleTester(test, { + const {ruleId, rule} = this; + + let {testerOptions = {}, valid, invalid} = tests; + + valid = valid.map(testCase => normalizeTestCase(testCase)); + invalid = invalid.map(testCase => normalizeInvalidTest(normalizeTestCase(testCase), rule)); + + const testConfig = { ...testerOptions, - parserOptions: { - ...defaultOptions.parserOptions, - ...testerOptions.parserOptions, - }, - env: { - ...defaultOptions.env, - ...testerOptions.env, - }, - globals: { - ...defaultOptions.globals, - ...testerOptions.globals, - }, - }); + languageOptions: mergeLanguageOptions(DEFAULT_LANGUAGE_OPTIONS, testerOptions.languageOptions), + }; - if (beforeAll) { - beforeAll(tester); - } + const tester = new AvaRuleTester(test, testConfig); return tester.run( - this.ruleId, - this.rule, - { - valid, - invalid: invalid.map(test => normalizeInvalidTest(test, this.rule)), - }, + ruleId, + rule, + {valid, invalid}, ); } snapshot(tests) { - let { - testerOptions = {}, - valid, - invalid, - } = tests; + let {testerOptions = {}, valid, invalid} = tests; - testerOptions = normalizeParser(testerOptions); - valid = valid.map(testCase => normalizeParser(normalizeTestCase(testCase))); - invalid = invalid.map(testCase => normalizeParser(normalizeTestCase(testCase))); + valid = valid.map(testCase => normalizeTestCase(testCase)); + invalid = invalid.map(testCase => normalizeTestCase(testCase)); - const tester = new SnapshotRuleTester(test, { + const testConfig = { ...testerOptions, - parserOptions: { - ...defaultOptions.parserOptions, - ...testerOptions.parserOptions, - }, - env: { - ...defaultOptions.env, - ...testerOptions.env, - }, - globals: { - ...defaultOptions.globals, - ...testerOptions.globals, - }, - }); - return tester.run(this.ruleId, this.rule, {valid, invalid}); + languageOptions: mergeLanguageOptions(DEFAULT_LANGUAGE_OPTIONS, testerOptions.languageOptions), + }; + + const tester = new SnapshotRuleTester(test, testConfig); + const {ruleId, rule} = this; + return tester.run(ruleId, rule, {valid, invalid}); } } @@ -147,22 +113,22 @@ function getTester(importMeta) { const filename = url.fileURLToPath(importMeta.url); const ruleId = path.basename(filename, '.mjs'); const tester = new Tester(ruleId); + const runTest = Tester.prototype.runTest.bind(tester); runTest.snapshot = Tester.prototype.snapshot.bind(tester); runTest.only = only; - for (const [parserName, parserSettings] of Object.entries(parsers)) { - Reflect.defineProperty(runTest, parserName, { + for (const parser of Object.values(parsers)) { + Reflect.defineProperty(runTest, parser.name, { value(tests) { - const testerOptions = tests.testerOptions || {}; - const {parser, mergeParserOptions} = parserSettings; - return runTest({ ...tests, testerOptions: { - ...testerOptions, - parser, - parserOptions: mergeParserOptions(testerOptions.parserOptions), + ...tests.testerOptions, + languageOptions: { + ...tests.testerOptions?.languageOptions, + parser, + }, }, }); }, @@ -177,7 +143,7 @@ function getTester(importMeta) { } const addComment = (testCase, comment) => { - testCase = normalizeTestCase(testCase); + testCase = normalizeTestCase(testCase, /* shouldNormalizeLanguageOptions */ false); const {code, output} = testCase; const fixedTest = { ...testCase,