diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 911d2f6e12c..fd24f0e97f5 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -110,7 +110,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-dynamic-delete`](./docs/rules/no-dynamic-delete.md) | Disallow the delete operator with computed key expressions | | :wrench: | | | [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/no-extra-non-null-assertion`](./docs/rules/no-extra-non-null-assertion.md) | Disallow extra non-null assertion | | | | +| [`@typescript-eslint/no-extra-non-null-assertion`](./docs/rules/no-extra-non-null-assertion.md) | Disallow extra non-null assertion | | :wrench: | | | [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces | | | | | [`@typescript-eslint/no-floating-promises`](./docs/rules/no-floating-promises.md) | Requires Promise-like values to be handled appropriately | | | :thought_balloon: | | [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop | :heavy_check_mark: | | :thought_balloon: | diff --git a/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts index 39969de283f..44182650d14 100644 --- a/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-extra-non-null-assertion.ts @@ -10,6 +10,7 @@ export default util.createRule({ category: 'Stylistic Issues', recommended: false, }, + fixable: 'code', schema: [], messages: { noExtraNonNullAssertion: 'Forbidden extra non-null assertion.', @@ -20,12 +21,19 @@ export default util.createRule({ function checkExtraNonNullAssertion( node: TSESTree.TSNonNullExpression, ): void { - context.report({ messageId: 'noExtraNonNullAssertion', node }); + context.report({ + node, + messageId: 'noExtraNonNullAssertion', + fix(fixer) { + return fixer.removeRange([node.range[1] - 1, node.range[1]]); + }, + }); } return { 'TSNonNullExpression > TSNonNullExpression': checkExtraNonNullAssertion, 'OptionalMemberExpression > TSNonNullExpression': checkExtraNonNullAssertion, + 'OptionalCallExpression > TSNonNullExpression.callee': checkExtraNonNullAssertion, }; }, }); diff --git a/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts index 074dc90697e..51ff2d2e43f 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-non-null-assertion.test.ts @@ -31,21 +31,13 @@ function foo(bar?: { n: number }) { { code: ` const foo: { bar: number } | null = null; -const bar = foo!!!!.bar; +const bar = foo!!.bar; + `, + output: ` +const foo: { bar: number } | null = null; +const bar = foo!.bar; `, errors: [ - { - messageId: 'noExtraNonNullAssertion', - endColumn: 19, - column: 13, - line: 3, - }, - { - messageId: 'noExtraNonNullAssertion', - endColumn: 18, - column: 13, - line: 3, - }, { messageId: 'noExtraNonNullAssertion', endColumn: 17, @@ -58,6 +50,11 @@ const bar = foo!!!!.bar; code: ` function foo(bar: number | undefined) { const bar: number = bar!!; +} + `, + output: ` +function foo(bar: number | undefined) { + const bar: number = bar!; } `, errors: [ @@ -73,6 +70,11 @@ function foo(bar: number | undefined) { code: ` function foo(bar?: { n: number }) { return bar!?.n; +} + `, + output: ` +function foo(bar?: { n: number }) { + return bar?.n; } `, errors: [ @@ -87,29 +89,101 @@ function foo(bar?: { n: number }) { { code: ` function foo(bar?: { n: number }) { - return bar!!!?.n; + return bar!?.(); +} + `, + output: ` +function foo(bar?: { n: number }) { + return bar?.(); } `, errors: [ { messageId: 'noExtraNonNullAssertion', - endColumn: 16, + endColumn: 14, column: 10, line: 3, }, + ], + }, + // parentheses + { + code: ` +const foo: { bar: number } | null = null; +const bar = (foo!)!.bar; + `, + output: ` +const foo: { bar: number } | null = null; +const bar = (foo)!.bar; + `, + errors: [ + { + messageId: 'noExtraNonNullAssertion', + endColumn: 18, + column: 14, + line: 3, + }, + ], + }, + { + code: ` +function foo(bar?: { n: number }) { + return (bar!)?.n; +} + `, + output: ` +function foo(bar?: { n: number }) { + return (bar)?.n; +} + `, + errors: [ { messageId: 'noExtraNonNullAssertion', endColumn: 15, - column: 10, + column: 11, line: 3, }, + ], + }, + { + code: ` +function foo(bar?: { n: number }) { + return (bar)!?.n; +} + `, + output: ` +function foo(bar?: { n: number }) { + return (bar)?.n; +} + `, + errors: [ { messageId: 'noExtraNonNullAssertion', - endColumn: 14, + endColumn: 16, column: 10, line: 3, }, ], }, + { + code: ` +function foo(bar?: { n: number }) { + return (bar!)?.(); +} + `, + output: ` +function foo(bar?: { n: number }) { + return (bar)?.(); +} + `, + errors: [ + { + messageId: 'noExtraNonNullAssertion', + endColumn: 15, + column: 11, + line: 3, + }, + ], + }, ], });