Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): [no-unused-var] handle implicit exports in declaration files#10714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
7117fcc
3d2fc3b
f9f18c5
2264c7d
02d1b06
e307ce3
4eb0d58
f84ded5
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -10,6 +10,8 @@ import { | ||
} from '@typescript-eslint/scope-manager'; | ||
import { AST_NODE_TYPES, TSESLint } from '@typescript-eslint/utils'; | ||
import type { MakeRequired } from '../util'; | ||
import { | ||
collectVariables, | ||
createRule, | ||
@@ -58,6 +60,11 @@ type VariableType = | ||
| 'parameter' | ||
| 'variable'; | ||
type ModuleDeclarationWithBody = MakeRequired< | ||
TSESTree.TSModuleDeclaration, | ||
'body' | ||
>; | ||
export default createRule<Options, MessageIds>({ | ||
name: 'no-unused-vars', | ||
meta: { | ||
@@ -144,7 +151,10 @@ export default createRule<Options, MessageIds>({ | ||
}, | ||
defaultOptions: [{}], | ||
create(context, [firstOption]) { | ||
const MODULE_DECL_CACHE = new Map< | ||
ModuleDeclarationWithBody | TSESTree.Program, | ||
boolean | ||
>(); | ||
const options = ((): TranslatedOptions => { | ||
const options: TranslatedOptions = { | ||
@@ -557,40 +567,71 @@ export default createRule<Options, MessageIds>({ | ||
} | ||
return { | ||
//top-leveldeclaration file handling | ||
[ambientDeclarationSelector(AST_NODE_TYPES.Program)]( | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Testing various edge cases, I think that only checking `declare'd module-level values creates some incorrect reports (playground link). Removing this didn't cause any tests to fail, though I could be missing an edge case. | ||
node: DeclarationSelectorNode, | ||
): void { | ||
if (!isDefinitionFile(context.filename)) { | ||
return; | ||
} | ||
const moduleDecl = nullThrows( | ||
node.parent, | ||
NullThrowsReasons.MissingParent, | ||
) as TSESTree.Program; | ||
if (checkForOverridingExportStatements(moduleDecl)) { | ||
return; | ||
} | ||
Comment on lines +578 to +585 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This addition is so the rule would report the following (playground link): // should be reported but doesn'tdeclareclassFoo{}export{} Along with not reporting on the following (playground link): // reported but shouldn'tclassFoo{}// even though this shows up as a compiler error, this isn't marked as used// uncomment the code below and TypeScript would mark this as unused:// export {}; | ||
markDeclarationChildAsUsed(node); | ||
}, | ||
// children of a namespace that is a child of a declared namespace are auto-exported | ||
[ambientDeclarationSelector( | ||
'TSModuleDeclaration[declare = true] > TSModuleBlock TSModuleDeclaration > TSModuleBlock', | ||
)](node: DeclarationSelectorNode): void { | ||
const moduleDecl = nullThrows( | ||
node.parent.parent, | ||
NullThrowsReasons.MissingParent, | ||
) as ModuleDeclarationWithBody; | ||
if (checkForOverridingExportStatements(moduleDecl)) { | ||
return; | ||
} | ||
markDeclarationChildAsUsed(node); | ||
}, | ||
// declared namespace handling | ||
[ambientDeclarationSelector( | ||
'TSModuleDeclaration[declare = true] > TSModuleBlock', | ||
)](node: DeclarationSelectorNode): void { | ||
const moduleDecl = nullThrows( | ||
node.parent.parent, | ||
NullThrowsReasons.MissingParent, | ||
) asModuleDeclarationWithBody; | ||
if (checkForOverridingExportStatements(moduleDecl)) { | ||
return; | ||
} | ||
markDeclarationChildAsUsed(node); | ||
}, | ||
// namespace handling in definition files | ||
[ambientDeclarationSelector('TSModuleDeclaration > TSModuleBlock')]( | ||
node: DeclarationSelectorNode, | ||
): void { | ||
if (!isDefinitionFile(context.filename)) { | ||
return; | ||
} | ||
const moduleDecl = nullThrows( | ||
node.parent.parent, | ||
NullThrowsReasons.MissingParent, | ||
) as ModuleDeclarationWithBody; | ||
if (checkForOverridingExportStatements(moduleDecl)) { | ||
return; | ||
} | ||
@@ -670,21 +711,19 @@ export default createRule<Options, MessageIds>({ | ||
}, | ||
}; | ||
functioncheckForOverridingExportStatements( | ||
node:ModuleDeclarationWithBody |TSESTree.Program, | ||
): boolean { | ||
const cached = MODULE_DECL_CACHE.get(node); | ||
if (cached != null) { | ||
return cached; | ||
} | ||
const body = getStatementsOfNode(node); | ||
if (hasOverridingExportStatement(body)) { | ||
MODULE_DECL_CACHE.set(node, true); | ||
return true; | ||
} | ||
MODULE_DECL_CACHE.set(node, false); | ||
@@ -700,10 +739,7 @@ export default createRule<Options, MessageIds>({ | ||
| TSESTree.TSModuleDeclaration | ||
| TSESTree.TSTypeAliasDeclaration | ||
| TSESTree.VariableDeclaration; | ||
function ambientDeclarationSelector(parent: string): string { | ||
return [ | ||
// Types are ambiently exported | ||
`${parent} > :matches(${[ | ||
@@ -717,7 +753,7 @@ export default createRule<Options, MessageIds>({ | ||
AST_NODE_TYPES.TSEnumDeclaration, | ||
AST_NODE_TYPES.TSModuleDeclaration, | ||
AST_NODE_TYPES.VariableDeclaration, | ||
].join(', ')})`, | ||
].join(', '); | ||
} | ||
function markDeclarationChildAsUsed(node: DeclarationSelectorNode): void { | ||
@@ -774,6 +810,40 @@ export default createRule<Options, MessageIds>({ | ||
}, | ||
}); | ||
function hasOverridingExportStatement( | ||
body: TSESTree.ProgramStatement[], | ||
): boolean { | ||
for (const statement of body) { | ||
if ( | ||
(statement.type === AST_NODE_TYPES.ExportNamedDeclaration && | ||
statement.declaration == null) || | ||
statement.type === AST_NODE_TYPES.ExportAllDeclaration || | ||
statement.type === AST_NODE_TYPES.TSExportAssignment | ||
) { | ||
return true; | ||
} | ||
if ( | ||
statement.type === AST_NODE_TYPES.ExportDefaultDeclaration && | ||
statement.declaration.type === AST_NODE_TYPES.Identifier | ||
) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function getStatementsOfNode( | ||
block: ModuleDeclarationWithBody | TSESTree.Program, | ||
): TSESTree.ProgramStatement[] { | ||
if (block.type === AST_NODE_TYPES.Program) { | ||
return block.body; | ||
} | ||
return block.body.body; | ||
} | ||
/* | ||
###### TODO ###### | ||
Uh oh!
There was an error while loading.Please reload this page.