diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1ea0b7c78cb7..1452d7de6cdb 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -224,18 +224,17 @@ export default createRule({ requiresQuoting(missingBranchName.toString(), compilerOptions.target) ) { const escapedBranchName = missingBranchName - .replace(/'/g, "\\'") - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r'); + .replaceAll("'", "\\'") + .replaceAll('\n', '\\n') + .replaceAll('\r', '\\r'); caseTest = `${symbolName}['${escapedBranchName}']`; } - const errorMessage = `Not implemented yet: ${caseTest} case`; - const escapedErrorMessage = errorMessage.replace(/'/g, "\\'"); - missingCases.push( - `case ${caseTest}: { throw new Error('${escapedErrorMessage}') }`, + `case ${caseTest}: { throw new Error('Not implemented yet: ${caseTest + .replaceAll('\\', '\\\\') + .replaceAll("'", "\\'")} case') }`, ); } @@ -305,9 +304,7 @@ export default createRule({ context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', - data: { - missingBranches: 'default', - }, + data: { missingBranches: 'default' }, suggest: [ { messageId: 'addMissingCases', diff --git a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts index b7919942e011..ff4e2a4199db 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -1964,7 +1964,7 @@ switch (value) { switch (a) { case Enum.a: { throw new Error('Not implemented yet: Enum.a case') } - case Enum['key-with\\n\\n new-line']: { throw new Error('Not implemented yet: Enum[\\'key-with\\n\\n new-line\\'] case') } + case Enum['key-with\\n\\n new-line']: { throw new Error('Not implemented yet: Enum[\\'key-with\\\\n\\\\n new-line\\'] case') } } `, }, @@ -1999,7 +1999,7 @@ switch (value) { switch (a) { case Enum.a: { throw new Error('Not implemented yet: Enum.a case') } - case Enum['\\'a\\' \`b\` "c"']: { throw new Error('Not implemented yet: Enum[\\'\\\\'a\\\\' \`b\` "c"\\'] case') } + case Enum['\\'a\\' \`b\` "c"']: { throw new Error('Not implemented yet: Enum[\\'\\\\\\'a\\\\\\' \`b\` "c"\\'] case') } } `, }, diff --git a/packages/rule-tester/src/RuleTester.ts b/packages/rule-tester/src/RuleTester.ts index 4b8e54e57856..b4911ef6febc 100644 --- a/packages/rule-tester/src/RuleTester.ts +++ b/packages/rule-tester/src/RuleTester.ts @@ -254,10 +254,7 @@ export class RuleTester extends TestFramework { throw new Error(DUPLICATE_PARSER_ERROR_MESSAGE); } if (!test.filename) { - return { - ...test, - filename: getFilename(test.parserOptions), - }; + return { ...test, filename: getFilename(test.parserOptions) }; } return test; }; @@ -483,6 +480,8 @@ export class RuleTester extends TestFramework { output: string; beforeAST: TSESTree.Program; afterAST: TSESTree.Program; + config: RuleTesterConfig; + filename?: string; } { let config: TesterConfigWithDefaults = merge({}, this.#testerConfig); let code; @@ -656,6 +655,8 @@ export class RuleTester extends TestFramework { // is definitely assigned within the `rule-tester/validate-ast` rule // eslint-disable-next-line @typescript-eslint/no-non-null-assertion afterAST: cloneDeeplyExcludesParent(afterAST!), + config, + filename, }; } @@ -1046,6 +1047,25 @@ export class RuleTester extends TestFramework { [actualSuggestion], ).output; + // Verify if suggestion fix makes a syntax error or not. + const errorMessageInSuggestion = this.#linter + .verify( + codeWithAppliedSuggestion, + result.config, + result.filename, + ) + .find(m => m.fatal); + + assert( + !errorMessageInSuggestion, + [ + 'A fatal parsing error occurred in suggestion fix.', + `Error: ${errorMessageInSuggestion?.message}`, + 'Suggestion output:', + codeWithAppliedSuggestion, + ].join('\n'), + ); + assert.strictEqual( codeWithAppliedSuggestion, expectedSuggestion.output, diff --git a/packages/rule-tester/tests/RuleTester.test.ts b/packages/rule-tester/tests/RuleTester.test.ts index 2cfc3678ad58..20bc0c9cb4a7 100644 --- a/packages/rule-tester/tests/RuleTester.test.ts +++ b/packages/rule-tester/tests/RuleTester.test.ts @@ -112,6 +112,7 @@ runRuleForItemSpy.mockImplementation((_1, _2, testCase) => { output: testCase.code, afterAST: EMPTY_PROGRAM, beforeAST: EMPTY_PROGRAM, + config: { parser: '' }, }; }); diff --git a/packages/rule-tester/tests/eslint-base/eslint-base.test.js b/packages/rule-tester/tests/eslint-base/eslint-base.test.js index 56429508e4e3..260dcc542970 100644 --- a/packages/rule-tester/tests/eslint-base/eslint-base.test.js +++ b/packages/rule-tester/tests/eslint-base/eslint-base.test.js @@ -2045,6 +2045,48 @@ describe("RuleTester", () => { }, "Error should have 2 suggestions. Instead found 1 suggestions"); }); + it("should throw if suggestion fix made a syntax error.", () => { + assert.throw(() => { + ruleTester.run( + "foo", + { + meta: { hasSuggestions: true }, + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: "make a syntax error", + suggest: [ + { + desc: "make a syntax error", + fix(fixer) { + return fixer.replaceText(node, "one two"); + } + } + ] + }); + } + }; + } + }, + { + valid: [""], + invalid: [{ + code: "one()", + errors: [{ + message: "make a syntax error", + suggestions: [{ + desc: "make a syntax error", + output: "one two()" + }] + }] + }] + } + ); + }, /A fatal parsing error occurred in suggestion fix\.\nError: .+\nSuggestion output:\n.+/u); + }); + it("should throw if the suggestion description doesn't match", () => { assert.throws(() => { ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, {