UNPKG

24.1 kBJavaScriptView Raw
1"use strict";
2var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3 if (kind === "m") throw new TypeError("Private method is not writable");
4 if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5 if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6 return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7};
8var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9 if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10 if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11 return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12};
13var _UnusedVarsVisitor_scopeManager;
14Object.defineProperty(exports, "__esModule", { value: true });
15exports.collectUnusedVariables = void 0;
16const utils_1 = require("@typescript-eslint/utils");
17const scope_manager_1 = require("@typescript-eslint/scope-manager");
18const Visitor_1 = require("@typescript-eslint/scope-manager/dist/referencer/Visitor");
19class UnusedVarsVisitor extends Visitor_1.Visitor {
20 // readonly #unusedVariables = new Set<TSESLint.Scope.Variable>();
21 constructor(context) {
22 super({
23 visitChildrenEvenIfSelectorExists: true,
24 });
25 _UnusedVarsVisitor_scopeManager.set(this, void 0);
26 //#endregion HELPERS
27 //#region VISITORS
28 // NOTE - This is a simple visitor - meaning it does not support selectors
29 this.ClassDeclaration = this.visitClass;
30 this.ClassExpression = this.visitClass;
31 this.FunctionDeclaration = this.visitFunction;
32 this.FunctionExpression = this.visitFunction;
33 this.MethodDefinition = this.visitSetter;
34 this.Property = this.visitSetter;
35 this.TSCallSignatureDeclaration = this.visitFunctionTypeSignature;
36 this.TSConstructorType = this.visitFunctionTypeSignature;
37 this.TSConstructSignatureDeclaration = this.visitFunctionTypeSignature;
38 this.TSDeclareFunction = this.visitFunctionTypeSignature;
39 this.TSEmptyBodyFunctionExpression = this.visitFunctionTypeSignature;
40 this.TSFunctionType = this.visitFunctionTypeSignature;
41 this.TSMethodSignature = this.visitFunctionTypeSignature;
42 __classPrivateFieldSet(this, _UnusedVarsVisitor_scopeManager, utils_1.ESLintUtils.nullThrows(context.getSourceCode().scopeManager, 'Missing required scope manager'), "f");
43 }
44 static collectUnusedVariables(context) {
45 const program = context.getSourceCode().ast;
46 const cached = this.RESULTS_CACHE.get(program);
47 if (cached) {
48 return cached;
49 }
50 const visitor = new this(context);
51 visitor.visit(program);
52 const unusedVars = visitor.collectUnusedVariables(visitor.getScope(program));
53 this.RESULTS_CACHE.set(program, unusedVars);
54 return unusedVars;
55 }
56 collectUnusedVariables(scope, unusedVariables = new Set()) {
57 for (const variable of scope.variables) {
58 if (
59 // skip function expression names,
60 scope.functionExpressionScope ||
61 // variables marked with markVariableAsUsed(),
62 variable.eslintUsed ||
63 // implicit lib variables (from @typescript-eslint/scope-manager),
64 variable instanceof scope_manager_1.ImplicitLibVariable ||
65 // basic exported variables
66 isExported(variable) ||
67 // variables implicitly exported via a merged declaration
68 isMergableExported(variable) ||
69 // used variables
70 isUsedVariable(variable)) {
71 continue;
72 }
73 unusedVariables.add(variable);
74 }
75 for (const childScope of scope.childScopes) {
76 this.collectUnusedVariables(childScope, unusedVariables);
77 }
78 return unusedVariables;
79 }
80 //#region HELPERS
81 getScope(currentNode) {
82 // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
83 const inner = currentNode.type !== utils_1.AST_NODE_TYPES.Program;
84 let node = currentNode;
85 while (node) {
86 const scope = __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").acquire(node, inner);
87 if (scope) {
88 if (scope.type === 'function-expression-name') {
89 return scope.childScopes[0];
90 }
91 return scope;
92 }
93 node = node.parent;
94 }
95 return __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").scopes[0];
96 }
97 markVariableAsUsed(variableOrIdentifierOrName, parent) {
98 if (typeof variableOrIdentifierOrName !== 'string' &&
99 !('type' in variableOrIdentifierOrName)) {
100 variableOrIdentifierOrName.eslintUsed = true;
101 return;
102 }
103 let name;
104 let node;
105 if (typeof variableOrIdentifierOrName === 'string') {
106 name = variableOrIdentifierOrName;
107 node = parent;
108 }
109 else {
110 name = variableOrIdentifierOrName.name;
111 node = variableOrIdentifierOrName;
112 }
113 let currentScope = this.getScope(node);
114 while (currentScope) {
115 const variable = currentScope.variables.find(scopeVar => scopeVar.name === name);
116 if (variable) {
117 variable.eslintUsed = true;
118 return;
119 }
120 currentScope = currentScope.upper;
121 }
122 }
123 visitClass(node) {
124 // skip a variable of class itself name in the class scope
125 const scope = this.getScope(node);
126 for (const variable of scope.variables) {
127 if (variable.identifiers[0] === scope.block.id) {
128 this.markVariableAsUsed(variable);
129 return;
130 }
131 }
132 }
133 visitFunction(node) {
134 const scope = this.getScope(node);
135 // skip implicit "arguments" variable
136 const variable = scope.set.get('arguments');
137 if ((variable === null || variable === void 0 ? void 0 : variable.defs.length) === 0) {
138 this.markVariableAsUsed(variable);
139 }
140 }
141 visitFunctionTypeSignature(node) {
142 // function type signature params create variables because they can be referenced within the signature,
143 // but they obviously aren't unused variables for the purposes of this rule.
144 for (const param of node.params) {
145 this.visitPattern(param, name => {
146 this.markVariableAsUsed(name);
147 });
148 }
149 }
150 visitSetter(node) {
151 if (node.kind === 'set') {
152 // ignore setter parameters because they're syntactically required to exist
153 for (const param of node.value.params) {
154 this.visitPattern(param, id => {
155 this.markVariableAsUsed(id);
156 });
157 }
158 }
159 }
160 ForInStatement(node) {
161 /**
162 * (Brad Zacher): I hate that this has to exist.
163 * But it is required for compat with the base ESLint rule.
164 *
165 * In 2015, ESLint decided to add an exception for these two specific cases
166 * ```
167 * for (var key in object) return;
168 *
169 * var key;
170 * for (key in object) return;
171 * ```
172 *
173 * I disagree with it, but what are you going to do...
174 *
175 * https://github.com/eslint/eslint/issues/2342
176 */
177 let idOrVariable;
178 if (node.left.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
179 const variable = __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").getDeclaredVariables(node.left)[0];
180 if (!variable) {
181 return;
182 }
183 idOrVariable = variable;
184 }
185 if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
186 idOrVariable = node.left;
187 }
188 if (idOrVariable == null) {
189 return;
190 }
191 let body = node.body;
192 if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
193 if (node.body.body.length !== 1) {
194 return;
195 }
196 body = node.body.body[0];
197 }
198 if (body.type !== utils_1.AST_NODE_TYPES.ReturnStatement) {
199 return;
200 }
201 this.markVariableAsUsed(idOrVariable);
202 }
203 Identifier(node) {
204 const scope = this.getScope(node);
205 if (scope.type === utils_1.TSESLint.Scope.ScopeType.function &&
206 node.name === 'this') {
207 // this parameters should always be considered used as they're pseudo-parameters
208 if ('params' in scope.block && scope.block.params.includes(node)) {
209 this.markVariableAsUsed(node);
210 }
211 }
212 }
213 TSEnumDeclaration(node) {
214 // enum members create variables because they can be referenced within the enum,
215 // but they obviously aren't unused variables for the purposes of this rule.
216 const scope = this.getScope(node);
217 for (const variable of scope.variables) {
218 this.markVariableAsUsed(variable);
219 }
220 }
221 TSMappedType(node) {
222 // mapped types create a variable for their type name, but it's not necessary to reference it,
223 // so we shouldn't consider it as unused for the purpose of this rule.
224 this.markVariableAsUsed(node.typeParameter.name);
225 }
226 TSModuleDeclaration(node) {
227 // -- global augmentation can be in any file, and they do not need exports
228 if (node.global === true) {
229 this.markVariableAsUsed('global', node.parent);
230 }
231 }
232 TSParameterProperty(node) {
233 let identifier = null;
234 switch (node.parameter.type) {
235 case utils_1.AST_NODE_TYPES.AssignmentPattern:
236 if (node.parameter.left.type === utils_1.AST_NODE_TYPES.Identifier) {
237 identifier = node.parameter.left;
238 }
239 break;
240 case utils_1.AST_NODE_TYPES.Identifier:
241 identifier = node.parameter;
242 break;
243 }
244 if (identifier) {
245 this.markVariableAsUsed(identifier);
246 }
247 }
248}
249_UnusedVarsVisitor_scopeManager = new WeakMap();
250UnusedVarsVisitor.RESULTS_CACHE = new WeakMap();
251//#region private helpers
252/**
253 * Checks the position of given nodes.
254 * @param inner A node which is expected as inside.
255 * @param outer A node which is expected as outside.
256 * @returns `true` if the `inner` node exists in the `outer` node.
257 */
258function isInside(inner, outer) {
259 return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
260}
261/**
262 * Determine if an identifier is referencing an enclosing name.
263 * This only applies to declarations that create their own scope (modules, functions, classes)
264 * @param ref The reference to check.
265 * @param nodes The candidate function nodes.
266 * @returns True if it's a self-reference, false if not.
267 */
268function isSelfReference(ref, nodes) {
269 let scope = ref.from;
270 while (scope) {
271 if (nodes.has(scope.block)) {
272 return true;
273 }
274 scope = scope.upper;
275 }
276 return false;
277}
278const MERGABLE_TYPES = new Set([
279 utils_1.AST_NODE_TYPES.TSInterfaceDeclaration,
280 utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration,
281 utils_1.AST_NODE_TYPES.TSModuleDeclaration,
282 utils_1.AST_NODE_TYPES.ClassDeclaration,
283 utils_1.AST_NODE_TYPES.FunctionDeclaration,
284]);
285/**
286 * Determine if the variable is directly exported
287 * @param variable the variable to check
288 * @param target the type of node that is expected to be exported
289 */
290function isMergableExported(variable) {
291 var _a, _b;
292 // If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one
293 for (const def of variable.defs) {
294 // parameters can never be exported.
295 // their `node` prop points to the function decl, which can be exported
296 // so we need to special case them
297 if (def.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) {
298 continue;
299 }
300 if ((MERGABLE_TYPES.has(def.node.type) &&
301 ((_a = def.node.parent) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) ||
302 ((_b = def.node.parent) === null || _b === void 0 ? void 0 : _b.type) === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
303 return true;
304 }
305 }
306 return false;
307}
308/**
309 * Determines if a given variable is being exported from a module.
310 * @param variable eslint-scope variable object.
311 * @returns True if the variable is exported, false if not.
312 */
313function isExported(variable) {
314 const definition = variable.defs[0];
315 if (definition) {
316 let node = definition.node;
317 if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
318 node = node.parent;
319 }
320 else if (definition.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) {
321 return false;
322 }
323 return node.parent.type.indexOf('Export') === 0;
324 }
325 return false;
326}
327/**
328 * Determines if the variable is used.
329 * @param variable The variable to check.
330 * @returns True if the variable is used
331 */
332function isUsedVariable(variable) {
333 /**
334 * Gets a list of function definitions for a specified variable.
335 * @param variable eslint-scope variable object.
336 * @returns Function nodes.
337 */
338 function getFunctionDefinitions(variable) {
339 const functionDefinitions = new Set();
340 variable.defs.forEach(def => {
341 var _a, _b;
342 // FunctionDeclarations
343 if (def.type === utils_1.TSESLint.Scope.DefinitionType.FunctionName) {
344 functionDefinitions.add(def.node);
345 }
346 // FunctionExpressions
347 if (def.type === utils_1.TSESLint.Scope.DefinitionType.Variable &&
348 (((_a = def.node.init) === null || _a === void 0 ? void 0 : _a.type) === utils_1.AST_NODE_TYPES.FunctionExpression ||
349 ((_b = def.node.init) === null || _b === void 0 ? void 0 : _b.type) === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
350 functionDefinitions.add(def.node.init);
351 }
352 });
353 return functionDefinitions;
354 }
355 function getTypeDeclarations(variable) {
356 const nodes = new Set();
357 variable.defs.forEach(def => {
358 if (def.node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
359 def.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
360 nodes.add(def.node);
361 }
362 });
363 return nodes;
364 }
365 function getModuleDeclarations(variable) {
366 const nodes = new Set();
367 variable.defs.forEach(def => {
368 if (def.node.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration) {
369 nodes.add(def.node);
370 }
371 });
372 return nodes;
373 }
374 /**
375 * Checks if the ref is contained within one of the given nodes
376 */
377 function isInsideOneOf(ref, nodes) {
378 for (const node of nodes) {
379 if (isInside(ref.identifier, node)) {
380 return true;
381 }
382 }
383 return false;
384 }
385 /**
386 * If a given reference is left-hand side of an assignment, this gets
387 * the right-hand side node of the assignment.
388 *
389 * In the following cases, this returns null.
390 *
391 * - The reference is not the LHS of an assignment expression.
392 * - The reference is inside of a loop.
393 * - The reference is inside of a function scope which is different from
394 * the declaration.
395 * @param ref A reference to check.
396 * @param prevRhsNode The previous RHS node.
397 * @returns The RHS node or null.
398 */
399 function getRhsNode(ref, prevRhsNode) {
400 /**
401 * Checks whether the given node is in a loop or not.
402 * @param node The node to check.
403 * @returns `true` if the node is in a loop.
404 */
405 function isInLoop(node) {
406 let currentNode = node;
407 while (currentNode) {
408 if (utils_1.ASTUtils.isFunction(currentNode)) {
409 break;
410 }
411 if (utils_1.ASTUtils.isLoop(currentNode)) {
412 return true;
413 }
414 currentNode = currentNode.parent;
415 }
416 return false;
417 }
418 const id = ref.identifier;
419 const parent = id.parent;
420 const grandparent = parent.parent;
421 const refScope = ref.from.variableScope;
422 const varScope = ref.resolved.scope.variableScope;
423 const canBeUsedLater = refScope !== varScope || isInLoop(id);
424 /*
425 * Inherits the previous node if this reference is in the node.
426 * This is for `a = a + a`-like code.
427 */
428 if (prevRhsNode && isInside(id, prevRhsNode)) {
429 return prevRhsNode;
430 }
431 if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
432 grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
433 id === parent.left &&
434 !canBeUsedLater) {
435 return parent.right;
436 }
437 return null;
438 }
439 /**
440 * Checks whether a given reference is a read to update itself or not.
441 * @param ref A reference to check.
442 * @param rhsNode The RHS node of the previous assignment.
443 * @returns The reference is a read to update itself.
444 */
445 function isReadForItself(ref, rhsNode) {
446 /**
447 * Checks whether a given Identifier node exists inside of a function node which can be used later.
448 *
449 * "can be used later" means:
450 * - the function is assigned to a variable.
451 * - the function is bound to a property and the object can be used later.
452 * - the function is bound as an argument of a function call.
453 *
454 * If a reference exists in a function which can be used later, the reference is read when the function is called.
455 * @param id An Identifier node to check.
456 * @param rhsNode The RHS node of the previous assignment.
457 * @returns `true` if the `id` node exists inside of a function node which can be used later.
458 */
459 function isInsideOfStorableFunction(id, rhsNode) {
460 /**
461 * Finds a function node from ancestors of a node.
462 * @param node A start node to find.
463 * @returns A found function node.
464 */
465 function getUpperFunction(node) {
466 let currentNode = node;
467 while (currentNode) {
468 if (utils_1.ASTUtils.isFunction(currentNode)) {
469 return currentNode;
470 }
471 currentNode = currentNode.parent;
472 }
473 return null;
474 }
475 /**
476 * Checks whether a given function node is stored to somewhere or not.
477 * If the function node is stored, the function can be used later.
478 * @param funcNode A function node to check.
479 * @param rhsNode The RHS node of the previous assignment.
480 * @returns `true` if under the following conditions:
481 * - the funcNode is assigned to a variable.
482 * - the funcNode is bound as an argument of a function call.
483 * - the function is bound to a property and the object satisfies above conditions.
484 */
485 function isStorableFunction(funcNode, rhsNode) {
486 let node = funcNode;
487 let parent = funcNode.parent;
488 while (parent && isInside(parent, rhsNode)) {
489 switch (parent.type) {
490 case utils_1.AST_NODE_TYPES.SequenceExpression:
491 if (parent.expressions[parent.expressions.length - 1] !== node) {
492 return false;
493 }
494 break;
495 case utils_1.AST_NODE_TYPES.CallExpression:
496 case utils_1.AST_NODE_TYPES.NewExpression:
497 return parent.callee !== node;
498 case utils_1.AST_NODE_TYPES.AssignmentExpression:
499 case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
500 case utils_1.AST_NODE_TYPES.YieldExpression:
501 return true;
502 default:
503 if (parent.type.endsWith('Statement') ||
504 parent.type.endsWith('Declaration')) {
505 /*
506 * If it encountered statements, this is a complex pattern.
507 * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
508 */
509 return true;
510 }
511 }
512 node = parent;
513 parent = parent.parent;
514 }
515 return false;
516 }
517 const funcNode = getUpperFunction(id);
518 return (!!funcNode &&
519 isInside(funcNode, rhsNode) &&
520 isStorableFunction(funcNode, rhsNode));
521 }
522 const id = ref.identifier;
523 const parent = id.parent;
524 const grandparent = parent.parent;
525 return (ref.isRead() && // in RHS of an assignment for itself. e.g. `a = a + 1`
526 // self update. e.g. `a += 1`, `a++`
527 ((parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
528 grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
529 parent.left === id) ||
530 (parent.type === utils_1.AST_NODE_TYPES.UpdateExpression &&
531 grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement) ||
532 (!!rhsNode &&
533 isInside(id, rhsNode) &&
534 !isInsideOfStorableFunction(id, rhsNode))));
535 }
536 const functionNodes = getFunctionDefinitions(variable);
537 const isFunctionDefinition = functionNodes.size > 0;
538 const typeDeclNodes = getTypeDeclarations(variable);
539 const isTypeDecl = typeDeclNodes.size > 0;
540 const moduleDeclNodes = getModuleDeclarations(variable);
541 const isModuleDecl = moduleDeclNodes.size > 0;
542 let rhsNode = null;
543 return variable.references.some(ref => {
544 const forItself = isReadForItself(ref, rhsNode);
545 rhsNode = getRhsNode(ref, rhsNode);
546 return (ref.isRead() &&
547 !forItself &&
548 !(isFunctionDefinition && isSelfReference(ref, functionNodes)) &&
549 !(isTypeDecl && isInsideOneOf(ref, typeDeclNodes)) &&
550 !(isModuleDecl && isSelfReference(ref, moduleDeclNodes)));
551 });
552}
553//#endregion private helpers
554/**
555 * Collects the set of unused variables for a given context.
556 *
557 * Due to complexity, this does not take into consideration:
558 * - variables within declaration files
559 * - variables within ambient module declarations
560 */
561function collectUnusedVariables(context) {
562 return UnusedVarsVisitor.collectUnusedVariables(context);
563}
564exports.collectUnusedVariables = collectUnusedVariables;
565//# sourceMappingURL=collectUnusedVariables.js.map
\No newline at end of file