Content-Length: 22283 | pFad | http://github.com/typescript-eslint/typescript-eslint/pull/11243.diff
thub.com
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index f2bfa26bb1a8..6b7873f1303e 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -15,6 +15,7 @@ import type { MakeRequired } from '../util';
import {
collectVariables,
createRule,
+ getFixOrSuggest,
getNameLocationInGlobalDirectiveComment,
isDefinitionFile,
isFunction,
@@ -23,7 +24,11 @@ import {
} from '../util';
import { referenceContainsTypeQuery } from '../util/referenceContainsTypeQuery';
-export type MessageIds = 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType';
+export type MessageIds =
+ | 'unusedVar'
+ | 'unusedVarSuggestion'
+ | 'usedIgnoredVar'
+ | 'usedOnlyAsType';
export type Options = [
| 'all'
| 'local'
@@ -38,6 +43,9 @@ export type Options = [
reportUsedIgnorePattern?: boolean;
vars?: 'all' | 'local';
varsIgnorePattern?: string;
+ enableAutofixRemoval?: {
+ imports: boolean;
+ };
},
];
@@ -52,6 +60,9 @@ interface TranslatedOptions {
reportUsedIgnorePattern: boolean;
vars: 'all' | 'local';
varsIgnorePattern?: RegExp;
+ enableAutofixRemoval?: {
+ imports: boolean;
+ };
}
type VariableType =
@@ -74,8 +85,13 @@ export default createRule({
extendsBaseRule: true,
recommended: 'recommended',
},
+ fixable: 'code',
+ // If generate suggest dynamically, disable the eslint rule.
+ // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
+ hasSuggestions: true,
messages: {
unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
+ unusedVarSuggestion: 'Remove unused variable.',
usedIgnoredVar:
"'{{varName}}' is marked as ignored but is used{{additional}}.",
usedOnlyAsType:
@@ -117,6 +133,16 @@ export default createRule({
description:
'Regular expressions of destructured array variable names to not check for usage.',
},
+ enableAutofixRemoval: {
+ type: 'object',
+ properties: {
+ imports: {
+ type: 'boolean',
+ description:
+ 'Whether to enable autofix for removing unused imports.',
+ },
+ },
+ },
ignoreClassWithStaticInitBlock: {
type: 'boolean',
description:
@@ -208,6 +234,10 @@ export default createRule({
'u',
);
}
+
+ if (firstOption.enableAutofixRemoval) {
+ options.enableAutofixRemoval = firstOption.enableAutofixRemoval;
+ }
}
return options;
@@ -681,12 +711,83 @@ export default createRule({
},
};
+ const fixer: TSESLint.ReportFixFunction = fixer => {
+ // Find the import statement
+ const def = unusedVar.defs.find(
+ d => d.type === DefinitionType.ImportBinding,
+ );
+ if (!def) {
+ return null;
+ }
+
+ const source = context.sourceCode;
+ const node = def.node;
+ const decl = node.parent;
+ if (decl.type !== AST_NODE_TYPES.ImportDeclaration) {
+ // decl.type is Program, import foo = require('bar');
+ return fixer.remove(node);
+ }
+
+ const afterNodeToken = source.getTokenAfter(node);
+ const beforeNodeToken = source.getTokenBefore(node);
+ const prevBeforeNodeToken = beforeNodeToken
+ ? source.getTokenBefore(beforeNodeToken)
+ : null;
+
+ // Remove import declaration if no used specifiers are left
+ if (decl.specifiers.length === 1) {
+ return fixer.removeRange(decl.range);
+ }
+
+ // case: remove braces if no used named specifiers are left
+ const restNamed = decl.specifiers.filter(
+ s => s.type === AST_NODE_TYPES.ImportSpecifier,
+ );
+ if (
+ restNamed.length === 1 &&
+ afterNodeToken?.value === '}' &&
+ prevBeforeNodeToken?.value === ','
+ ) {
+ return fixer.removeRange([
+ prevBeforeNodeToken.range[0],
+ afterNodeToken.range[1],
+ ]);
+ }
+
+ // case: Remove comma after node, import { unused, used } from 'a';
+ if (afterNodeToken?.value === ',') {
+ return fixer.removeRange([
+ node.range[0],
+ afterNodeToken.range[1],
+ ]);
+ }
+
+ // case: Remove comma before node, import { used, unused } from 'a';
+ if (beforeNodeToken?.value === ',') {
+ return fixer.removeRange([
+ beforeNodeToken.range[0],
+ node.range[1],
+ ]);
+ }
+
+ return null;
+ };
+
context.report({
loc,
messageId,
data: unusedVar.references.some(ref => ref.isWrite())
? getAssignedMessageData(unusedVar)
: getDefinedMessageData(unusedVar),
+ ...getFixOrSuggest({
+ fixOrSuggest: options.enableAutofixRemoval?.imports
+ ? 'fix'
+ : 'suggest',
+ suggestion: {
+ messageId: 'unusedVarSuggestion',
+ fix: fixer,
+ },
+ }),
});
// If there are no regular declaration, report the first `/*globals*/` comment directive.
diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts
index c9048412d8c0..a68c0f7abee4 100644
--- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars-eslint.test.ts
@@ -385,7 +385,17 @@ function f() {
},
{
code: "import x from 'y';",
- errors: [definedError('x')],
+ errors: [
+ {
+ ...definedError('x'),
+ suggestions: [
+ {
+ messageId: 'unusedVarSuggestion',
+ output: '',
+ },
+ ],
+ },
+ ],
languageOptions: {
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
},
diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts
index 5f40bbcdc3cc..eb0f2d712f2b 100644
--- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts
@@ -53,6 +53,16 @@ export class Foo {}
endLine: 2,
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'ClassDecoratorFactory' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+export class Foo {}
+ `,
+ },
+ ],
},
],
},
@@ -72,6 +82,17 @@ baz();
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Foo' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Bar } from 'foo';
+function baz(): Foo {}
+baz();
+ `,
+ },
+ ],
},
],
},
@@ -91,6 +112,17 @@ console.log(a);
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Nullable' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+const a: string = 'hello';
+console.log(a);
+ `,
+ },
+ ],
},
],
},
@@ -111,6 +143,18 @@ console.log(a);
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'SomeOther' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+const a: Nullable = 'hello';
+console.log(a);
+ `,
+ },
+ ],
},
],
},
@@ -136,6 +180,22 @@ new A();
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Another' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+class A {
+ do = (a: Nullable) => {
+ console.log(a);
+ };
+}
+new A();
+ `,
+ },
+ ],
},
],
},
@@ -160,6 +220,22 @@ new A();
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Another' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+class A {
+ do(a: Nullable) {
+ console.log(a);
+ }
+}
+new A();
+ `,
+ },
+ ],
},
],
},
@@ -184,6 +260,22 @@ new A();
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Another' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+class A {
+ do(): Nullable {
+ return null;
+ }
+}
+new A();
+ `,
+ },
+ ],
},
],
},
@@ -205,6 +297,19 @@ export interface A {
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Another' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+export interface A {
+ do(a: Nullable);
+}
+ `,
+ },
+ ],
},
],
},
@@ -226,6 +331,19 @@ export interface A {
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Nullable' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+export interface A {
+ other: Nullable;
+}
+ `,
+ },
+ ],
},
],
},
@@ -247,6 +365,19 @@ foo();
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Nullable' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+function foo(a: string) {
+ console.log(a);
+}
+foo();
+ `,
+ },
+ ],
},
],
},
@@ -268,6 +399,19 @@ foo();
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Nullable' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+function foo(): string | null {
+ return null;
+}
+foo();
+ `,
+ },
+ ],
},
],
},
@@ -291,6 +435,21 @@ new A();
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'SomeOther' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+import { Another } from 'some';
+class A extends Nullable {
+ other: Nullable;
+}
+new A();
+ `,
+ },
+ ],
},
],
},
@@ -314,6 +473,21 @@ new A();
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'SomeOther' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import { Nullable } from 'nullable';
+
+import { Another } from 'some';
+abstract class A extends Nullable {
+ other: Nullable;
+}
+new A();
+ `,
+ },
+ ],
},
],
},
@@ -353,6 +527,17 @@ export interface Bar extends baz.test {}
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'test' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+import baz from 'baz';
+export interface Bar extends baz.test {}
+ `,
+ },
+ ],
},
],
},
@@ -372,6 +557,17 @@ export class Bar implements baz.test {}
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'test' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+import baz from 'baz';
+export class Bar implements baz.test {}
+ `,
+ },
+ ],
},
],
},
@@ -391,6 +587,17 @@ export class Bar implements baz().test {}
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'test' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+import baz from 'baz';
+export class Bar implements baz().test {}
+ `,
+ },
+ ],
},
],
},
@@ -560,6 +767,20 @@ export const ComponentFoo = () => {
},
line: 3,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'Fragment' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+import React from 'react';
+
+
+export const ComponentFoo = () => {
+ return Foo Foo
;
+};
+ `,
+ },
+ ],
},
],
languageOptions: {
@@ -589,6 +810,20 @@ export const ComponentFoo = () => {
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'React' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+import { h } from 'some-other-jsx-lib';
+
+export const ComponentFoo = () => {
+ return Foo Foo
;
+};
+ `,
+ },
+ ],
},
],
languageOptions: {
@@ -619,6 +854,19 @@ export const ComponentFoo = () => {
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'React' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+
+export const ComponentFoo = () => {
+ return Foo Foo
;
+};
+ `,
+ },
+ ],
},
],
languageOptions: {
@@ -832,17 +1080,17 @@ export = Foo;
],
},
{
- code: `
+ code: noFormat`
namespace Foo {
export const foo = 1;
}
export namespace Bar {
- import TheFoo = Foo;
+import TheFoo = Foo;
}
`,
errors: [
{
- column: 10,
+ column: 8,
data: {
action: 'defined',
additional: '',
@@ -850,6 +1098,20 @@ export namespace Bar {
},
line: 6,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'TheFoo' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+namespace Foo {
+ export const foo = 1;
+}
+export namespace Bar {
+
+}
+ `,
+ },
+ ],
},
],
},
@@ -1715,6 +1977,206 @@ export {};
],
filename: 'foo.d.ts',
},
+ {
+ code: `
+import Unused from 'foo';
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import { Unused } from 'foo';
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import * as Unused from 'foo';
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import { Unused as u1 } from 'foo';
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import type { UnusedType } from 'foo';
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import { type UnusedType } from 'foo';
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import type * as UnusedType from 'foo';
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: noFormat`
+import Unused,{ Used } from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used } from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: noFormat`
+import Used,{ Unused } from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import Used from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import { /* cmt */ Unused, Used } from 'foo';
+export { Used };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { /* cmt */ Used } from 'foo';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import { Used, Unused /* cmt */ } from 'foo';
+export { Used };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used /* cmt */ } from 'foo';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import { Used1 /* cmt1 */, Unused, /* cmt2 */ Used2 } from 'foo';
+export { Used1, Used2 };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1 /* cmt1 */, /* cmt2 */ Used2 } from 'foo';
+export { Used1, Used2 };
+ `,
+ },
+ {
+ code: `
+import type { UnusedType, UsedType } from 'foo';
+export { UsedType };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import type { UsedType } from 'foo';
+export { UsedType };
+ `,
+ },
+ {
+ code: `
+import { type UnusedType, type UsedType } from 'foo';
+export { UsedType };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { type UsedType } from 'foo';
+export { UsedType };
+ `,
+ },
+ {
+ code: `
+import { Unused as u1, Used as u2 } from 'foo';
+export { u2 };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used as u2 } from 'foo';
+export { u2 };
+ `,
+ },
+ {
+ code: `
+import unused = require('foo');
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
],
valid: [
diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unused-vars.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unused-vars.shot
index 0d9872aa6a11..afe9fbe0945b 100644
--- a/packages/eslint-plugin/tests/schema-snapshots/no-unused-vars.shot
+++ b/packages/eslint-plugin/tests/schema-snapshots/no-unused-vars.shot
@@ -33,6 +33,15 @@
"description": "Regular expressions of destructured array variable names to not check for usage.",
"type": "string"
},
+ "enableAutofixRemoval": {
+ "properties": {
+ "imports": {
+ "description": "Whether to enable autofix for removing unused imports.",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
"ignoreClassWithStaticInitBlock": {
"description": "Whether to ignore classes with at least one static initialization block.",
"type": "boolean"
@@ -85,6 +94,11 @@ type Options = [
caughtErrorsIgnorePattern?: string;
/** Regular expressions of destructured array variable names to not check for usage. */
destructuredArrayIgnorePattern?: string;
+ enableAutofixRemoval?: {
+ /** Whether to enable autofix for removing unused imports. */
+ imports?: boolean;
+ [k: string]: unknown;
+ };
/** Whether to ignore classes with at least one static initialization block. */
ignoreClassWithStaticInitBlock?: boolean;
/** Whether to ignore sibling properties in `...` destructurings. */
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/typescript-eslint/typescript-eslint/pull/11243.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy