Skip to content

Commit 4d3ce5f

Browse files
authored
fix(eslint-plugin): [no-unused-vars] properly handle ambient declaration exports (typescript-eslint#2496)
1 parent 916e95a commit 4d3ce5f

File tree

3 files changed

+103
-26
lines changed

3 files changed

+103
-26
lines changed

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"words": [
3939
"Airbnb",
4040
"Airbnb's",
41+
"ambiently",
4142
"ASTs",
4243
"autofix",
4344
"autofixers",

packages/eslint-plugin/src/rules/no-unused-vars.ts

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default util.createRule<Options, MessageIds>({
2828
defaultOptions: [{}],
2929
create(context) {
3030
const rules = baseRule.create(context);
31+
const filename = context.getFilename();
3132

3233
/**
3334
* Gets a list of TS module definitions for a specified variable.
@@ -207,19 +208,93 @@ export default util.createRule<Options, MessageIds>({
207208
}
208209
},
209210

210-
// TODO - this could probably be refined a bit
211-
'*[declare=true] Identifier'(node: TSESTree.Identifier): void {
212-
context.markVariableAsUsed(node.name);
213-
const scope = context.getScope();
214-
const { variableScope } = scope;
215-
if (variableScope !== scope) {
216-
const superVar = variableScope.set.get(node.name);
211+
// declaration file handling
212+
[declarationSelector(AST_NODE_TYPES.Program, true)](
213+
node: DeclarationSelectorNode,
214+
): void {
215+
if (!util.isDefinitionFile(filename)) {
216+
return;
217+
}
218+
markDeclarationChildAsUsed(node);
219+
},
220+
221+
// declared namespace handling
222+
[declarationSelector(
223+
'TSModuleDeclaration[declare = true] > TSModuleBlock',
224+
false,
225+
)](node: DeclarationSelectorNode): void {
226+
markDeclarationChildAsUsed(node);
227+
},
228+
};
229+
230+
type DeclarationSelectorNode =
231+
| TSESTree.TSInterfaceDeclaration
232+
| TSESTree.TSTypeAliasDeclaration
233+
| TSESTree.ClassDeclaration
234+
| TSESTree.FunctionDeclaration
235+
| TSESTree.TSDeclareFunction
236+
| TSESTree.TSEnumDeclaration
237+
| TSESTree.TSModuleDeclaration
238+
| TSESTree.VariableDeclaration;
239+
function declarationSelector(
240+
parent: string,
241+
childDeclare: boolean,
242+
): string {
243+
return [
244+
// Types are ambiently exported
245+
`${parent} > :matches(${[
246+
AST_NODE_TYPES.TSInterfaceDeclaration,
247+
AST_NODE_TYPES.TSTypeAliasDeclaration,
248+
].join(', ')})`,
249+
// Value things are ambiently exported if they are "declare"d
250+
`${parent} > :matches(${[
251+
AST_NODE_TYPES.ClassDeclaration,
252+
AST_NODE_TYPES.TSDeclareFunction,
253+
AST_NODE_TYPES.TSEnumDeclaration,
254+
AST_NODE_TYPES.TSModuleDeclaration,
255+
AST_NODE_TYPES.VariableDeclaration,
256+
].join(', ')})${childDeclare ? '[declare=true]' : ''}`,
257+
].join(', ');
258+
}
259+
function markDeclarationChildAsUsed(node: DeclarationSelectorNode): void {
260+
const identifiers: TSESTree.Identifier[] = [];
261+
switch (node.type) {
262+
case AST_NODE_TYPES.TSInterfaceDeclaration:
263+
case AST_NODE_TYPES.TSTypeAliasDeclaration:
264+
case AST_NODE_TYPES.ClassDeclaration:
265+
case AST_NODE_TYPES.FunctionDeclaration:
266+
case AST_NODE_TYPES.TSDeclareFunction:
267+
case AST_NODE_TYPES.TSEnumDeclaration:
268+
case AST_NODE_TYPES.TSModuleDeclaration:
269+
if (node.id?.type === AST_NODE_TYPES.Identifier) {
270+
identifiers.push(node.id);
271+
}
272+
break;
273+
274+
case AST_NODE_TYPES.VariableDeclaration:
275+
for (const declaration of node.declarations) {
276+
visitPattern(declaration, pattern => {
277+
identifiers.push(pattern);
278+
});
279+
}
280+
break;
281+
}
282+
283+
const scope = context.getScope();
284+
const { variableScope } = scope;
285+
if (variableScope !== scope) {
286+
for (const id of identifiers) {
287+
const superVar = variableScope.set.get(id.name);
217288
if (superVar) {
218289
superVar.eslintUsed = true;
219290
}
220291
}
221-
},
222-
};
292+
} else {
293+
for (const id of identifiers) {
294+
context.markVariableAsUsed(id.name);
295+
}
296+
}
297+
}
223298

224299
function visitPattern(
225300
node: TSESTree.Node,

packages/eslint-plugin/tests/rules/no-unused-vars.test.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -550,20 +550,6 @@ declare namespace Foo {
550550
var baz: string;
551551
}
552552
console.log(Foo);
553-
`,
554-
// https://github.com/typescript-eslint/typescript-eslint/issues/61
555-
`
556-
declare var Foo: {
557-
new (value?: any): Object;
558-
foo(): string;
559-
};
560-
`,
561-
// https://github.com/typescript-eslint/typescript-eslint/issues/106
562-
`
563-
declare class Foo {
564-
constructor(value?: any): Object;
565-
foo(): string;
566-
}
567553
`,
568554
`
569555
import foo from 'foo';
@@ -631,9 +617,6 @@ export default class Foo {
631617
}
632618
`,
633619
`
634-
declare function foo(a: number): void;
635-
`,
636-
`
637620
export function foo(): void;
638621
export function foo(): void;
639622
export function foo(): void {}
@@ -788,6 +771,24 @@ export const StyledPayment = styled.div<StyledPaymentProps>\`\`;
788771
import type { foo } from './a';
789772
export type Bar = typeof foo;
790773
`,
774+
// https://github.com/typescript-eslint/typescript-eslint/issues/2456
775+
{
776+
code: `
777+
interface Foo {}
778+
type Bar = {};
779+
declare class Clazz {}
780+
declare function func();
781+
declare enum Enum {}
782+
declare namespace Name {}
783+
declare const v1 = 1;
784+
declare var v2 = 1;
785+
declare let v3 = 1;
786+
declare const { v4 };
787+
declare const { v4: v5 };
788+
declare const [v6];
789+
`,
790+
filename: 'foo.d.ts',
791+
},
791792
],
792793

793794
invalid: [

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy