From 160a5040f951f582eeae44f2102b1104eeba9183 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 25 Sep 2024 20:36:21 +0900 Subject: [PATCH 1/7] add test cases for 3rd-party plugin --- src/__snapshots__/core.test.ts.snap | 61 +++++++++++++++++++++-------- src/core.test.ts | 37 +++++++++++++++++ 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/__snapshots__/core.test.ts.snap b/src/__snapshots__/core.test.ts.snap index bf9da771..4444aa26 100644 --- a/src/__snapshots__/core.test.ts.snap +++ b/src/__snapshots__/core.test.ts.snap @@ -69,6 +69,20 @@ exports[`Core > disablePerLine 1`] = ` let a = 1;" `; +exports[`Core > fixes problems with legacy config and 3rd-party plugins 1`] = ` +"// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +// eslint-disable-next-line test/ban-nullish-coalescing-operator +2 ?? 2;" +`; + exports[`Core > flat config 1`] = ` [ { @@ -194,6 +208,17 @@ exports[`Core > lint > returns lint results 1`] = ` ], "warningCount": 0, }, + { + "errorCount": 1, + "filePath": "/src/ban-nullish-coalescing-operator.js", + "messages": [ + { + "ruleId": "test/ban-nullish-coalescing-operator", + "severity": 2, + }, + ], + "warningCount": 0, + }, { "errorCount": 1, "filePath": "/src/import-order.js", @@ -270,22 +295,24 @@ exports[`Core > printDetailsOfResults 1`] = ` `; exports[`Core > printSummaryOfResults 1`] = ` -"- 7 files (0 file passed, 7 files failed) checked. -- 18 problems (17 errors, 1 warning) found. -╔═════════════════════════════╤═══════╤═════════╤════════════╤═════════════════╗ -║ Rule │ Error │ Warning │ is fixable │ has suggestions ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ arrow-body-style │ 12 │ 0 │ 12 │ 0 ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ ban-exponentiation-operator │ 1 │ 0 │ 0 │ 0 ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ import/order │ 1 │ 0 │ 0 │ 0 ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ no-unsafe-negation │ 1 │ 0 │ 0 │ 1 ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ no-unused-vars │ 1 │ 0 │ 0 │ 0 ║ -╟─────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ -║ ]8;;https://example.comprefer-const]8;; │ 1 │ 1 │ 2 │ 0 ║ -╚═════════════════════════════╧═══════╧═════════╧════════════╧═════════════════╝ +"- 8 files (0 file passed, 8 files failed) checked. +- 19 problems (18 errors, 1 warning) found. +╔══════════════════════════════════════╤═══════╤═════════╤════════════╤═════════════════╗ +║ Rule │ Error │ Warning │ is fixable │ has suggestions ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ arrow-body-style │ 12 │ 0 │ 12 │ 0 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ ban-exponentiation-operator │ 1 │ 0 │ 0 │ 0 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ test/ban-nullish-coalescing-operator │ 1 │ 0 │ 0 │ 0 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ import/order │ 1 │ 0 │ 0 │ 0 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ no-unsafe-negation │ 1 │ 0 │ 0 │ 1 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ no-unused-vars │ 1 │ 0 │ 0 │ 0 ║ +╟──────────────────────────────────────┼───────┼─────────┼────────────┼─────────────────╢ +║ ]8;;https://example.comprefer-const]8;; │ 1 │ 1 │ 2 │ 0 ║ +╚══════════════════════════════════════╧═══════╧═════════╧════════════╧═════════════════╝ " `; diff --git a/src/core.test.ts b/src/core.test.ts index 314965af..7e03cedf 100644 --- a/src/core.test.ts +++ b/src/core.test.ts @@ -82,6 +82,9 @@ const iff = await createIFF({ 'src/ban-exponentiation-operator.js': dedent` 2 ** 2; `, + 'src/ban-nullish-coalescing-operator.js': dedent` + 2 ?? 2; + `, 'src/no-unused-vars.js': dedent` const a = 1; `, @@ -91,6 +94,33 @@ const iff = await createIFF({ 'src/no-unsafe-negation.js': dedent` if (!key in object) {} `, + 'node_modules/eslint-plugin-test/package.json': dedent` + { + "name": "eslint-plugin-test", + "version": "1.0.0", + "main": "index.js" + } + `, + 'node_modules/eslint-plugin-test/index.js': dedent` + module.exports = { + rules: { + 'ban-nullish-coalescing-operator': { + create(context) { + return { + LogicalExpression: (node) => { + if (node.operator === '??') { + context.report({ + node, + message: 'Ban nullish coalescing operator', + }); + } + }, + }; + }, + }, + }, + }; + `, '.eslintrc.js': dedent` module.exports = { root: true, @@ -98,11 +128,13 @@ const iff = await createIFF({ ecmaVersion: 2021, sourceType: 'module', }, + plugins: ['test'], overrides: [ { files: ['prefer-const.js'], rules: { 'prefer-const': 'error' } }, { files: ['arrow-body-style.js'], rules: { 'arrow-body-style': ['error', 'always'] } }, { files: ['import-order.js'], rules: { 'import/order': 'error' } }, { files: ['ban-exponentiation-operator.js'], rules: { 'ban-exponentiation-operator': 'error' } }, + { files: ['ban-nullish-coalescing-operator.js'], rules: { 'test/ban-nullish-coalescing-operator': 'error' } }, { files: ['no-unused-vars.js'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, @@ -312,4 +344,9 @@ describe('Core', () => { expect(await readFile(iff.paths['src/index.jsx'], 'utf-8')).toMatchSnapshot(); expect(await readFile(iff.paths['src/.index.js'], 'utf-8')).toMatchSnapshot(); }); + test('fixes problems with legacy config and 3rd-party plugins', async () => { + const results = await core.lint(); + await core.disablePerLine(results, ['test/ban-nullish-coalescing-operator']); + expect(await readFile(iff.paths['src/ban-nullish-coalescing-operator.js'], 'utf-8')).toMatchSnapshot(); + }); }); From 33de5de58245f03f45ffcc1cb59d5c3c5450be23 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 25 Sep 2024 20:37:34 +0900 Subject: [PATCH 2/7] logging for debug --- src/eslint/linter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eslint/linter.ts b/src/eslint/linter.ts index 8677ead0..06b4d1fd 100644 --- a/src/eslint/linter.ts +++ b/src/eslint/linter.ts @@ -49,9 +49,9 @@ export function verifyAndFix( do { passNumber++; - const messages = linter - .verify(currentText, config, filePath) - .filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); + const unfilteredMessages = linter.verify(currentText, config, filePath); + if (passNumber === 1) console.log('unfilteredMessages', unfilteredMessages); + const messages = unfilteredMessages.filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); const sourceCode = linter.getSourceCode(); // Create `Rule.Fix[]` From 0434b64787de1406fe333fd3cd7d5df3d97ac9ee Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 25 Sep 2024 21:42:30 +0900 Subject: [PATCH 3/7] Revert "logging for debug" This reverts commit 33de5de58245f03f45ffcc1cb59d5c3c5450be23. --- src/eslint/linter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eslint/linter.ts b/src/eslint/linter.ts index 06b4d1fd..8677ead0 100644 --- a/src/eslint/linter.ts +++ b/src/eslint/linter.ts @@ -49,9 +49,9 @@ export function verifyAndFix( do { passNumber++; - const unfilteredMessages = linter.verify(currentText, config, filePath); - if (passNumber === 1) console.log('unfilteredMessages', unfilteredMessages); - const messages = unfilteredMessages.filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); + const messages = linter + .verify(currentText, config, filePath) + .filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); const sourceCode = linter.getSourceCode(); // Create `Rule.Fix[]` From 0df8753f75f7b24ac299aec01b8e53fde9878cd0 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 25 Sep 2024 21:48:18 +0900 Subject: [PATCH 4/7] use `ESLint` instead of `Linter` to fix the code --- src/config.ts | 2 ++ src/core.ts | 30 +++++++++++------------------- src/eslint/linter.ts | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/config.ts b/src/config.ts index 7dea6406..8abf8878 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,6 +13,7 @@ type LegacyESLintOptions = { type: 'eslintrc' } & Pick< | 'overrideConfig' | 'cwd' | 'resolvePluginsRelativeTo' + | 'plugins' >; type FlatESLintOptions = { type: 'flat' } & Pick< FlatESLint.Options, @@ -110,6 +111,7 @@ export function normalizeConfig(config: Config): NormalizedConfig { cwd, resolvePluginsRelativeTo: config.eslintOptions.resolvePluginsRelativeTo ?? configDefaults.eslintOptions.resolvePluginsRelativeTo, + plugins: config.eslintOptions.plugins, }; } else { eslintOptions = { diff --git a/src/core.ts b/src/core.ts index 85010558..2c5e7da9 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,6 +1,6 @@ import { writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; -import { ESLint, Linter, Rule } from 'eslint'; +import { ESLint, Rule } from 'eslint'; import eslintPkg, { LegacyESLint as LegacyESLintNS } from 'eslint/use-at-your-own-risk'; import isInstalledGlobally from 'is-installed-globally'; import { DescriptionPosition } from './cli/prompt.js'; @@ -49,7 +49,14 @@ export class Core { const eslintOptions = this.config.eslintOptions; if (eslintOptions.type === 'eslintrc') { const { type, ...rest } = eslintOptions; - this.eslint = new LegacyESLint(rest); + this.eslint = new LegacyESLint({ + ...rest, + plugins: {}, + overrideConfig: { + ...rest.overrideConfig, + plugins: [...(rest.overrideConfig?.plugins ?? []), 'eslint-plugin-eslint-interactive'], + }, + }); } else { const { type, ...rest } = eslintOptions; this.eslint = new FlatESLint(rest); @@ -191,20 +198,13 @@ export class Core { ): Promise { // NOTE: Extract only necessary results and files for performance const filteredResultsOfLint = filterResultsByRuleId(resultsOfLint, ruleIds); - const linter = new Linter({ configType: this.config.eslintOptions.type }); // eslint-disable-next-line prefer-const for (let { filePath, source } of filteredResultsOfLint) { if (!source) throw new Error('Source code is required to apply fixes.'); - const config: Linter.Config | Linter.FlatConfig[] = - this.config.eslintOptions.type === 'eslintrc' - ? // eslint-disable-next-line no-await-in-loop - await this.eslint.calculateConfigForFile(filePath) - : // NOTE: For some reason, if files is not specified, it will not match .jsx - // eslint-disable-next-line no-await-in-loop - [{ ...(await calculateConfigForFile(this.eslint, filePath)), files: ['**/*.*', '**/*'] }]; - const fixedResult = verifyAndFix(linter, source, config, filePath, ruleIds, fixCreator); + // eslint-disable-next-line no-await-in-loop + const fixedResult = await verifyAndFix(this.eslint, source, filePath, ruleIds, fixCreator); // Write the fixed source code to the file if (fixedResult.fixed) { @@ -219,11 +219,3 @@ export class Core { }; } } - -async function calculateConfigForFile(eslint: ESLint, filePath: string): Promise { - const config = await eslint.calculateConfigForFile(filePath); - // `language` property has been added to the object returned by `ESLint.prototype.calculateConfigForFile(filePath)` since ESLint v9.5.0. - // But, `Linter.prototype.verify()` does not accept `language` option. So, remove it. - delete config['language']; - return config; -} diff --git a/src/eslint/linter.ts b/src/eslint/linter.ts index 8677ead0..e5cccb7c 100644 --- a/src/eslint/linter.ts +++ b/src/eslint/linter.ts @@ -7,7 +7,8 @@ * @author aladdin-add */ -import { Linter, Rule } from 'eslint'; +import { Rule } from 'eslint'; +import { LegacyESLint } from 'eslint/use-at-your-own-risk'; import { FixContext } from '../fix/index.js'; import { ruleFixer } from './rule-fixer.js'; import { SourceCodeFixer } from './source-code-fixer.js'; @@ -24,14 +25,13 @@ type FixedResult = { * @param linter */ // eslint-disable-next-line max-params -export function verifyAndFix( - linter: Linter, +export async function verifyAndFix( + eslint: LegacyESLint, text: string, - config: Linter.Config | Linter.FlatConfig[], filePath: string, ruleIds: string[], fixCreator: (context: FixContext) => Rule.Fix[], -): FixedResult { +): Promise { let fixedResult: FixedResult; let fixed = false; let passNumber = 0; @@ -49,10 +49,12 @@ export function verifyAndFix( do { passNumber++; - const messages = linter - .verify(currentText, config, filePath) + // eslint-disable-next-line no-await-in-loop + const results = await eslint.lintText(currentText, { filePath }); + const messages = results + .flatMap((result) => result.messages) .filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); - const sourceCode = linter.getSourceCode(); + const sourceCode = null as any; // TODO: Get `SourceCode` from `eslint` // Create `Rule.Fix[]` const fixContext: FixContext = { From 32cc68a4ad932a972d14117593f67ecf29a0a048 Mon Sep 17 00:00:00 2001 From: mizdra Date: Thu, 26 Sep 2024 00:01:32 +0900 Subject: [PATCH 5/7] improve vscode config --- .vscode/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index ee73d13a..c3c3b3bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, + "typescript.preferences.importModuleSpecifier": "relative", + "javascript.preferences.importModuleSpecifier": "relative", "files.exclude": { "**/.git": true, "**/.svn": true, From 43d9e339d8039e8f520fe12c0bab5d321b9f0d5c Mon Sep 17 00:00:00 2001 From: mizdra Date: Thu, 26 Sep 2024 00:04:35 +0900 Subject: [PATCH 6/7] get `SourceCode` instance from custom rule --- src/core.ts | 31 +++++++++++++++++-- src/eslint/linter.ts | 4 ++- src/plugin.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/plugin.ts diff --git a/src/core.ts b/src/core.ts index 2c5e7da9..9a0afb15 100644 --- a/src/core.ts +++ b/src/core.ts @@ -18,6 +18,7 @@ import { verifyAndFix, } from './fix/index.js'; import { format } from './formatter/index.js'; +import { plugin } from './plugin.js'; import { filterResultsByRuleId } from './util/eslint.js'; const { LegacyESLint, FlatESLint } = eslintPkg; @@ -51,15 +52,39 @@ export class Core { const { type, ...rest } = eslintOptions; this.eslint = new LegacyESLint({ ...rest, - plugins: {}, + plugins: { + ...rest.plugins, + 'eslint-interactive': plugin, + }, overrideConfig: { ...rest.overrideConfig, - plugins: [...(rest.overrideConfig?.plugins ?? []), 'eslint-plugin-eslint-interactive'], + plugins: [...(rest.overrideConfig?.plugins ?? []), 'eslint-interactive'], + rules: { + ...rest.overrideConfig?.rules, + 'eslint-interactive/source-code-snatcher': 'error', + }, }, }); } else { const { type, ...rest } = eslintOptions; - this.eslint = new FlatESLint(rest); + const overrideConfigs = Array.isArray(rest.overrideConfig) + ? rest.overrideConfig + : rest.overrideConfig + ? [rest.overrideConfig] + : []; + this.eslint = new FlatESLint({ + ...rest, + overrideConfig: [ + ...overrideConfigs, + { + ...rest.overrideConfig, + plugins: { 'eslint-interactive': plugin }, + rules: { + 'eslint-interactive/source-code-snatcher': 'error', + }, + }, + ], + }); } } diff --git a/src/eslint/linter.ts b/src/eslint/linter.ts index e5cccb7c..dd13621e 100644 --- a/src/eslint/linter.ts +++ b/src/eslint/linter.ts @@ -10,6 +10,7 @@ import { Rule } from 'eslint'; import { LegacyESLint } from 'eslint/use-at-your-own-risk'; import { FixContext } from '../fix/index.js'; +import { getLastSourceCode } from '../plugin.js'; import { ruleFixer } from './rule-fixer.js'; import { SourceCodeFixer } from './source-code-fixer.js'; @@ -54,7 +55,8 @@ export async function verifyAndFix( const messages = results .flatMap((result) => result.messages) .filter((message) => message.ruleId && ruleIds.includes(message.ruleId)); - const sourceCode = null as any; // TODO: Get `SourceCode` from `eslint` + const sourceCode = getLastSourceCode(); + if (!sourceCode) throw new Error('Failed to get the last source code.'); // Create `Rule.Fix[]` const fixContext: FixContext = { diff --git a/src/plugin.ts b/src/plugin.ts new file mode 100644 index 00000000..0bb0f923 --- /dev/null +++ b/src/plugin.ts @@ -0,0 +1,71 @@ +import { ESLint, Rule, SourceCode } from 'eslint'; + +let lastSourceCode: SourceCode | null = null; + +export function getLastSourceCode() { + return lastSourceCode; +} + +export const plugin: ESLint.Plugin = { + rules: { + /** + * This is a rule for getting a `SourceCode` instance. + * `ESLint` class does not provide a method for getting a `SourceCode` instance. As an alternative, we have prepared this custom rule. + */ + 'source-code-snatcher': { + create(context) { + lastSourceCode = context.sourceCode; + return {}; + }, + }, + /** This is a rule for testing purposes. */ + 'prefer-addition-shorthand': { + meta: { + type: 'suggestion', + // @ts-ignore + hasSuggestions: true, + }, + create(context: Rule.RuleContext) { + return { + // eslint-disable-next-line @typescript-eslint/naming-convention + AssignmentExpression: (node) => { + if (node.left.type !== 'Identifier') return; + const leftIdentifier = node.left; + if (node.right.type !== 'BinaryExpression') return; + const rightBinaryExpression = node.right; + if (rightBinaryExpression.operator !== '+') return; + if (rightBinaryExpression.left.type !== 'Identifier') return; + const rightIdentifier = rightBinaryExpression.left; + if (leftIdentifier.name !== rightIdentifier.name) return; + if (rightBinaryExpression.right.type !== 'Literal' || rightBinaryExpression.right.value !== 1) return; + + context.report({ + node, + message: 'The addition method is redundant.', + suggest: [ + { + desc: 'Use `val += 1` instead.', + fix(fixer) { + return fixer.replaceText(node, `${leftIdentifier.name} += 1`); + }, + }, + { + desc: 'Use `val++` instead.', + fix(fixer) { + return fixer.replaceText(node, `${leftIdentifier.name}++`); + }, + }, + { + desc: 'Use `++val` instead.', + fix(fixer) { + return fixer.replaceText(node, `++${leftIdentifier.name}`); + }, + }, + ], + }); + }, + }; + }, + }, + }, +}; From 056228fe4fb81252274425a05c7074e8d4fc9eff Mon Sep 17 00:00:00 2001 From: mizdra Date: Thu, 26 Sep 2024 00:05:39 +0900 Subject: [PATCH 7/7] fix broken test cases --- src/__snapshots__/core.test.ts.snap | 9 --- .../apply-suggestions.test.ts.snap | 6 +- src/fix/apply-auto-fixes.test.ts | 16 ++--- src/fix/apply-suggestions.test.ts | 40 ++++++------- .../convert-error-to-warning-per-file.test.ts | 32 +++++----- src/fix/disable-per-file.test.ts | 44 +++++++------- src/fix/disable-per-line.test.ts | 60 +++++++++---------- src/fix/make-fixable-and-fix.test.ts | 28 ++++----- src/test-util/fix-tester.ts | 32 ++++++---- .../prefer-addition-shorthand-rule.ts | 54 ----------------- 10 files changed, 132 insertions(+), 189 deletions(-) delete mode 100644 src/test-util/prefer-addition-shorthand-rule.ts diff --git a/src/__snapshots__/core.test.ts.snap b/src/__snapshots__/core.test.ts.snap index 4444aa26..03ed8e24 100644 --- a/src/__snapshots__/core.test.ts.snap +++ b/src/__snapshots__/core.test.ts.snap @@ -71,15 +71,6 @@ let a = 1;" exports[`Core > fixes problems with legacy config and 3rd-party plugins 1`] = ` "// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator -// eslint-disable-next-line test/ban-nullish-coalescing-operator 2 ?? 2;" `; diff --git a/src/fix/__snapshots__/apply-suggestions.test.ts.snap b/src/fix/__snapshots__/apply-suggestions.test.ts.snap index 53daa1fc..8f84b96a 100644 --- a/src/fix/__snapshots__/apply-suggestions.test.ts.snap +++ b/src/fix/__snapshots__/apply-suggestions.test.ts.snap @@ -22,7 +22,7 @@ exports[`apply-suggestions > filter には suggestions, message が渡ってく "line": 1, "message": "The addition method is redundant.", "nodeType": "AssignmentExpression", - "ruleId": "prefer-addition-shorthand", + "ruleId": "eslint-interactive/prefer-addition-shorthand", "severity": 2, "suggestions": [ { @@ -59,7 +59,7 @@ exports[`apply-suggestions > filter には suggestions, message が渡ってく }, ], "ruleIds": [ - "prefer-addition-shorthand", + "eslint-interactive/prefer-addition-shorthand", ], "sourceCode": { "text": "a = a + 1;", @@ -72,7 +72,7 @@ exports[`apply-suggestions > filter には suggestions, message が渡ってく "line": 1, "message": "The addition method is redundant.", "nodeType": "AssignmentExpression", - "ruleId": "prefer-addition-shorthand", + "ruleId": "eslint-interactive/prefer-addition-shorthand", "severity": 2, "suggestions": [ { diff --git a/src/fix/apply-auto-fixes.test.ts b/src/fix/apply-auto-fixes.test.ts index ba027106..7c672f85 100644 --- a/src/fix/apply-auto-fixes.test.ts +++ b/src/fix/apply-auto-fixes.test.ts @@ -9,25 +9,25 @@ const tester = new FixTester( ); describe('apply-auto-fixes', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { semi: 'error' }, }), ).toMatchInlineSnapshot(`"var val;"`); }); - test('同一行にて複数の rule を同時に fix できる', () => { + test('同一行にて複数の rule を同時に fix できる', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { 'semi': 'error', 'no-var': 'error' }, }), ).toMatchInlineSnapshot(`"let val;"`); }); - test('複数行を同時に fix できる', () => { + test('複数行を同時に fix できる', async () => { expect( - tester.test({ + await tester.test({ code: ['var val1', 'var val2', '', 'var val3'], rules: { semi: 'error' }, }), @@ -38,9 +38,9 @@ describe('apply-auto-fixes', () => { var val3;" `); }); - test('fixable な problem がない場合は何もしない', () => { + test('fixable な problem がない場合は何もしない', async () => { expect( - tester.test({ + await tester.test({ code: 'var val;', rules: { semi: 'error' }, }), diff --git a/src/fix/apply-suggestions.test.ts b/src/fix/apply-suggestions.test.ts index e01a27eb..8fb25bc6 100644 --- a/src/fix/apply-suggestions.test.ts +++ b/src/fix/apply-suggestions.test.ts @@ -10,20 +10,20 @@ const tester = new FixTester( ); describe('apply-suggestions', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'a = a + 1;', - rules: { 'prefer-addition-shorthand': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error' }, args: { filter: (suggestions) => suggestions[0] }, }), ).toMatchInlineSnapshot(`"a += 1;"`); }); - test('一度に複数の suggestion を適用できる', () => { + test('一度に複数の suggestion を適用できる', async () => { expect( - tester.test({ + await tester.test({ code: ['a = a + 1;', 'b = b + 1;'], - rules: { 'prefer-addition-shorthand': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error' }, args: { filter: (suggestions) => suggestions[0] }, }), ).toMatchInlineSnapshot(` @@ -31,11 +31,11 @@ describe('apply-suggestions', () => { b += 1;" `); }); - test('一度に複数の rule の suggestion を適用できる', () => { + test('一度に複数の rule の suggestion を適用できる', async () => { expect( - tester.test({ + await tester.test({ code: ['a = a + 1;', 'if (!key in object) {}'], - rules: { 'prefer-addition-shorthand': 'error', 'no-unsafe-negation': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error', 'no-unsafe-negation': 'error' }, args: { filter: (suggestions) => suggestions[0] }, }), ).toMatchInlineSnapshot(` @@ -43,19 +43,19 @@ describe('apply-suggestions', () => { if (!(key in object)) {}" `); }); - test('1 つの行に複数の suggestion があっても全ての suggestion が適用できる', () => { + test('1 つの行に複数の suggestion があっても全ての suggestion が適用できる', async () => { expect( - tester.test({ + await tester.test({ code: ['a = a + 1; b = b + 1;'], - rules: { 'prefer-addition-shorthand': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error' }, args: { filter: (suggestions) => suggestions[0] }, }), ).toMatchInlineSnapshot(`"a += 1; b += 1;"`); }); - test('filter には suggestions, message が渡ってくる', () => { - tester.test({ + test('filter には suggestions, message が渡ってくる', async () => { + await tester.test({ code: ['a = a + 1;'], - rules: { 'prefer-addition-shorthand': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error' }, args: { filter: (suggestions, message, context) => { expect({ @@ -74,20 +74,20 @@ describe('apply-suggestions', () => { }, }); }); - test('suggestion がない場合は何もしない', () => { + test('suggestion がない場合は何もしない', async () => { expect( - tester.test({ + await tester.test({ code: 'a = a + 1;', rules: { semi: 'error' }, args: { filter: (suggestions) => suggestions[0] }, }), ).toMatchInlineSnapshot(`null`); }); - test('filter から null もしくは undefined を返すと、suggestion は適用されない', () => { + test('filter から null もしくは undefined を返すと、suggestion は適用されない', async () => { expect( - tester.test({ + await tester.test({ code: 'a = a + 1;', - rules: { 'prefer-addition-shorthand': 'error' }, + rules: { 'eslint-interactive/prefer-addition-shorthand': 'error' }, args: { filter: (_suggestions) => (Math.random() < 0.5 ? null : undefined) }, }), ).toMatchInlineSnapshot(`null`); diff --git a/src/fix/convert-error-to-warning-per-file.test.ts b/src/fix/convert-error-to-warning-per-file.test.ts index caccae30..9911ae83 100644 --- a/src/fix/convert-error-to-warning-per-file.test.ts +++ b/src/fix/convert-error-to-warning-per-file.test.ts @@ -9,9 +9,9 @@ const tester = new FixTester( ); describe('convert-error-to-warning-per-file', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { semi: 'error' }, }), @@ -20,9 +20,9 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('fixes multiple rules', () => { + test('fixes multiple rules', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { 'semi': 'error', 'no-var': 'error' }, }), @@ -31,9 +31,9 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('can add description', () => { + test('can add description', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { semi: 'error' }, args: { description: 'comment' }, @@ -43,17 +43,17 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('ignores warnings', () => { + test('ignores warnings', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint semi: 1 */', 'var val'], rules: { semi: 'error' }, }), ).toMatchInlineSnapshot(`null`); }); - test('combines directives into one', () => { + test('combines directives into one', async () => { expect( - tester.test({ + await tester.test({ code: ['var val', 'var val'], rules: { semi: 'error' }, }), @@ -63,9 +63,9 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('`eslint` directive has precedence over `@ts-check`', () => { + test('`eslint` directive has precedence over `@ts-check`', async () => { expect( - tester.test({ + await tester.test({ code: ['// @ts-check', 'var val'], rules: { 'no-var': 'error' }, }), @@ -75,9 +75,9 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('`eslint` directive has precedence over `/* @jsxImportSource xxx */`', () => { + test('`eslint` directive has precedence over `/* @jsxImportSource xxx */`', async () => { expect( - tester.test({ + await tester.test({ code: ['/* @jsxImportSource @emotion/react */', 'var val'], rules: { 'no-var': 'error' }, }), @@ -87,9 +87,9 @@ describe('convert-error-to-warning-per-file', () => { var val" `); }); - test('The shebang has precedence over `eslint` directive', () => { + test('The shebang has precedence over `eslint` directive', async () => { expect( - tester.test({ + await tester.test({ code: ['#!/usr/bin/env node', 'var val'], rules: { 'no-var': 'error' }, }), diff --git a/src/fix/disable-per-file.test.ts b/src/fix/disable-per-file.test.ts index a497f776..f3991c21 100644 --- a/src/fix/disable-per-file.test.ts +++ b/src/fix/disable-per-file.test.ts @@ -9,9 +9,9 @@ const tester = new FixTester( ); describe('disable-per-file', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { semi: 'error' }, }), @@ -20,9 +20,9 @@ describe('disable-per-file', () => { var val" `); }); - test('複数の rule を同時に disable できる', () => { + test('複数の rule を同時に disable できる', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { 'semi': 'error', 'no-var': 'error' }, }), @@ -31,9 +31,9 @@ describe('disable-per-file', () => { var val" `); }); - test('既に disable comment が付いている場合は、末尾に足す', () => { + test('既に disable comment が付いている場合は、末尾に足す', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable semi */', 'var val'], rules: { 'no-var': 'error' }, }), @@ -42,9 +42,9 @@ describe('disable-per-file', () => { var val" `); }); - test('disable description があっても disable できる', () => { + test('disable description があっても disable できる', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable semi -- comment */', 'var val'], rules: { 'no-var': 'error' }, }), @@ -53,9 +53,9 @@ describe('disable-per-file', () => { var val" `); }); - test('disable description を追加できる', () => { + test('disable description を追加できる', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable semi */', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'comment' }, @@ -65,9 +65,9 @@ describe('disable-per-file', () => { var val" `); }); - test('既に disable description があるコメントに対しても disable description を追加できる', () => { + test('既に disable description があるコメントに対しても disable description を追加できる', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable semi -- foo */', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'bar' }, @@ -77,9 +77,9 @@ describe('disable-per-file', () => { var val" `); }); - test('add a description to the line before the disable comment if there is already disable comment', () => { + test('add a description to the line before the disable comment if there is already disable comment', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable semi -- foo */', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'bar', descriptionPosition: 'previousLine' }, @@ -90,9 +90,9 @@ describe('disable-per-file', () => { var val" `); }); - test('add a description comment before the line with the problem', () => { + test('add a description comment before the line with the problem', async () => { expect( - tester.test({ + await tester.test({ code: ['var val'], rules: { semi: 'error' }, args: { description: 'foo', descriptionPosition: 'previousLine' }, @@ -103,9 +103,9 @@ describe('disable-per-file', () => { var val" `); }); - test('`eslint-disable` has precedence over `@ts-check`', () => { + test('`eslint-disable` has precedence over `@ts-check`', async () => { expect( - tester.test({ + await tester.test({ code: ['// @ts-check', 'var val'], rules: { 'no-var': 'error' }, }), @@ -115,9 +115,9 @@ describe('disable-per-file', () => { var val" `); }); - test('`eslint-disable` has precedence over `/* @jsxImportSource xxx */`', () => { + test('`eslint-disable` has precedence over `/* @jsxImportSource xxx */`', async () => { expect( - tester.test({ + await tester.test({ code: ['/* @jsxImportSource @emotion/react */', 'var val'], rules: { 'no-var': 'error' }, }), @@ -127,9 +127,9 @@ describe('disable-per-file', () => { var val" `); }); - test('The shebang has precedence over `eslint-disable`', () => { + test('The shebang has precedence over `eslint-disable`', async () => { expect( - tester.test({ + await tester.test({ code: ['#!/usr/bin/env node', 'var val'], rules: { 'no-var': 'error' }, }), diff --git a/src/fix/disable-per-line.test.ts b/src/fix/disable-per-line.test.ts index 745b8109..d4161602 100644 --- a/src/fix/disable-per-line.test.ts +++ b/src/fix/disable-per-line.test.ts @@ -11,9 +11,9 @@ const tester = new FixTester( ); describe('disable-per-line', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { semi: 'error' }, }), @@ -22,9 +22,9 @@ describe('disable-per-line', () => { var val" `); }); - test('同一行にて複数の rule を同時に disable できる', () => { + test('同一行にて複数の rule を同時に disable できる', async () => { expect( - tester.test({ + await tester.test({ code: 'var val', rules: { 'semi': 'error', 'no-var': 'error' }, }), @@ -33,9 +33,9 @@ describe('disable-per-line', () => { var val" `); }); - test('既に disable comment が付いている場合は、末尾に足す', () => { + test('既に disable comment が付いている場合は、末尾に足す', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi', 'var val'], rules: { 'no-var': 'error' }, }), @@ -44,17 +44,17 @@ describe('disable-per-line', () => { var val" `); }); - test('既に disable されている場合は何もしない', () => { + test('既に disable されている場合は何もしない', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi', 'var val'], rules: { semi: 'error' }, }), ).toMatchInlineSnapshot(`null`); }); - test('`/* ... */` スタイルであっても disable できる', () => { + test('`/* ... */` スタイルであっても disable できる', async () => { expect( - tester.test({ + await tester.test({ code: ['/* eslint-disable-next-line semi */', 'var val'], rules: { 'no-var': 'error' }, }), @@ -63,9 +63,9 @@ describe('disable-per-line', () => { var val" `); }); - test('disable description があっても disable できる', () => { + test('disable description があっても disable できる', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi -- comment', 'var val'], rules: { 'no-var': 'error' }, }), @@ -74,9 +74,9 @@ describe('disable-per-line', () => { var val" `); }); - test('disable description を追加できる', () => { + test('disable description を追加できる', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'comment' }, @@ -86,9 +86,9 @@ describe('disable-per-line', () => { var val" `); }); - test('既に disable description があるコメントに対しても disable description を追加できる', () => { + test('既に disable description があるコメントに対しても disable description を追加できる', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi -- foo', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'bar' }, @@ -98,9 +98,9 @@ describe('disable-per-line', () => { var val" `); }); - test('add a description to the line before the disable comment if there is already disable comment', () => { + test('add a description to the line before the disable comment if there is already disable comment', async () => { expect( - tester.test({ + await tester.test({ code: ['// eslint-disable-next-line semi -- foo', 'var val'], rules: { 'no-var': 'error' }, args: { description: 'bar', descriptionPosition: 'previousLine' }, @@ -111,9 +111,9 @@ describe('disable-per-line', () => { var val" `); }); - test('add a description comment before the line with the problem', () => { + test('add a description comment before the line with the problem', async () => { expect( - tester.test({ + await tester.test({ code: ['var val'], rules: { semi: 'error' }, args: { description: 'foo', descriptionPosition: 'previousLine' }, @@ -124,9 +124,9 @@ describe('disable-per-line', () => { var val" `); }); - test('複数行を同時に disable できる', () => { + test('複数行を同時に disable できる', async () => { expect( - tester.test({ + await tester.test({ code: ['var val1', 'var val2', '', 'var val3'], rules: { 'no-var': 'error' }, }), @@ -141,9 +141,9 @@ describe('disable-per-line', () => { `); }); describe('add a disable comment for JSX', () => { - test('when descriptionPosition is sameLine', () => { + test('when descriptionPosition is sameLine', async () => { expect( - tester.test({ + await tester.test({ code: [ 'var jsx =
', ' text1', @@ -173,9 +173,9 @@ describe('disable-per-line', () => {
;" `); }); - test('when descriptionPosition is previousLine', () => { + test('when descriptionPosition is previousLine', async () => { expect( - tester.test({ + await tester.test({ code: [ 'var jsx =
', ' text1', @@ -210,9 +210,9 @@ describe('disable-per-line', () => { `); }); }); - test('disable comment のある行に disable comment 以外の Node があっても disable できる', () => { + test('disable comment のある行に disable comment 以外の Node があっても disable できる', async () => { expect( - tester.test({ + await tester.test({ code: [ 'var val1; // eslint-disable-next-line semi', 'var val2;', @@ -235,9 +235,9 @@ describe('disable-per-line', () => { var val6;" `); }); - test('supports auto-indent', () => { + test('supports auto-indent', async () => { expect( - tester.test({ + await tester.test({ code: [ '{', ' void 0;', diff --git a/src/fix/make-fixable-and-fix.test.ts b/src/fix/make-fixable-and-fix.test.ts index 52c77ed3..5ff2ee0e 100644 --- a/src/fix/make-fixable-and-fix.test.ts +++ b/src/fix/make-fixable-and-fix.test.ts @@ -18,9 +18,9 @@ const tester = new FixTester( ); describe('make-fixable-and-fix', () => { - test('basic', () => { + test('basic', async () => { expect( - tester.test({ + await tester.test({ code: 'const a = 1;', rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, args: { @@ -32,9 +32,9 @@ describe('make-fixable-and-fix', () => { }), ).toMatchInlineSnapshot(`"const _a = 1;"`); }); - test('can process multiple messages at once', () => { + test('can process multiple messages at once', async () => { expect( - tester.test({ + await tester.test({ code: ['const a = 1;', 'const b = 2;'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, args: { @@ -49,9 +49,9 @@ describe('make-fixable-and-fix', () => { const _b = 2;" `); }); - test('can process messages of multiple rules at once', () => { + test('can process messages of multiple rules at once', async () => { expect( - tester.test({ + await tester.test({ code: ['const a = 1;', 'let b = 2;', 'b++;', 'console.log(b);'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }], 'no-plusplus': 'error' }, args: { @@ -74,9 +74,9 @@ describe('make-fixable-and-fix', () => { console.log(b);" `); }); - test('can process messages on the same line', () => { + test('can process messages on the same line', async () => { expect( - tester.test({ + await tester.test({ code: ['const a = 1; const b = 2;'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, args: { @@ -88,8 +88,8 @@ describe('make-fixable-and-fix', () => { }), ).toMatchInlineSnapshot(`"const _a = 1; const _b = 2;"`); }); - test('`fixableMaker` receives the message and node.', () => { - tester.test({ + test('`fixableMaker` receives the message and node.', async () => { + await tester.test({ code: ['const a = 1;'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, args: { @@ -110,8 +110,8 @@ describe('make-fixable-and-fix', () => { }, }); }); - test('node is null if message is not associated with a node', () => { - tester.test({ + test('node is null if message is not associated with a node', async () => { + await tester.test({ code: ['// this is comment'], rules: { 'capitalized-comments': 'error' }, args: { @@ -122,9 +122,9 @@ describe('make-fixable-and-fix', () => { }, }); }); - test('do not process if `fixableMaker` returns null', () => { + test('do not process if `fixableMaker` returns null', async () => { expect( - tester.test({ + await tester.test({ code: 'const a = 1;', rules: { 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }] }, args: { diff --git a/src/test-util/fix-tester.ts b/src/test-util/fix-tester.ts index c840010e..85dfde0e 100644 --- a/src/test-util/fix-tester.ts +++ b/src/test-util/fix-tester.ts @@ -1,7 +1,8 @@ import { Linter, Rule } from 'eslint'; +import { LegacyESLint } from 'eslint/use-at-your-own-risk'; import { verifyAndFix } from '../eslint/linter.js'; import { FixContext } from '../fix/index.js'; -import { preferAdditionShorthandRule } from './prefer-addition-shorthand-rule.js'; +import { plugin } from '../plugin.js'; const DEFAULT_FILENAME = 'test.js'; @@ -33,7 +34,6 @@ type TestResult = string | null; * The test utility for the fix. */ export class FixTester { - private linter: Linter; private fixCreator: (context: FixContext, args: FixArgs) => Rule.Fix[]; private defaultFixArgs: FixArgs; private defaultLinterConfig: Linter.Config; @@ -42,8 +42,6 @@ export class FixTester { defaultFixArgs: FixArgs, defaultLinterConfig: Linter.Config, ) { - this.linter = new Linter({ configType: 'eslintrc' }); - this.linter.defineRule('prefer-addition-shorthand', preferAdditionShorthandRule); this.fixCreator = fixCreator; this.defaultFixArgs = defaultFixArgs; this.defaultLinterConfig = defaultLinterConfig; @@ -53,20 +51,28 @@ export class FixTester { * @param testCase The test case. * @returns The fixed code. If the fix skipped, null is returned. */ - test(testCase: TestCase): TestResult { + async test(testCase: TestCase): Promise { const code = Array.isArray(testCase.code) ? testCase.code.join('\n') : testCase.code; const filePath = testCase.filename ?? DEFAULT_FILENAME; - const config: Linter.Config = { - ...this.defaultLinterConfig, - rules: { - ...this.defaultLinterConfig.rules, - ...testCase.rules, - }, - }; const ruleIdsToFix = Object.keys(testCase.rules); - const fixedResult = verifyAndFix(this.linter, code, config, filePath, ruleIdsToFix, (context) => + const eslint = new LegacyESLint({ + useEslintrc: false, + plugins: { + 'eslint-interactive': plugin, + }, + overrideConfig: { + ...this.defaultLinterConfig, + plugins: ['eslint-interactive', ...(this.defaultLinterConfig.plugins ?? [])], + rules: { + ...this.defaultLinterConfig.rules, + ...testCase.rules, + 'eslint-interactive/source-code-snatcher': 'error', + }, + }, + }); + const fixedResult = await verifyAndFix(eslint, code, filePath, ruleIdsToFix, (context) => this.fixCreator(context, testCase.args ?? this.defaultFixArgs), ); diff --git a/src/test-util/prefer-addition-shorthand-rule.ts b/src/test-util/prefer-addition-shorthand-rule.ts deleted file mode 100644 index 7f628461..00000000 --- a/src/test-util/prefer-addition-shorthand-rule.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Rule } from 'eslint'; - -/** - * @file This is a rule for testing purposes. - */ - -export const preferAdditionShorthandRule: Rule.RuleModule = { - meta: { - type: 'suggestion', - // @ts-ignore - hasSuggestions: true, - }, - create(context: Rule.RuleContext) { - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - AssignmentExpression: (node) => { - if (node.left.type !== 'Identifier') return; - const leftIdentifier = node.left; - if (node.right.type !== 'BinaryExpression') return; - const rightBinaryExpression = node.right; - if (rightBinaryExpression.operator !== '+') return; - if (rightBinaryExpression.left.type !== 'Identifier') return; - const rightIdentifier = rightBinaryExpression.left; - if (leftIdentifier.name !== rightIdentifier.name) return; - if (rightBinaryExpression.right.type !== 'Literal' || rightBinaryExpression.right.value !== 1) return; - - context.report({ - node, - message: 'The addition method is redundant.', - suggest: [ - { - desc: 'Use `val += 1` instead.', - fix(fixer) { - return fixer.replaceText(node, `${leftIdentifier.name} += 1`); - }, - }, - { - desc: 'Use `val++` instead.', - fix(fixer) { - return fixer.replaceText(node, `${leftIdentifier.name}++`); - }, - }, - { - desc: 'Use `++val` instead.', - fix(fixer) { - return fixer.replaceText(node, `++${leftIdentifier.name}`); - }, - }, - ], - }); - }, - }; - }, -};