diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts index 25617ca41f25..f80d0ee00914 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -29,33 +29,19 @@ export default createRule<[], MessageIds>({ return { TSNonNullExpression(node): void { const suggest: TSESLint.ReportSuggestionArray = []; - function convertTokenToOptional( - replacement: '?.' | '?', - ): TSESLint.ReportFixFunction { - return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => { - const operator = sourceCode.getTokenAfter( - node.expression, - isNonNullAssertionPunctuator, - ); - if (operator) { - return fixer.replaceText(operator, replacement); - } - return null; - }; + // it always exists in non-null assertion + const nonNullOperator = sourceCode.getTokenAfter( + node.expression, + isNonNullAssertionPunctuator, + )!; + + function replaceTokenWithOptional(): TSESLint.ReportFixFunction { + return fixer => fixer.replaceText(nonNullOperator, '?.'); } - function removeToken(): TSESLint.ReportFixFunction { - return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => { - const operator = sourceCode.getTokenAfter( - node.expression, - isNonNullAssertionPunctuator, - ); - if (operator) { - return fixer.remove(operator); - } - return null; - }; + function removeToken(): TSESLint.ReportFixFunction { + return fixer => fixer.remove(nonNullOperator); } if ( @@ -67,13 +53,21 @@ export default createRule<[], MessageIds>({ // it is x![y]?.z suggest.push({ messageId: 'suggestOptionalChain', - fix: convertTokenToOptional('?.'), + fix: replaceTokenWithOptional(), }); } else { // it is x!.y?.z suggest.push({ messageId: 'suggestOptionalChain', - fix: convertTokenToOptional('?'), + fix(fixer) { + // x!.y?.z + // ^ punctuator + const punctuator = sourceCode.getTokenAfter(nonNullOperator)!; + return [ + fixer.remove(nonNullOperator), + fixer.insertTextBefore(punctuator, '?'), + ]; + }, }); } } else { @@ -99,7 +93,7 @@ export default createRule<[], MessageIds>({ // it is x.y?.z!() suggest.push({ messageId: 'suggestOptionalChain', - fix: convertTokenToOptional('?.'), + fix: replaceTokenWithOptional(), }); } else { // it is x.y.z!?.() diff --git a/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts index 10dbb0d09c3c..d1a93c058d12 100644 --- a/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-non-null-assertion'; @@ -293,5 +293,101 @@ ruleTester.run('no-non-null-assertion', rule, { }, ], }, + { + code: noFormat` +x! +.y + `, + errors: [ + { + messageId: 'noNonNull', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'suggestOptionalChain', + output: ` +x +?.y + `, + }, + ], + }, + ], + }, + { + code: noFormat` +x! +// comment +.y + `, + errors: [ + { + messageId: 'noNonNull', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'suggestOptionalChain', + output: ` +x +// comment +?.y + `, + }, + ], + }, + ], + }, + { + code: noFormat` +x! + // comment + . /* comment */ + y + `, + errors: [ + { + messageId: 'noNonNull', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'suggestOptionalChain', + output: ` +x + // comment + ?. /* comment */ + y + `, + }, + ], + }, + ], + }, + { + code: noFormat` +x! + // comment + /* comment */ ['y'] + `, + errors: [ + { + messageId: 'noNonNull', + line: 2, + column: 1, + suggestions: [ + { + messageId: 'suggestOptionalChain', + output: ` +x?. + // comment + /* comment */ ['y'] + `, + }, + ], + }, + ], + }, ], });