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