Content-Length: 96871 | pFad | http://github.com/typescript-eslint/typescript-eslint/pull/11243.patch
thub.com
From e5394b29b407fdc3039a5a271411d599924a607d Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sat, 24 May 2025 00:47:33 +0900
Subject: [PATCH 01/22] test: add testcase
---
.../no-unused-vars/no-unused-vars.test.ts | 149 ++++++++++++++----
1 file changed, 116 insertions(+), 33 deletions(-)
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 5ebc8d9a406b..e9da670e5045 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
@@ -1734,6 +1734,122 @@ export {};
],
filename: 'foo.d.ts',
},
+ {
+ code: `
+import * as Unused from 'foo';
+import * as Used from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ column: 13,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import * as Used from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import Unused1 from 'foo';
+import Unused2, { Used } from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ column: 8,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused1',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 8,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused2',
+ },
+ line: 3,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used } from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import { Unused1 } from 'foo';
+import Used1, { Unused2 } from 'bar';
+import { Used2, Unused3 } from 'baz';
+import Used3, { Unused4, Used4 } from 'foobar';
+export { Used1, Used2, Used3, Used4 };
+ `,
+ errors: [
+ {
+ column: 10,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused1',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused2',
+ },
+ line: 3,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused3',
+ },
+ line: 4,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused4',
+ },
+ line: 5,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import Used1 from 'bar';
+import { Used2 } from 'baz';
+import Used3, { Used4 } from 'foobar';
+export { Used1, Used2, Used3, Used4 };
+ `,
+ },
],
valid: [
@@ -2946,39 +3062,6 @@ declare module 'foo' {
{
code: `
export import Bar = Something.Bar;
-const foo: 1234;
- `,
- filename: 'foo.d.ts',
- },
- {
- code: `
-declare module 'foo' {
- export import Bar = Something.Bar;
- const foo: 1234;
- export const bar: string;
- export namespace NS {
- const baz: 1234;
- }
-}
- `,
- filename: 'foo.d.ts',
- },
- {
- code: `
-export namespace Foo {
- export import Bar = Something.Bar;
- const foo: 1234;
- export const bar: string;
- export namespace NS {
- const baz: 1234;
- }
-}
- `,
- filename: 'foo.d.ts',
- },
- {
- code: `
-export import Bar = Something.Bar;
const foo: 1234;
export const bar: string;
export namespace NS {
From e3399731cb2f1be8d128d636886e9dfb5406a885 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sat, 24 May 2025 00:47:52 +0900
Subject: [PATCH 02/22] feat: test successful logic
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index f2bfa26bb1a8..8d70db0926d7 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -38,6 +38,9 @@ export type Options = [
reportUsedIgnorePattern?: boolean;
vars?: 'all' | 'local';
varsIgnorePattern?: string;
+ enableAutofixRemoval?: {
+ imports: boolean;
+ };
},
];
@@ -52,6 +55,9 @@ interface TranslatedOptions {
reportUsedIgnorePattern: boolean;
vars: 'all' | 'local';
varsIgnorePattern?: RegExp;
+ enableAutofixRemoval?: {
+ imports: boolean;
+ };
}
type VariableType =
@@ -74,6 +80,7 @@ export default createRule({
extendsBaseRule: true,
recommended: 'recommended',
},
+ fixable: 'code',
messages: {
unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
usedIgnoredVar:
@@ -117,6 +124,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 +225,10 @@ export default createRule({
'u',
);
}
+
+ if (firstOption.enableAutofixRemoval) {
+ options.enableAutofixRemoval = firstOption.enableAutofixRemoval;
+ }
}
return options;
@@ -687,6 +708,87 @@ export default createRule({
data: unusedVar.references.some(ref => ref.isWrite())
? getAssignedMessageData(unusedVar)
: getDefinedMessageData(unusedVar),
+ fix:
+ options.enableAutofixRemoval?.imports &&
+ unusedVar.defs.some(
+ d => d.type === DefinitionType.ImportBinding,
+ )
+ ? fixer => {
+ 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 as TSESTree.ImportDeclaration;
+
+ // Remove import declaration line if no specifiers are left
+ if (decl.specifiers.length === 1) {
+ const next = source.getTokenAfter(decl) ?? {
+ range: [decl.range[1], decl.range[1]],
+ };
+ return fixer.removeRange([
+ decl.range[0],
+ next.range[0],
+ ]);
+ }
+
+ // case: remove { unused }
+ const restNamed = decl.specifiers.filter(
+ s =>
+ s === node &&
+ s.type === AST_NODE_TYPES.ImportSpecifier,
+ );
+ if (restNamed.length === 1) {
+ const nextBraceToken = source.getTokenAfter(node);
+ const prevBraceToken = source.getTokenBefore(node);
+ if (
+ nextBraceToken?.value === '}' &&
+ prevBraceToken?.value === '{'
+ ) {
+ // remove comma
+ const prevComma =
+ source.getTokenBefore(prevBraceToken);
+
+ return fixer.removeRange([
+ prevComma?.value === ','
+ ? prevComma.range[0]
+ : prevBraceToken.range[0],
+ nextBraceToken.range[1],
+ ]);
+ }
+ }
+
+ // case: Remove comma after node
+ const nextCommaToken = source.getTokenAfter(node);
+ if (nextCommaToken?.value === ',') {
+ const nextToken = source.getTokenAfter(nextCommaToken, {
+ includeComments: true,
+ });
+
+ return fixer.removeRange([
+ node.range[0],
+ nextToken
+ ? nextToken.range[0]
+ : nextCommaToken.range[1],
+ ]);
+ }
+
+ // case: Remove comma before node
+ const prevCommaToken = source.getTokenBefore(node);
+ if (prevCommaToken?.value === ',') {
+ return fixer.removeRange([
+ prevCommaToken.range[0],
+ node.range[1],
+ ]);
+ }
+ // Remove the current specifier and all tokens until the next specifier
+ return fixer.remove(node);
+ }
+ : undefined,
});
// If there are no regular declaration, report the first `/*globals*/` comment directive.
From 7fc5d69e837f30b98ddc26746150a12d821a5821 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sat, 24 May 2025 22:00:01 +0900
Subject: [PATCH 03/22] chore: generate schema
---
.../tests/schema-snapshots/no-unused-vars.shot | 14 ++++++++++++++
1 file changed, 14 insertions(+)
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. */
From 7b06e7cc51c98fa691226a4e6345412fd347a37b Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Tue, 3 Jun 2025 17:42:37 +0900
Subject: [PATCH 04/22] chore: Revert "test: add testcase"
This reverts commit e5394b29b407fdc3039a5a271411d599924a607d.
---
.../no-unused-vars/no-unused-vars.test.ts | 149 ++++--------------
1 file changed, 33 insertions(+), 116 deletions(-)
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 e9da670e5045..5ebc8d9a406b 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
@@ -1734,122 +1734,6 @@ export {};
],
filename: 'foo.d.ts',
},
- {
- code: `
-import * as Unused from 'foo';
-import * as Used from 'bar';
-export { Used };
- `,
- errors: [
- {
- column: 13,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused',
- },
- line: 2,
- messageId: 'unusedVar',
- },
- ],
- options: [{ enableAutofixRemoval: { imports: true } }],
- output: `
-import * as Used from 'bar';
-export { Used };
- `,
- },
- {
- code: `
-import Unused1 from 'foo';
-import Unused2, { Used } from 'bar';
-export { Used };
- `,
- errors: [
- {
- column: 8,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused1',
- },
- line: 2,
- messageId: 'unusedVar',
- },
- {
- column: 8,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused2',
- },
- line: 3,
- messageId: 'unusedVar',
- },
- ],
- options: [{ enableAutofixRemoval: { imports: true } }],
- output: `
-import { Used } from 'bar';
-export { Used };
- `,
- },
- {
- code: `
-import { Unused1 } from 'foo';
-import Used1, { Unused2 } from 'bar';
-import { Used2, Unused3 } from 'baz';
-import Used3, { Unused4, Used4 } from 'foobar';
-export { Used1, Used2, Used3, Used4 };
- `,
- errors: [
- {
- column: 10,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused1',
- },
- line: 2,
- messageId: 'unusedVar',
- },
- {
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused2',
- },
- line: 3,
- messageId: 'unusedVar',
- },
- {
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused3',
- },
- line: 4,
- messageId: 'unusedVar',
- },
- {
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused4',
- },
- line: 5,
- messageId: 'unusedVar',
- },
- ],
- options: [{ enableAutofixRemoval: { imports: true } }],
- output: `
-import Used1 from 'bar';
-import { Used2 } from 'baz';
-import Used3, { Used4 } from 'foobar';
-export { Used1, Used2, Used3, Used4 };
- `,
- },
],
valid: [
@@ -3062,6 +2946,39 @@ declare module 'foo' {
{
code: `
export import Bar = Something.Bar;
+const foo: 1234;
+ `,
+ filename: 'foo.d.ts',
+ },
+ {
+ code: `
+declare module 'foo' {
+ export import Bar = Something.Bar;
+ const foo: 1234;
+ export const bar: string;
+ export namespace NS {
+ const baz: 1234;
+ }
+}
+ `,
+ filename: 'foo.d.ts',
+ },
+ {
+ code: `
+export namespace Foo {
+ export import Bar = Something.Bar;
+ const foo: 1234;
+ export const bar: string;
+ export namespace NS {
+ const baz: 1234;
+ }
+}
+ `,
+ filename: 'foo.d.ts',
+ },
+ {
+ code: `
+export import Bar = Something.Bar;
const foo: 1234;
export const bar: string;
export namespace NS {
From a2dce5f7e7e12754466b36113d457c301081f603 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Tue, 3 Jun 2025 20:15:48 +0900
Subject: [PATCH 05/22] chore: remove uninteded code changes
---
.../no-unused-vars/no-unused-vars.test.ts | 116 ++++++++++++++++++
1 file changed, 116 insertions(+)
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 5ebc8d9a406b..ac60033d2a5e 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
@@ -1734,6 +1734,122 @@ export {};
],
filename: 'foo.d.ts',
},
+ {
+ code: `
+import * as Unused from 'foo';
+import * as Used from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ column: 13,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import * as Used from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import Unused1 from 'foo';
+import Unused2, { Used } from 'bar';
+export { Used };
+ `,
+ errors: [
+ {
+ column: 8,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused1',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 8,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused2',
+ },
+ line: 3,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used } from 'bar';
+export { Used };
+ `,
+ },
+ {
+ code: `
+import { Unused1 } from 'foo';
+import Used1, { Unused2 } from 'bar';
+import { Used2, Unused3 } from 'baz';
+import Used3, { Unused4, Used4 } from 'foobar';
+export { Used1, Used2, Used3, Used4 };
+ `,
+ errors: [
+ {
+ column: 10,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused1',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused2',
+ },
+ line: 3,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused3',
+ },
+ line: 4,
+ messageId: 'unusedVar',
+ },
+ {
+ column: 17,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'Unused4',
+ },
+ line: 5,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import Used1 from 'bar';
+import { Used2 } from 'baz';
+import Used3, { Used4 } from 'foobar';
+export { Used1, Used2, Used3, Used4 };
+ `,
+ },
],
valid: [
From 855acdf59b8f4d4d68109f858ed0399e43d3786d Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 4 Jun 2025 16:47:22 +0900
Subject: [PATCH 06/22] refactor: simplify conditional statements to increase
converage
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 69 +++++++++----------
1 file changed, 33 insertions(+), 36 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 8d70db0926d7..2df7ad2e5cb8 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -714,6 +714,7 @@ export default createRule({
d => d.type === DefinitionType.ImportBinding,
)
? fixer => {
+ // Find the import statement
const def = unusedVar.defs.find(
d => d.type === DefinitionType.ImportBinding,
);
@@ -725,47 +726,44 @@ export default createRule({
const node = def.node;
const decl = node.parent as TSESTree.ImportDeclaration;
- // Remove import declaration line if no specifiers are left
- if (decl.specifiers.length === 1) {
- const next = source.getTokenAfter(decl) ?? {
- range: [decl.range[1], decl.range[1]],
- };
+ const afterDeclToken = source.getTokenAfter(decl);
+ const afterNodeToken = source.getTokenAfter(node);
+ const beforeNodeToken = source.getTokenBefore(node);
+
+ // Remove import declaration line if no specifiers are left, import unused from 'a';
+ if (decl.specifiers.length === 1 && afterDeclToken) {
return fixer.removeRange([
decl.range[0],
- next.range[0],
+ afterDeclToken.range[0],
]);
}
- // case: remove { unused }
+ // case: remove braces, import used, { unused } from 'a';
const restNamed = decl.specifiers.filter(
s =>
s === node &&
s.type === AST_NODE_TYPES.ImportSpecifier,
);
- if (restNamed.length === 1) {
- const nextBraceToken = source.getTokenAfter(node);
- const prevBraceToken = source.getTokenBefore(node);
- if (
- nextBraceToken?.value === '}' &&
- prevBraceToken?.value === '{'
- ) {
- // remove comma
- const prevComma =
- source.getTokenBefore(prevBraceToken);
-
- return fixer.removeRange([
- prevComma?.value === ','
- ? prevComma.range[0]
- : prevBraceToken.range[0],
- nextBraceToken.range[1],
- ]);
- }
+ if (
+ restNamed.length === 1 &&
+ afterNodeToken?.value === '}' &&
+ beforeNodeToken?.value === '{'
+ ) {
+ // remove comma before braces
+ const prevComma =
+ source.getTokenBefore(beforeNodeToken);
+
+ return fixer.removeRange([
+ prevComma?.value === ','
+ ? prevComma.range[0]
+ : beforeNodeToken.range[0],
+ afterNodeToken.range[1],
+ ]);
}
- // case: Remove comma after node
- const nextCommaToken = source.getTokenAfter(node);
- if (nextCommaToken?.value === ',') {
- const nextToken = source.getTokenAfter(nextCommaToken, {
+ // case: Remove comma after node, import { unused, used } from 'a';
+ if (afterNodeToken?.value === ',') {
+ const nextToken = source.getTokenAfter(afterNodeToken, {
includeComments: true,
});
@@ -773,20 +771,19 @@ export default createRule({
node.range[0],
nextToken
? nextToken.range[0]
- : nextCommaToken.range[1],
+ : afterNodeToken.range[1],
]);
}
- // case: Remove comma before node
- const prevCommaToken = source.getTokenBefore(node);
- if (prevCommaToken?.value === ',') {
+ // case: Remove comma before node, import { used, unused } from 'a';
+ if (beforeNodeToken?.value === ',') {
return fixer.removeRange([
- prevCommaToken.range[0],
+ beforeNodeToken.range[0],
node.range[1],
]);
}
- // Remove the current specifier and all tokens until the next specifier
- return fixer.remove(node);
+
+ return null;
}
: undefined,
});
From 233fb1e9d35059ef603eccea4a62088fe66c8b8b Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 4 Jun 2025 16:48:33 +0900
Subject: [PATCH 07/22] test: add testacase autofixer should do nothing in
import-autofix feature
---
.../no-unused-vars/no-unused-vars.test.ts | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
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 ac60033d2a5e..a972e927cd4e 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
@@ -1850,6 +1850,25 @@ import Used3, { Used4 } from 'foobar';
export { Used1, Used2, Used3, Used4 };
`,
},
+ {
+ code: `
+let unused;
+ `,
+ errors: [
+ {
+ column: 5,
+ data: {
+ action: 'defined',
+ additional: '',
+ varName: 'unused',
+ },
+ line: 2,
+ messageId: 'unusedVar',
+ },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: null,
+ },
],
valid: [
From 622aaf0fb5ae2dae0ab502ce8455d0aa32d96935 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 4 Jun 2025 17:54:54 +0900
Subject: [PATCH 08/22] test: add more testcases
---
.../no-unused-vars/no-unused-vars.test.ts | 67 ++++++++++++++++++-
1 file changed, 66 insertions(+), 1 deletion(-)
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 a972e927cd4e..47a1870704b5 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
@@ -1869,8 +1869,73 @@ let unused;
options: [{ enableAutofixRemoval: { imports: true } }],
output: null,
},
+ {
+ code: `
+import { /* cmt */ Unused1, Used1 } from 'foo';
+export { Used1 };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { /* cmt */ Used1 } from 'foo';
+export { Used1 };
+ `,
+ },
+ {
+ code: `
+import type { UnusedType } from 'foo';
+import { Used1, Unused1 } from 'foo';
+export { Used1 };
+ `,
+ errors: [{ messageId: 'unusedVar' }, { messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1 } from 'foo';
+export { Used1 };
+ `,
+ },
+ {
+ code: `
+import { Unused1 as u1, Used1 as u2 } from 'foo';
+export { u2 };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1 as u2 } from 'foo';
+export { u2 };
+ `,
+ },
+ {
+ code: `
+import {
+ Unused1,
+ Unused2,
+ Unused3,
+ Unused4,
+ Used1,
+ /* cmt */
+ Unused5,
+ Unused6,
+ Used2,
+} from 'foo';
+export { Used1, Used2 };
+ `,
+ errors: [
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1, /* cmt */ Used2 } from 'foo';
+export { Used1, Used2 };
+ `,
+ },
],
-
valid: [
`
import { ClassDecoratorFactory } from 'decorators';
From 122509f5c802b31c245f2e4af16c35837d399ccf Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 4 Jun 2025 17:55:20 +0900
Subject: [PATCH 09/22] refactor: remove duplicate condition validate
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 146 +++++++++---------
1 file changed, 70 insertions(+), 76 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 2df7ad2e5cb8..a0f6d9381636 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -708,84 +708,78 @@ export default createRule({
data: unusedVar.references.some(ref => ref.isWrite())
? getAssignedMessageData(unusedVar)
: getDefinedMessageData(unusedVar),
- fix:
- options.enableAutofixRemoval?.imports &&
- unusedVar.defs.some(
- d => d.type === DefinitionType.ImportBinding,
- )
- ? 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 as TSESTree.ImportDeclaration;
-
- const afterDeclToken = source.getTokenAfter(decl);
- const afterNodeToken = source.getTokenAfter(node);
- const beforeNodeToken = source.getTokenBefore(node);
-
- // Remove import declaration line if no specifiers are left, import unused from 'a';
- if (decl.specifiers.length === 1 && afterDeclToken) {
- return fixer.removeRange([
- decl.range[0],
- afterDeclToken.range[0],
- ]);
- }
-
- // case: remove braces, import used, { unused } from 'a';
- const restNamed = decl.specifiers.filter(
- s =>
- s === node &&
- s.type === AST_NODE_TYPES.ImportSpecifier,
- );
- if (
- restNamed.length === 1 &&
- afterNodeToken?.value === '}' &&
- beforeNodeToken?.value === '{'
- ) {
- // remove comma before braces
- const prevComma =
- source.getTokenBefore(beforeNodeToken);
-
- return fixer.removeRange([
- prevComma?.value === ','
- ? prevComma.range[0]
- : beforeNodeToken.range[0],
- afterNodeToken.range[1],
- ]);
- }
-
- // case: Remove comma after node, import { unused, used } from 'a';
- if (afterNodeToken?.value === ',') {
- const nextToken = source.getTokenAfter(afterNodeToken, {
- includeComments: true,
- });
-
- return fixer.removeRange([
- node.range[0],
- nextToken
- ? nextToken.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],
- ]);
- }
-
+ fix: options.enableAutofixRemoval?.imports
+ ? fixer => {
+ // Find the import statement
+ const def = unusedVar.defs.find(
+ d => d.type === DefinitionType.ImportBinding,
+ );
+ if (!def) {
return null;
}
- : undefined,
+
+ const source = context.sourceCode;
+ const node = def.node;
+ const decl = node.parent as TSESTree.ImportDeclaration;
+
+ const afterDeclToken = source.getTokenAfter(decl);
+ const afterNodeToken = source.getTokenAfter(node);
+ const beforeNodeToken = source.getTokenBefore(node);
+
+ // Remove import declaration line if no specifiers are left, import unused from 'a';
+ if (decl.specifiers.length === 1 && afterDeclToken) {
+ return fixer.removeRange([
+ decl.range[0],
+ afterDeclToken.range[0],
+ ]);
+ }
+
+ // case: remove braces, import used, { unused } from 'a';
+ const restNamed = decl.specifiers.filter(
+ s =>
+ s === node && s.type === AST_NODE_TYPES.ImportSpecifier,
+ );
+ if (
+ restNamed.length === 1 &&
+ afterNodeToken?.value === '}' &&
+ beforeNodeToken?.value === '{'
+ ) {
+ // remove comma before braces
+ const prevComma = source.getTokenBefore(beforeNodeToken);
+
+ return fixer.removeRange([
+ prevComma?.value === ','
+ ? prevComma.range[0]
+ : beforeNodeToken.range[0],
+ afterNodeToken.range[1],
+ ]);
+ }
+
+ // case: Remove comma after node, import { unused, used } from 'a';
+ if (afterNodeToken?.value === ',') {
+ const nextToken = source.getTokenAfter(afterNodeToken, {
+ includeComments: true,
+ });
+
+ return fixer.removeRange([
+ node.range[0],
+ nextToken
+ ? nextToken.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;
+ }
+ : undefined,
});
// If there are no regular declaration, report the first `/*globals*/` comment directive.
From 556ae92f878f612c97418257d84ba4c926424647 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 4 Jun 2025 22:57:05 +0900
Subject: [PATCH 10/22] test: add more test case
---
.../no-unused-vars/no-unused-vars.test.ts | 24 ++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
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 47a1870704b5..5551576926e4 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
@@ -1908,6 +1908,24 @@ export { u2 };
},
{
code: `
+import { Unused1, Unused2, Used1 } from 'foo';
+import { Unused3, Unused4 } from 'bar';
+export { Used1 };
+ `,
+ errors: [
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1 } from 'foo';
+export { Used1 };
+ `,
+ },
+ {
+ code: `
import {
Unused1,
Unused2,
@@ -1931,7 +1949,11 @@ export { Used1, Used2 };
],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used1, /* cmt */ Used2 } from 'foo';
+import {
+ Used1,
+ /* cmt */
+ Used2,
+} from 'foo';
export { Used1, Used2 };
`,
},
From 6d35a7181dd93f311855faf35456c625226f17c5 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Thu, 5 Jun 2025 01:55:29 +0900
Subject: [PATCH 11/22] chore: logic that is currently difficult to implement
is left as TODO
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 36 +++---
.../no-unused-vars/no-unused-vars.test.ts | 105 +++++++++---------
2 files changed, 73 insertions(+), 68 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index a0f6d9381636..ca9ddd616c5f 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -721,16 +721,17 @@ export default createRule({
const source = context.sourceCode;
const node = def.node;
const decl = node.parent as TSESTree.ImportDeclaration;
-
- const afterDeclToken = source.getTokenAfter(decl);
const afterNodeToken = source.getTokenAfter(node);
const beforeNodeToken = source.getTokenBefore(node);
+ const prevBeforeNodeToken = beforeNodeToken
+ ? source.getTokenBefore(beforeNodeToken)
+ : null;
// Remove import declaration line if no specifiers are left, import unused from 'a';
- if (decl.specifiers.length === 1 && afterDeclToken) {
+ if (decl.specifiers.length === 1) {
return fixer.removeRange([
decl.range[0],
- afterDeclToken.range[0],
+ decl.range[1] + 1, // +1 to include "\n"
]);
}
@@ -742,15 +743,11 @@ export default createRule({
if (
restNamed.length === 1 &&
afterNodeToken?.value === '}' &&
- beforeNodeToken?.value === '{'
+ beforeNodeToken?.value === '{' &&
+ prevBeforeNodeToken?.value === ','
) {
- // remove comma before braces
- const prevComma = source.getTokenBefore(beforeNodeToken);
-
return fixer.removeRange([
- prevComma?.value === ','
- ? prevComma.range[0]
- : beforeNodeToken.range[0],
+ prevBeforeNodeToken.range[0],
afterNodeToken.range[1],
]);
}
@@ -761,12 +758,17 @@ export default createRule({
includeComments: true,
});
- return fixer.removeRange([
- node.range[0],
- nextToken
- ? nextToken.range[0]
- : afterNodeToken.range[1],
- ]);
+ if (
+ nextToken?.loc.end.line === afterNodeToken.loc.end.line
+ ) {
+ return fixer.removeRange([
+ node.range[0],
+ nextToken.range[0],
+ ]);
+ }
+
+ // TODO: remove multi-line import
+ return null;
}
// case: Remove comma before node, import { used, unused } from 'a';
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 5551576926e4..3fa478fd405d 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
@@ -1906,57 +1906,60 @@ import { Used1 as u2 } from 'foo';
export { u2 };
`,
},
- {
- code: `
-import { Unused1, Unused2, Used1 } from 'foo';
-import { Unused3, Unused4 } from 'bar';
-export { Used1 };
- `,
- errors: [
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- ],
- options: [{ enableAutofixRemoval: { imports: true } }],
- output: `
-import { Used1 } from 'foo';
-export { Used1 };
- `,
- },
- {
- code: `
-import {
- Unused1,
- Unused2,
- Unused3,
- Unused4,
- Used1,
- /* cmt */
- Unused5,
- Unused6,
- Used2,
-} from 'foo';
-export { Used1, Used2 };
- `,
- errors: [
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- ],
- options: [{ enableAutofixRemoval: { imports: true } }],
- output: `
-import {
- Used1,
- /* cmt */
- Used2,
-} from 'foo';
-export { Used1, Used2 };
- `,
- },
+ // TODO: Logic to remove multiple unused vars in one-line
+ // {
+ // code: `
+ // import { Unused1, Unused2, Used1 } from 'foo';
+ // import { Unused3, Unused4 } from 'bar';
+ // export { Used1 };
+ // `,
+ // errors: [
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // ],
+ // options: [{ enableAutofixRemoval: { imports: true } }],
+ // output: `
+ // import { Used1 } from 'foo';
+ // export { Used1 };
+ // `,
+ // },
+
+ // TODO: multi-line import
+ // {
+ // code: `
+ // import {
+ // Unused1,
+ // Unused2,
+ // Unused3,
+ // Unused4,
+ // Used1,
+ // /* cmt */
+ // Unused5,
+ // Unused6,
+ // Used2,
+ // } from 'foo';
+ // export { Used1, Used2 };
+ // `,
+ // errors: [
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // { messageId: 'unusedVar' },
+ // ],
+ // options: [{ enableAutofixRemoval: { imports: true } }],
+ // output: `
+ // import {
+ // Used1,
+ // /* cmt */
+ // Used2,
+ // } from 'foo';
+ // export { Used1, Used2 };
+ // `,
+ // },
],
valid: [
`
From d9f16097c99c873cde131a1809c7a6678c96fc3b Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Thu, 5 Jun 2025 15:15:02 +0900
Subject: [PATCH 12/22] chore: add line for test category
---
.../tests/rules/no-unused-vars/no-unused-vars.test.ts | 1 +
1 file changed, 1 insertion(+)
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 3fa478fd405d..ef359795176d 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
@@ -1961,6 +1961,7 @@ export { u2 };
// `,
// },
],
+
valid: [
`
import { ClassDecoratorFactory } from 'decorators';
From 11ac4fa7833a17b0883875952b82be34ad39126d Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Fri, 6 Jun 2025 18:20:38 +0900
Subject: [PATCH 13/22] fix: remove type casting and support
TSImportEqulasDeclaration node
---
packages/eslint-plugin/src/rules/no-unused-vars.ts | 7 ++++++-
.../rules/no-unused-vars/no-unused-vars.test.ts | 14 ++++++++++++++
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index ca9ddd616c5f..ad3547197823 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -720,7 +720,12 @@ export default createRule({
const source = context.sourceCode;
const node = def.node;
- const decl = node.parent as TSESTree.ImportDeclaration;
+ 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
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 ef359795176d..95faf938ca9f 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
@@ -1906,6 +1906,20 @@ import { Used1 as u2 } from 'foo';
export { u2 };
`,
},
+ {
+ code: `
+import x = require('foo');
+import y = require('bar');
+export { y };
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+import y = require('bar');
+export { y };
+ `,
+ },
// TODO: Logic to remove multiple unused vars in one-line
// {
// code: `
From 633d2c8c00d7882f7e98331bec09d24fd0502288 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Fri, 6 Jun 2025 23:55:21 +0900
Subject: [PATCH 14/22] test: simplify test case with noFormat
---
.../no-unused-vars/no-unused-vars.test.ts | 150 +++++++-----------
1 file changed, 55 insertions(+), 95 deletions(-)
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 95faf938ca9f..b1a99165757b 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
@@ -1735,20 +1735,12 @@ export {};
filename: 'foo.d.ts',
},
{
- code: `
-import * as Unused from 'foo';
-import * as Used from 'bar';
+ code: noFormat`
+import * as Unused from 'foo';import * as Used from 'bar';
export { Used };
`,
errors: [
{
- column: 13,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused',
- },
- line: 2,
messageId: 'unusedVar',
},
],
@@ -1759,91 +1751,51 @@ export { Used };
`,
},
{
- code: `
+ code: noFormat`
import Unused1 from 'foo';
-import Unused2, { Used } from 'bar';
+import Unused2,{ Used } from 'bar';
export { Used };
`,
errors: [
{
- column: 8,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused1',
- },
- line: 2,
messageId: 'unusedVar',
},
{
- column: 8,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused2',
- },
- line: 3,
messageId: 'unusedVar',
},
],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
+
import { Used } from 'bar';
export { Used };
`,
},
{
- code: `
+ code: noFormat`
import { Unused1 } from 'foo';
import Used1, { Unused2 } from 'bar';
import { Used2, Unused3 } from 'baz';
-import Used3, { Unused4, Used4 } from 'foobar';
+import Used3, { Unused4,Used4 } from 'foobar';
export { Used1, Used2, Used3, Used4 };
`,
errors: [
{
- column: 10,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused1',
- },
- line: 2,
messageId: 'unusedVar',
},
{
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused2',
- },
- line: 3,
messageId: 'unusedVar',
},
{
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused3',
- },
- line: 4,
messageId: 'unusedVar',
},
{
- column: 17,
- data: {
- action: 'defined',
- additional: '',
- varName: 'Unused4',
- },
- line: 5,
messageId: 'unusedVar',
},
],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
+
import Used1 from 'bar';
import { Used2 } from 'baz';
import Used3, { Used4 } from 'foobar';
@@ -1877,14 +1829,13 @@ export { Used1 };
errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { /* cmt */ Used1 } from 'foo';
+import { /* cmt */ Used1 } from 'foo';
export { Used1 };
`,
},
{
- code: `
-import type { UnusedType } from 'foo';
-import { Used1, Unused1 } from 'foo';
+ code: noFormat`
+import type { UnusedType } from 'foo';import { Used1, Unused1 } from 'foo';
export { Used1 };
`,
errors: [{ messageId: 'unusedVar' }, { messageId: 'unusedVar' }],
@@ -1902,7 +1853,7 @@ export { u2 };
errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used1 as u2 } from 'foo';
+import { Used1 as u2 } from 'foo';
export { u2 };
`,
},
@@ -1925,40 +1876,13 @@ export { y };
// code: `
// import { Unused1, Unused2, Used1 } from 'foo';
// import { Unused3, Unused4 } from 'bar';
- // export { Used1 };
+ // export { Used1, Used2 };
// `,
// errors: [
// { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
- // ],
- // options: [{ enableAutofixRemoval: { imports: true } }],
- // output: `
- // import { Used1 } from 'foo';
- // export { Used1 };
- // `,
- // },
-
- // TODO: multi-line import
- // {
- // code: `
- // import {
- // Unused1,
- // Unused2,
- // Unused3,
- // Unused4,
- // Used1,
- // /* cmt */
- // Unused5,
- // Unused6,
- // Used2,
- // } from 'foo';
- // export { Used1, Used2 };
- // `,
- // errors: [
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
// { messageId: 'unusedVar' },
@@ -1966,14 +1890,50 @@ export { y };
// ],
// options: [{ enableAutofixRemoval: { imports: true } }],
// output: `
- // import {
- // Used1,
- // /* cmt */
- // Used2,
- // } from 'foo';
+ // import { Used1,Used2 } from 'foo';
+
// export { Used1, Used2 };
- // `,
+ // `,
// },
+ {
+ code: noFormat`
+import {
+Unused1,
+Unused2,
+Unused3,
+Unused4,
+Used1,
+/* cmt */
+Unused5,
+Unused6,
+Used2,
+} from 'foo';
+export { Used1, Used2 };
+ `,
+ errors: [
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: noFormat`
+import {
+
+
+
+
+Used1,
+/* cmt */
+
+
+Used2,
+} from 'foo';
+export { Used1, Used2 };
+ `,
+ },
],
valid: [
From 9f76441a8a109bbc4d63317f95eeb321769ef300 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Fri, 6 Jun 2025 23:56:00 +0900
Subject: [PATCH 15/22] fix: remove format-related logic
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 24 ++++---------------
1 file changed, 5 insertions(+), 19 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index ad3547197823..4d0456d4a46c 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -734,10 +734,7 @@ export default createRule({
// Remove import declaration line if no specifiers are left, import unused from 'a';
if (decl.specifiers.length === 1) {
- return fixer.removeRange([
- decl.range[0],
- decl.range[1] + 1, // +1 to include "\n"
- ]);
+ return fixer.removeRange([decl.range[0], decl.range[1]]);
}
// case: remove braces, import used, { unused } from 'a';
@@ -759,21 +756,10 @@ export default createRule({
// case: Remove comma after node, import { unused, used } from 'a';
if (afterNodeToken?.value === ',') {
- const nextToken = source.getTokenAfter(afterNodeToken, {
- includeComments: true,
- });
-
- if (
- nextToken?.loc.end.line === afterNodeToken.loc.end.line
- ) {
- return fixer.removeRange([
- node.range[0],
- nextToken.range[0],
- ]);
- }
-
- // TODO: remove multi-line import
- return null;
+ return fixer.removeRange([
+ node.range[0],
+ afterNodeToken.range[1],
+ ]);
}
// case: Remove comma before node, import { used, unused } from 'a';
From 2c8b7406a98205f95f5413cb0522f23c824a7abf Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sun, 8 Jun 2025 22:10:23 +0900
Subject: [PATCH 16/22] feat: add suggestion for enableAUtofixRemoval
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 148 ++++++++++--------
1 file changed, 82 insertions(+), 66 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 4d0456d4a46c..8a8a538b851d 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'
@@ -81,8 +86,12 @@ export default createRule({
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:
@@ -702,77 +711,84 @@ 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 line if no specifiers are left, import unused from 'a';
+ if (decl.specifiers.length === 1) {
+ return fixer.removeRange([decl.range[0], decl.range[1]]);
+ }
+
+ // case: remove braces, import used, { unused } from 'a';
+ const restNamed = decl.specifiers.filter(
+ s => s === node && s.type === AST_NODE_TYPES.ImportSpecifier,
+ );
+ if (
+ restNamed.length === 1 &&
+ afterNodeToken?.value === '}' &&
+ beforeNodeToken?.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),
- fix: options.enableAutofixRemoval?.imports
- ? 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 line if no specifiers are left, import unused from 'a';
- if (decl.specifiers.length === 1) {
- return fixer.removeRange([decl.range[0], decl.range[1]]);
- }
-
- // case: remove braces, import used, { unused } from 'a';
- const restNamed = decl.specifiers.filter(
- s =>
- s === node && s.type === AST_NODE_TYPES.ImportSpecifier,
- );
- if (
- restNamed.length === 1 &&
- afterNodeToken?.value === '}' &&
- beforeNodeToken?.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;
- }
- : undefined,
+ ...getFixOrSuggest({
+ fixOrSuggest: options.enableAutofixRemoval?.imports
+ ? 'fix'
+ : 'suggest',
+ suggestion: {
+ messageId: 'unusedVarSuggestion',
+ fix: fixer,
+ },
+ }),
});
// If there are no regular declaration, report the first `/*globals*/` comment directive.
From 9a15d4955b77dcd35a50e1e8f93b6517336cb58a Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sun, 8 Jun 2025 23:20:13 +0900
Subject: [PATCH 17/22] test: add suggestion effected by import statement
---
.../no-unused-vars/no-unused-vars.test.ts | 279 +++++++++++++++++-
1 file changed, 276 insertions(+), 3 deletions(-)
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 b1a99165757b..a9e9b7874950 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 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 {}
+ `,
+ },
+ ],
},
],
},
@@ -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 {}
+ `,
+ },
+ ],
},
],
},
@@ -410,6 +617,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 {}
+ `,
+ },
+ ],
},
],
},
@@ -579,6 +797,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: {
@@ -608,6 +840,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: {
@@ -638,6 +884,19 @@ export const ComponentFoo = () => {
},
line: 2,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'React' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+
+
+export const ComponentFoo = () => {
+ return Foo Foo
;
+};
+ `,
+ },
+ ],
},
],
languageOptions: {
@@ -851,17 +1110,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: '',
@@ -869,6 +1128,20 @@ export namespace Bar {
},
line: 6,
messageId: 'unusedVar',
+ suggestions: [
+ {
+ data: { varName: 'TheFoo' },
+ messageId: 'unusedVarSuggestion',
+ output: `
+namespace Foo {
+ export const foo = 1;
+}
+export namespace Bar {
+
+}
+ `,
+ },
+ ],
},
],
},
From 6de905049e8ad86b03abe918b0cd923ac5aa09c0 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Sun, 8 Jun 2025 23:43:17 +0900
Subject: [PATCH 18/22] test: add suggestion for no-unused-vars-eslint test
---
.../no-unused-vars/no-unused-vars-eslint.test.ts | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
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' },
},
From a177e5c2669a0eb206b07ddbcc27f12fe79385b9 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 11 Jun 2025 18:02:54 +0900
Subject: [PATCH 19/22] feat: remove all unused specifers at once if no used
specifers in decl
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 77 +++++++++++++++++--
1 file changed, 69 insertions(+), 8 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 8a8a538b851d..9eb7cc7ad4d6 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -671,6 +671,63 @@ export default createRule({
// collect
'Program:exit'(programNode): void {
const unusedVars = collectUnusedVariables();
+ /**
+ * metadata of import declaration include unused specifiers
+ */
+ interface UnusedDecl {
+ /**
+ * Ranges of all unused default & named specifiers
+ */
+ unusedNodeRanges: TSESLint.AST.Range[];
+ /**
+ * Range of named imports include braces
+ */
+ namedRange?: TSESLint.AST.Range;
+ /**
+ * Range of import declaration
+ */
+ declRange: TSESLint.AST.Range;
+ }
+
+ // Structuring unused specifiers for each decl
+ const unusedDecl = new Map();
+ for (const unusedVar of unusedVars) {
+ const def = unusedVar.defs.find(
+ d => d.type === DefinitionType.ImportBinding,
+ );
+ if (!def) {
+ continue;
+ }
+ const node = def.node;
+ const decl = node.parent;
+ if (decl.type !== AST_NODE_TYPES.ImportDeclaration) {
+ continue;
+ }
+ const mapKey = decl.range.toString();
+ const afterNodeToken = context.sourceCode.getTokenAfter(node);
+ const beforeNodeToken = context.sourceCode.getTokenBefore(node);
+
+ const prevValue = unusedDecl.get(mapKey);
+ // get range of { A, B, C, ... , D }
+ const namedRange = prevValue?.namedRange ?? [0, 0];
+ if (beforeNodeToken?.value === '{') {
+ namedRange[0] = beforeNodeToken.range[0];
+ }
+ if (afterNodeToken?.value === '}') {
+ namedRange[1] = afterNodeToken.range[1];
+ }
+
+ unusedDecl.set(mapKey, {
+ declRange: decl.range,
+ namedRange: namedRange.every(v => v === 0)
+ ? prevValue?.namedRange
+ : namedRange,
+ unusedNodeRanges: [
+ ...(prevValue?.unusedNodeRanges ?? []),
+ node.range,
+ ],
+ });
+ }
for (const unusedVar of unusedVars) {
// Report the first declaration.
@@ -734,24 +791,28 @@ export default createRule({
? source.getTokenBefore(beforeNodeToken)
: null;
- // Remove import declaration line if no specifiers are left, import unused from 'a';
- if (decl.specifiers.length === 1) {
- return fixer.removeRange([decl.range[0], decl.range[1]]);
+ const declInfo = unusedDecl.get(decl.range.toString());
+
+ // Remove import declaration if no used specifiers are left, import unused from 'a';
+ if (
+ declInfo &&
+ decl.specifiers.length === declInfo.unusedNodeRanges.length
+ ) {
+ return fixer.removeRange(declInfo.declRange);
}
// case: remove braces, import used, { unused } from 'a';
const restNamed = decl.specifiers.filter(
- s => s === node && s.type === AST_NODE_TYPES.ImportSpecifier,
+ s => s.type === AST_NODE_TYPES.ImportSpecifier,
);
if (
- restNamed.length === 1 &&
- afterNodeToken?.value === '}' &&
- beforeNodeToken?.value === '{' &&
+ declInfo?.namedRange &&
+ restNamed.length === declInfo.unusedNodeRanges.length &&
prevBeforeNodeToken?.value === ','
) {
return fixer.removeRange([
prevBeforeNodeToken.range[0],
- afterNodeToken.range[1],
+ declInfo.namedRange[1],
]);
}
From 1a054652aaffe47bd479a92e6b1794f42f679606 Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 11 Jun 2025 18:03:14 +0900
Subject: [PATCH 20/22] test: add test
---
.../no-unused-vars/no-unused-vars.test.ts | 45 +++++++++----------
1 file changed, 21 insertions(+), 24 deletions(-)
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 a9e9b7874950..4ba98c274cd0 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
@@ -2144,30 +2144,27 @@ import y = require('bar');
export { y };
`,
},
- // TODO: Logic to remove multiple unused vars in one-line
- // {
- // code: `
- // import { Unused1, Unused2, Used1 } from 'foo';
- // import { Unused3, Unused4 } from 'bar';
- // export { Used1, Used2 };
- // `,
- // errors: [
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // { messageId: 'unusedVar' },
- // ],
- // options: [{ enableAutofixRemoval: { imports: true } }],
- // output: `
- // import { Used1,Used2 } from 'foo';
-
- // export { Used1, Used2 };
- // `,
- // },
+ {
+ code: `
+import { Unused1, Unused2, Unused3, Used1 } from 'foo';
+import Used2, { Unused4, Unused5, Unused6 } from 'bar';
+export { Used1, Used2 };
+ `,
+ errors: [
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ { messageId: 'unusedVar' },
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+import { Used1 } from 'foo';
+import Used2 from 'bar';
+export { Used1, Used2 };
+ `,
+ },
{
code: noFormat`
import {
From 90c2d9a7f74e141d78665f171285d9414b1ebf5a Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Wed, 11 Jun 2025 21:28:23 +0900
Subject: [PATCH 21/22] chore: resolve real conflict
---
.../no-unused-vars/no-unused-vars.test.ts | 30 -------------------
1 file changed, 30 deletions(-)
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 4ba98c274cd0..f260ef9c0761 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
@@ -545,36 +545,6 @@ export interface Bar extends baz.test {}
code: `
import test from 'test';
import baz from 'baz';
-export interface Bar extends baz().test {}
- `,
- errors: [
- {
- column: 8,
- data: {
- action: 'defined',
- additional: '',
- varName: 'test',
- },
- line: 2,
- messageId: 'unusedVar',
- suggestions: [
- {
- data: { varName: 'test' },
- messageId: 'unusedVarSuggestion',
- output: `
-
-import baz from 'baz';
-export interface Bar extends baz().test {}
- `,
- },
- ],
- },
- ],
- },
- {
- code: `
-import test from 'test';
-import baz from 'baz';
export class Bar implements baz.test {}
`,
errors: [
From eb3fd2b2443f45b4dbb3d766e01ea1b0d453f67e Mon Sep 17 00:00:00 2001
From: nayounsang
Date: Mon, 14 Jul 2025 15:41:52 +0900
Subject: [PATCH 22/22] fix: simplyfy logic and modify tc to fit unit tests
---
.../eslint-plugin/src/rules/no-unused-vars.ts | 76 +------
.../no-unused-vars/no-unused-vars.test.ts | 213 +++++++++---------
2 files changed, 115 insertions(+), 174 deletions(-)
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 9eb7cc7ad4d6..6b7873f1303e 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -671,63 +671,6 @@ export default createRule({
// collect
'Program:exit'(programNode): void {
const unusedVars = collectUnusedVariables();
- /**
- * metadata of import declaration include unused specifiers
- */
- interface UnusedDecl {
- /**
- * Ranges of all unused default & named specifiers
- */
- unusedNodeRanges: TSESLint.AST.Range[];
- /**
- * Range of named imports include braces
- */
- namedRange?: TSESLint.AST.Range;
- /**
- * Range of import declaration
- */
- declRange: TSESLint.AST.Range;
- }
-
- // Structuring unused specifiers for each decl
- const unusedDecl = new Map();
- for (const unusedVar of unusedVars) {
- const def = unusedVar.defs.find(
- d => d.type === DefinitionType.ImportBinding,
- );
- if (!def) {
- continue;
- }
- const node = def.node;
- const decl = node.parent;
- if (decl.type !== AST_NODE_TYPES.ImportDeclaration) {
- continue;
- }
- const mapKey = decl.range.toString();
- const afterNodeToken = context.sourceCode.getTokenAfter(node);
- const beforeNodeToken = context.sourceCode.getTokenBefore(node);
-
- const prevValue = unusedDecl.get(mapKey);
- // get range of { A, B, C, ... , D }
- const namedRange = prevValue?.namedRange ?? [0, 0];
- if (beforeNodeToken?.value === '{') {
- namedRange[0] = beforeNodeToken.range[0];
- }
- if (afterNodeToken?.value === '}') {
- namedRange[1] = afterNodeToken.range[1];
- }
-
- unusedDecl.set(mapKey, {
- declRange: decl.range,
- namedRange: namedRange.every(v => v === 0)
- ? prevValue?.namedRange
- : namedRange,
- unusedNodeRanges: [
- ...(prevValue?.unusedNodeRanges ?? []),
- node.range,
- ],
- });
- }
for (const unusedVar of unusedVars) {
// Report the first declaration.
@@ -791,28 +734,23 @@ export default createRule({
? source.getTokenBefore(beforeNodeToken)
: null;
- const declInfo = unusedDecl.get(decl.range.toString());
-
- // Remove import declaration if no used specifiers are left, import unused from 'a';
- if (
- declInfo &&
- decl.specifiers.length === declInfo.unusedNodeRanges.length
- ) {
- return fixer.removeRange(declInfo.declRange);
+ // Remove import declaration if no used specifiers are left
+ if (decl.specifiers.length === 1) {
+ return fixer.removeRange(decl.range);
}
- // case: remove braces, import used, { unused } from 'a';
+ // case: remove braces if no used named specifiers are left
const restNamed = decl.specifiers.filter(
s => s.type === AST_NODE_TYPES.ImportSpecifier,
);
if (
- declInfo?.namedRange &&
- restNamed.length === declInfo.unusedNodeRanges.length &&
+ restNamed.length === 1 &&
+ afterNodeToken?.value === '}' &&
prevBeforeNodeToken?.value === ','
) {
return fixer.removeRange([
prevBeforeNodeToken.range[0],
- declInfo.namedRange[1],
+ afterNodeToken.range[1],
]);
}
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 f260ef9c0761..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
@@ -1978,9 +1978,8 @@ export {};
filename: 'foo.d.ts',
},
{
- code: noFormat`
-import * as Unused from 'foo';import * as Used from 'bar';
-export { Used };
+ code: `
+import Unused from 'foo';
`,
errors: [
{
@@ -1989,189 +1988,193 @@ export { Used };
],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import * as Used from 'bar';
-export { Used };
+
`,
},
{
- code: noFormat`
-import Unused1 from 'foo';
-import Unused2,{ Used } from 'bar';
-export { Used };
+ code: `
+import { Unused } from 'foo';
`,
errors: [
{
messageId: 'unusedVar',
},
- {
- messageId: 'unusedVar',
- },
],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used } from 'bar';
-export { Used };
`,
},
{
- code: noFormat`
-import { Unused1 } from 'foo';
-import Used1, { Unused2 } from 'bar';
-import { Used2, Unused3 } from 'baz';
-import Used3, { Unused4,Used4 } from 'foobar';
-export { Used1, Used2, Used3, Used4 };
+ code: `
+import * as Unused from 'foo';
`,
errors: [
{
messageId: 'unusedVar',
},
+ ],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
+
+ `,
+ },
+ {
+ code: `
+import { Unused as u1 } from 'foo';
+ `,
+ errors: [
{
messageId: 'unusedVar',
},
- {
- 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 Used1 from 'bar';
-import { Used2 } from 'baz';
-import Used3, { Used4 } from 'foobar';
-export { Used1, Used2, Used3, Used4 };
+import { Used } from 'bar';
+export { Used };
`,
},
{
- code: `
-let unused;
+ code: noFormat`
+import Used,{ Unused } from 'bar';
+export { Used };
`,
errors: [
{
- column: 5,
- data: {
- action: 'defined',
- additional: '',
- varName: 'unused',
- },
- line: 2,
messageId: 'unusedVar',
},
],
options: [{ enableAutofixRemoval: { imports: true } }],
- output: null,
+ output: `
+import Used from 'bar';
+export { Used };
+ `,
},
{
code: `
-import { /* cmt */ Unused1, Used1 } from 'foo';
-export { Used1 };
+import { /* cmt */ Unused, Used } from 'foo';
+export { Used };
`,
errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { /* cmt */ Used1 } from 'foo';
-export { Used1 };
+import { /* cmt */ Used } from 'foo';
+export { Used };
`,
},
{
- code: noFormat`
-import type { UnusedType } from 'foo';import { Used1, Unused1 } from 'foo';
-export { Used1 };
+ code: `
+import { Used, Unused /* cmt */ } from 'foo';
+export { Used };
`,
- errors: [{ messageId: 'unusedVar' }, { messageId: 'unusedVar' }],
+ errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used1 } from 'foo';
-export { Used1 };
+import { Used /* cmt */ } from 'foo';
+export { Used };
`,
},
{
code: `
-import { Unused1 as u1, Used1 as u2 } from 'foo';
-export { u2 };
+import { Used1 /* cmt1 */, Unused, /* cmt2 */ Used2 } from 'foo';
+export { Used1, Used2 };
`,
errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used1 as u2 } from 'foo';
-export { u2 };
+import { Used1 /* cmt1 */, /* cmt2 */ Used2 } from 'foo';
+export { Used1, Used2 };
`,
},
{
code: `
-import x = require('foo');
-import y = require('bar');
-export { y };
+import type { UnusedType, UsedType } from 'foo';
+export { UsedType };
`,
errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-
-import y = require('bar');
-export { y };
+import type { UsedType } from 'foo';
+export { UsedType };
`,
},
{
code: `
-import { Unused1, Unused2, Unused3, Used1 } from 'foo';
-import Used2, { Unused4, Unused5, Unused6 } from 'bar';
-export { Used1, Used2 };
+import { type UnusedType, type UsedType } from 'foo';
+export { UsedType };
`,
- errors: [
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- ],
+ errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
output: `
-import { Used1 } from 'foo';
-import Used2 from 'bar';
-export { Used1, Used2 };
+import { type UsedType } from 'foo';
+export { UsedType };
`,
},
{
- code: noFormat`
-import {
-Unused1,
-Unused2,
-Unused3,
-Unused4,
-Used1,
-/* cmt */
-Unused5,
-Unused6,
-Used2,
-} from 'foo';
-export { Used1, Used2 };
+ code: `
+import { Unused as u1, Used as u2 } from 'foo';
+export { u2 };
`,
- errors: [
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- { messageId: 'unusedVar' },
- ],
+ errors: [{ messageId: 'unusedVar' }],
options: [{ enableAutofixRemoval: { imports: true } }],
- output: noFormat`
-import {
-
-
-
-
-Used1,
-/* cmt */
-
+ output: `
+import { Used as u2 } from 'foo';
+export { u2 };
+ `,
+ },
+ {
+ code: `
+import unused = require('foo');
+ `,
+ errors: [{ messageId: 'unusedVar' }],
+ options: [{ enableAutofixRemoval: { imports: true } }],
+ output: `
-Used2,
-} from 'foo';
-export { Used1, Used2 };
`,
},
],
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/typescript-eslint/typescript-eslint/pull/11243.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy