source: imaps-frontend/node_modules/eslint/lib/rules/no-unused-vars.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 27.0 KB
Line 
1/**
2 * @fileoverview Rule to flag declared but unused variables
3 * @author Ilya Volodin
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Typedefs
16//------------------------------------------------------------------------------
17
18/**
19 * Bag of data used for formatting the `unusedVar` lint message.
20 * @typedef {Object} UnusedVarMessageData
21 * @property {string} varName The name of the unused var.
22 * @property {'defined'|'assigned a value'} action Description of the vars state.
23 * @property {string} additional Any additional info to be appended at the end.
24 */
25
26//------------------------------------------------------------------------------
27// Rule Definition
28//------------------------------------------------------------------------------
29
30/** @type {import('../shared/types').Rule} */
31module.exports = {
32 meta: {
33 type: "problem",
34
35 docs: {
36 description: "Disallow unused variables",
37 recommended: true,
38 url: "https://eslint.org/docs/latest/rules/no-unused-vars"
39 },
40
41 schema: [
42 {
43 oneOf: [
44 {
45 enum: ["all", "local"]
46 },
47 {
48 type: "object",
49 properties: {
50 vars: {
51 enum: ["all", "local"]
52 },
53 varsIgnorePattern: {
54 type: "string"
55 },
56 args: {
57 enum: ["all", "after-used", "none"]
58 },
59 ignoreRestSiblings: {
60 type: "boolean"
61 },
62 argsIgnorePattern: {
63 type: "string"
64 },
65 caughtErrors: {
66 enum: ["all", "none"]
67 },
68 caughtErrorsIgnorePattern: {
69 type: "string"
70 },
71 destructuredArrayIgnorePattern: {
72 type: "string"
73 }
74 },
75 additionalProperties: false
76 }
77 ]
78 }
79 ],
80
81 messages: {
82 unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}."
83 }
84 },
85
86 create(context) {
87 const sourceCode = context.sourceCode;
88
89 const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u;
90
91 const config = {
92 vars: "all",
93 args: "after-used",
94 ignoreRestSiblings: false,
95 caughtErrors: "none"
96 };
97
98 const firstOption = context.options[0];
99
100 if (firstOption) {
101 if (typeof firstOption === "string") {
102 config.vars = firstOption;
103 } else {
104 config.vars = firstOption.vars || config.vars;
105 config.args = firstOption.args || config.args;
106 config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
107 config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
108
109 if (firstOption.varsIgnorePattern) {
110 config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u");
111 }
112
113 if (firstOption.argsIgnorePattern) {
114 config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u");
115 }
116
117 if (firstOption.caughtErrorsIgnorePattern) {
118 config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
119 }
120
121 if (firstOption.destructuredArrayIgnorePattern) {
122 config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
123 }
124 }
125 }
126
127 /**
128 * Generates the message data about the variable being defined and unused,
129 * including the ignore pattern if configured.
130 * @param {Variable} unusedVar eslint-scope variable object.
131 * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
132 */
133 function getDefinedMessageData(unusedVar) {
134 const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type;
135 let type;
136 let pattern;
137
138 if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) {
139 type = "args";
140 pattern = config.caughtErrorsIgnorePattern.toString();
141 } else if (defType === "Parameter" && config.argsIgnorePattern) {
142 type = "args";
143 pattern = config.argsIgnorePattern.toString();
144 } else if (defType !== "Parameter" && config.varsIgnorePattern) {
145 type = "vars";
146 pattern = config.varsIgnorePattern.toString();
147 }
148
149 const additional = type ? `. Allowed unused ${type} must match ${pattern}` : "";
150
151 return {
152 varName: unusedVar.name,
153 action: "defined",
154 additional
155 };
156 }
157
158 /**
159 * Generate the warning message about the variable being
160 * assigned and unused, including the ignore pattern if configured.
161 * @param {Variable} unusedVar eslint-scope variable object.
162 * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
163 */
164 function getAssignedMessageData(unusedVar) {
165 const def = unusedVar.defs[0];
166 let additional = "";
167
168 if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") {
169 additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`;
170 } else if (config.varsIgnorePattern) {
171 additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`;
172 }
173
174 return {
175 varName: unusedVar.name,
176 action: "assigned a value",
177 additional
178 };
179 }
180
181 //--------------------------------------------------------------------------
182 // Helpers
183 //--------------------------------------------------------------------------
184
185 const STATEMENT_TYPE = /(?:Statement|Declaration)$/u;
186
187 /**
188 * Determines if a given variable is being exported from a module.
189 * @param {Variable} variable eslint-scope variable object.
190 * @returns {boolean} True if the variable is exported, false if not.
191 * @private
192 */
193 function isExported(variable) {
194
195 const definition = variable.defs[0];
196
197 if (definition) {
198
199 let node = definition.node;
200
201 if (node.type === "VariableDeclarator") {
202 node = node.parent;
203 } else if (definition.type === "Parameter") {
204 return false;
205 }
206
207 return node.parent.type.indexOf("Export") === 0;
208 }
209 return false;
210
211 }
212
213 /**
214 * Checks whether a node is a sibling of the rest property or not.
215 * @param {ASTNode} node a node to check
216 * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
217 */
218 function hasRestSibling(node) {
219 return node.type === "Property" &&
220 node.parent.type === "ObjectPattern" &&
221 REST_PROPERTY_TYPE.test(node.parent.properties[node.parent.properties.length - 1].type);
222 }
223
224 /**
225 * Determines if a variable has a sibling rest property
226 * @param {Variable} variable eslint-scope variable object.
227 * @returns {boolean} True if the variable is exported, false if not.
228 * @private
229 */
230 function hasRestSpreadSibling(variable) {
231 if (config.ignoreRestSiblings) {
232 const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent));
233 const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent));
234
235 return hasRestSiblingDefinition || hasRestSiblingReference;
236 }
237
238 return false;
239 }
240
241 /**
242 * Determines if a reference is a read operation.
243 * @param {Reference} ref An eslint-scope Reference
244 * @returns {boolean} whether the given reference represents a read operation
245 * @private
246 */
247 function isReadRef(ref) {
248 return ref.isRead();
249 }
250
251 /**
252 * Determine if an identifier is referencing an enclosing function name.
253 * @param {Reference} ref The reference to check.
254 * @param {ASTNode[]} nodes The candidate function nodes.
255 * @returns {boolean} True if it's a self-reference, false if not.
256 * @private
257 */
258 function isSelfReference(ref, nodes) {
259 let scope = ref.from;
260
261 while (scope) {
262 if (nodes.includes(scope.block)) {
263 return true;
264 }
265
266 scope = scope.upper;
267 }
268
269 return false;
270 }
271
272 /**
273 * Gets a list of function definitions for a specified variable.
274 * @param {Variable} variable eslint-scope variable object.
275 * @returns {ASTNode[]} Function nodes.
276 * @private
277 */
278 function getFunctionDefinitions(variable) {
279 const functionDefinitions = [];
280
281 variable.defs.forEach(def => {
282 const { type, node } = def;
283
284 // FunctionDeclarations
285 if (type === "FunctionName") {
286 functionDefinitions.push(node);
287 }
288
289 // FunctionExpressions
290 if (type === "Variable" && node.init &&
291 (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
292 functionDefinitions.push(node.init);
293 }
294 });
295 return functionDefinitions;
296 }
297
298 /**
299 * Checks the position of given nodes.
300 * @param {ASTNode} inner A node which is expected as inside.
301 * @param {ASTNode} outer A node which is expected as outside.
302 * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
303 * @private
304 */
305 function isInside(inner, outer) {
306 return (
307 inner.range[0] >= outer.range[0] &&
308 inner.range[1] <= outer.range[1]
309 );
310 }
311
312 /**
313 * Checks whether a given node is unused expression or not.
314 * @param {ASTNode} node The node itself
315 * @returns {boolean} The node is an unused expression.
316 * @private
317 */
318 function isUnusedExpression(node) {
319 const parent = node.parent;
320
321 if (parent.type === "ExpressionStatement") {
322 return true;
323 }
324
325 if (parent.type === "SequenceExpression") {
326 const isLastExpression = parent.expressions[parent.expressions.length - 1] === node;
327
328 if (!isLastExpression) {
329 return true;
330 }
331 return isUnusedExpression(parent);
332 }
333
334 return false;
335 }
336
337 /**
338 * If a given reference is left-hand side of an assignment, this gets
339 * the right-hand side node of the assignment.
340 *
341 * In the following cases, this returns null.
342 *
343 * - The reference is not the LHS of an assignment expression.
344 * - The reference is inside of a loop.
345 * - The reference is inside of a function scope which is different from
346 * the declaration.
347 * @param {eslint-scope.Reference} ref A reference to check.
348 * @param {ASTNode} prevRhsNode The previous RHS node.
349 * @returns {ASTNode|null} The RHS node or null.
350 * @private
351 */
352 function getRhsNode(ref, prevRhsNode) {
353 const id = ref.identifier;
354 const parent = id.parent;
355 const refScope = ref.from.variableScope;
356 const varScope = ref.resolved.scope.variableScope;
357 const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
358
359 /*
360 * Inherits the previous node if this reference is in the node.
361 * This is for `a = a + a`-like code.
362 */
363 if (prevRhsNode && isInside(id, prevRhsNode)) {
364 return prevRhsNode;
365 }
366
367 if (parent.type === "AssignmentExpression" &&
368 isUnusedExpression(parent) &&
369 id === parent.left &&
370 !canBeUsedLater
371 ) {
372 return parent.right;
373 }
374 return null;
375 }
376
377 /**
378 * Checks whether a given function node is stored to somewhere or not.
379 * If the function node is stored, the function can be used later.
380 * @param {ASTNode} funcNode A function node to check.
381 * @param {ASTNode} rhsNode The RHS node of the previous assignment.
382 * @returns {boolean} `true` if under the following conditions:
383 * - the funcNode is assigned to a variable.
384 * - the funcNode is bound as an argument of a function call.
385 * - the function is bound to a property and the object satisfies above conditions.
386 * @private
387 */
388 function isStorableFunction(funcNode, rhsNode) {
389 let node = funcNode;
390 let parent = funcNode.parent;
391
392 while (parent && isInside(parent, rhsNode)) {
393 switch (parent.type) {
394 case "SequenceExpression":
395 if (parent.expressions[parent.expressions.length - 1] !== node) {
396 return false;
397 }
398 break;
399
400 case "CallExpression":
401 case "NewExpression":
402 return parent.callee !== node;
403
404 case "AssignmentExpression":
405 case "TaggedTemplateExpression":
406 case "YieldExpression":
407 return true;
408
409 default:
410 if (STATEMENT_TYPE.test(parent.type)) {
411
412 /*
413 * If it encountered statements, this is a complex pattern.
414 * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
415 */
416 return true;
417 }
418 }
419
420 node = parent;
421 parent = parent.parent;
422 }
423
424 return false;
425 }
426
427 /**
428 * Checks whether a given Identifier node exists inside of a function node which can be used later.
429 *
430 * "can be used later" means:
431 * - the function is assigned to a variable.
432 * - the function is bound to a property and the object can be used later.
433 * - the function is bound as an argument of a function call.
434 *
435 * If a reference exists in a function which can be used later, the reference is read when the function is called.
436 * @param {ASTNode} id An Identifier node to check.
437 * @param {ASTNode} rhsNode The RHS node of the previous assignment.
438 * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
439 * @private
440 */
441 function isInsideOfStorableFunction(id, rhsNode) {
442 const funcNode = astUtils.getUpperFunction(id);
443
444 return (
445 funcNode &&
446 isInside(funcNode, rhsNode) &&
447 isStorableFunction(funcNode, rhsNode)
448 );
449 }
450
451 /**
452 * Checks whether a given reference is a read to update itself or not.
453 * @param {eslint-scope.Reference} ref A reference to check.
454 * @param {ASTNode} rhsNode The RHS node of the previous assignment.
455 * @returns {boolean} The reference is a read to update itself.
456 * @private
457 */
458 function isReadForItself(ref, rhsNode) {
459 const id = ref.identifier;
460 const parent = id.parent;
461
462 return ref.isRead() && (
463
464 // self update. e.g. `a += 1`, `a++`
465 (
466 (
467 parent.type === "AssignmentExpression" &&
468 parent.left === id &&
469 isUnusedExpression(parent) &&
470 !astUtils.isLogicalAssignmentOperator(parent.operator)
471 ) ||
472 (
473 parent.type === "UpdateExpression" &&
474 isUnusedExpression(parent)
475 )
476 ) ||
477
478 // in RHS of an assignment for itself. e.g. `a = a + 1`
479 (
480 rhsNode &&
481 isInside(id, rhsNode) &&
482 !isInsideOfStorableFunction(id, rhsNode)
483 )
484 );
485 }
486
487 /**
488 * Determine if an identifier is used either in for-in or for-of loops.
489 * @param {Reference} ref The reference to check.
490 * @returns {boolean} whether reference is used in the for-in loops
491 * @private
492 */
493 function isForInOfRef(ref) {
494 let target = ref.identifier.parent;
495
496
497 // "for (var ...) { return; }"
498 if (target.type === "VariableDeclarator") {
499 target = target.parent.parent;
500 }
501
502 if (target.type !== "ForInStatement" && target.type !== "ForOfStatement") {
503 return false;
504 }
505
506 // "for (...) { return; }"
507 if (target.body.type === "BlockStatement") {
508 target = target.body.body[0];
509
510 // "for (...) return;"
511 } else {
512 target = target.body;
513 }
514
515 // For empty loop body
516 if (!target) {
517 return false;
518 }
519
520 return target.type === "ReturnStatement";
521 }
522
523 /**
524 * Determines if the variable is used.
525 * @param {Variable} variable The variable to check.
526 * @returns {boolean} True if the variable is used
527 * @private
528 */
529 function isUsedVariable(variable) {
530 const functionNodes = getFunctionDefinitions(variable),
531 isFunctionDefinition = functionNodes.length > 0;
532 let rhsNode = null;
533
534 return variable.references.some(ref => {
535 if (isForInOfRef(ref)) {
536 return true;
537 }
538
539 const forItself = isReadForItself(ref, rhsNode);
540
541 rhsNode = getRhsNode(ref, rhsNode);
542
543 return (
544 isReadRef(ref) &&
545 !forItself &&
546 !(isFunctionDefinition && isSelfReference(ref, functionNodes))
547 );
548 });
549 }
550
551 /**
552 * Checks whether the given variable is after the last used parameter.
553 * @param {eslint-scope.Variable} variable The variable to check.
554 * @returns {boolean} `true` if the variable is defined after the last
555 * used parameter.
556 */
557 function isAfterLastUsedArg(variable) {
558 const def = variable.defs[0];
559 const params = sourceCode.getDeclaredVariables(def.node);
560 const posteriorParams = params.slice(params.indexOf(variable) + 1);
561
562 // If any used parameters occur after this parameter, do not report.
563 return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
564 }
565
566 /**
567 * Gets an array of variables without read references.
568 * @param {Scope} scope an eslint-scope Scope object.
569 * @param {Variable[]} unusedVars an array that saving result.
570 * @returns {Variable[]} unused variables of the scope and descendant scopes.
571 * @private
572 */
573 function collectUnusedVariables(scope, unusedVars) {
574 const variables = scope.variables;
575 const childScopes = scope.childScopes;
576 let i, l;
577
578 if (scope.type !== "global" || config.vars === "all") {
579 for (i = 0, l = variables.length; i < l; ++i) {
580 const variable = variables[i];
581
582 // skip a variable of class itself name in the class scope
583 if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
584 continue;
585 }
586
587 // skip function expression names and variables marked with markVariableAsUsed()
588 if (scope.functionExpressionScope || variable.eslintUsed) {
589 continue;
590 }
591
592 // skip implicit "arguments" variable
593 if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
594 continue;
595 }
596
597 // explicit global variables don't have definitions.
598 const def = variable.defs[0];
599
600 if (def) {
601 const type = def.type;
602 const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
603
604 // skip elements of array destructuring patterns
605 if (
606 (
607 def.name.parent.type === "ArrayPattern" ||
608 refUsedInArrayPatterns
609 ) &&
610 config.destructuredArrayIgnorePattern &&
611 config.destructuredArrayIgnorePattern.test(def.name.name)
612 ) {
613 continue;
614 }
615
616 // skip catch variables
617 if (type === "CatchClause") {
618 if (config.caughtErrors === "none") {
619 continue;
620 }
621
622 // skip ignored parameters
623 if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
624 continue;
625 }
626 }
627
628 if (type === "Parameter") {
629
630 // skip any setter argument
631 if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
632 continue;
633 }
634
635 // if "args" option is "none", skip any parameter
636 if (config.args === "none") {
637 continue;
638 }
639
640 // skip ignored parameters
641 if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
642 continue;
643 }
644
645 // if "args" option is "after-used", skip used variables
646 if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
647 continue;
648 }
649 } else {
650
651 // skip ignored variables
652 if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
653 continue;
654 }
655 }
656 }
657
658 if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
659 unusedVars.push(variable);
660 }
661 }
662 }
663
664 for (i = 0, l = childScopes.length; i < l; ++i) {
665 collectUnusedVariables(childScopes[i], unusedVars);
666 }
667
668 return unusedVars;
669 }
670
671 //--------------------------------------------------------------------------
672 // Public
673 //--------------------------------------------------------------------------
674
675 return {
676 "Program:exit"(programNode) {
677 const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []);
678
679 for (let i = 0, l = unusedVars.length; i < l; ++i) {
680 const unusedVar = unusedVars[i];
681
682 // Report the first declaration.
683 if (unusedVar.defs.length > 0) {
684
685 // report last write reference, https://github.com/eslint/eslint/issues/14324
686 const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
687
688 let referenceToReport;
689
690 if (writeReferences.length > 0) {
691 referenceToReport = writeReferences[writeReferences.length - 1];
692 }
693
694 context.report({
695 node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
696 messageId: "unusedVar",
697 data: unusedVar.references.some(ref => ref.isWrite())
698 ? getAssignedMessageData(unusedVar)
699 : getDefinedMessageData(unusedVar)
700 });
701
702 // If there are no regular declaration, report the first `/*globals*/` comment directive.
703 } else if (unusedVar.eslintExplicitGlobalComments) {
704 const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
705
706 context.report({
707 node: programNode,
708 loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
709 messageId: "unusedVar",
710 data: getDefinedMessageData(unusedVar)
711 });
712 }
713 }
714 }
715 };
716
717 }
718};
Note: See TracBrowser for help on using the repository browser.