.gitignore added
[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          * Determines if a variable has a sibling rest property
201          * @param {Variable} variable eslint-scope variable object.
202          * @returns {boolean} True if the variable is exported, false if not.
203          * @private
204          */
205         function hasRestSpreadSibling(variable) {
206             if (config.ignoreRestSiblings) {
207                 return variable.defs.some(def => {
208                     const propertyNode = def.name.parent;
209                     const patternNode = propertyNode.parent;
210
211                     return (
212                         propertyNode.type === "Property" &&
213                         patternNode.type === "ObjectPattern" &&
214                         REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type)
215                     );
216                 });
217             }
218
219             return false;
220         }
221
222         /**
223          * Determines if a reference is a read operation.
224          * @param {Reference} ref An eslint-scope Reference
225          * @returns {boolean} whether the given reference represents a read operation
226          * @private
227          */
228         function isReadRef(ref) {
229             return ref.isRead();
230         }
231
232         /**
233          * Determine if an identifier is referencing an enclosing function name.
234          * @param {Reference} ref The reference to check.
235          * @param {ASTNode[]} nodes The candidate function nodes.
236          * @returns {boolean} True if it's a self-reference, false if not.
237          * @private
238          */
239         function isSelfReference(ref, nodes) {
240             let scope = ref.from;
241
242             while (scope) {
243                 if (nodes.indexOf(scope.block) >= 0) {
244                     return true;
245                 }
246
247                 scope = scope.upper;
248             }
249
250             return false;
251         }
252
253         /**
254          * Gets a list of function definitions for a specified variable.
255          * @param {Variable} variable eslint-scope variable object.
256          * @returns {ASTNode[]} Function nodes.
257          * @private
258          */
259         function getFunctionDefinitions(variable) {
260             const functionDefinitions = [];
261
262             variable.defs.forEach(def => {
263                 const { type, node } = def;
264
265                 // FunctionDeclarations
266                 if (type === "FunctionName") {
267                     functionDefinitions.push(node);
268                 }
269
270                 // FunctionExpressions
271                 if (type === "Variable" && node.init &&
272                     (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
273                     functionDefinitions.push(node.init);
274                 }
275             });
276             return functionDefinitions;
277         }
278
279         /**
280          * Checks the position of given nodes.
281          * @param {ASTNode} inner A node which is expected as inside.
282          * @param {ASTNode} outer A node which is expected as outside.
283          * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
284          * @private
285          */
286         function isInside(inner, outer) {
287             return (
288                 inner.range[0] >= outer.range[0] &&
289                 inner.range[1] <= outer.range[1]
290             );
291         }
292
293         /**
294          * If a given reference is left-hand side of an assignment, this gets
295          * the right-hand side node of the assignment.
296          *
297          * In the following cases, this returns null.
298          *
299          * - The reference is not the LHS of an assignment expression.
300          * - The reference is inside of a loop.
301          * - The reference is inside of a function scope which is different from
302          *   the declaration.
303          * @param {eslint-scope.Reference} ref A reference to check.
304          * @param {ASTNode} prevRhsNode The previous RHS node.
305          * @returns {ASTNode|null} The RHS node or null.
306          * @private
307          */
308         function getRhsNode(ref, prevRhsNode) {
309             const id = ref.identifier;
310             const parent = id.parent;
311             const grandparent = parent.parent;
312             const refScope = ref.from.variableScope;
313             const varScope = ref.resolved.scope.variableScope;
314             const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
315
316             /*
317              * Inherits the previous node if this reference is in the node.
318              * This is for `a = a + a`-like code.
319              */
320             if (prevRhsNode && isInside(id, prevRhsNode)) {
321                 return prevRhsNode;
322             }
323
324             if (parent.type === "AssignmentExpression" &&
325                 grandparent.type === "ExpressionStatement" &&
326                 id === parent.left &&
327                 !canBeUsedLater
328             ) {
329                 return parent.right;
330             }
331             return null;
332         }
333
334         /**
335          * Checks whether a given function node is stored to somewhere or not.
336          * If the function node is stored, the function can be used later.
337          * @param {ASTNode} funcNode A function node to check.
338          * @param {ASTNode} rhsNode The RHS node of the previous assignment.
339          * @returns {boolean} `true` if under the following conditions:
340          *      - the funcNode is assigned to a variable.
341          *      - the funcNode is bound as an argument of a function call.
342          *      - the function is bound to a property and the object satisfies above conditions.
343          * @private
344          */
345         function isStorableFunction(funcNode, rhsNode) {
346             let node = funcNode;
347             let parent = funcNode.parent;
348
349             while (parent && isInside(parent, rhsNode)) {
350                 switch (parent.type) {
351                     case "SequenceExpression":
352                         if (parent.expressions[parent.expressions.length - 1] !== node) {
353                             return false;
354                         }
355                         break;
356
357                     case "CallExpression":
358                     case "NewExpression":
359                         return parent.callee !== node;
360
361                     case "AssignmentExpression":
362                     case "TaggedTemplateExpression":
363                     case "YieldExpression":
364                         return true;
365
366                     default:
367                         if (STATEMENT_TYPE.test(parent.type)) {
368
369                             /*
370                              * If it encountered statements, this is a complex pattern.
371                              * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
372                              */
373                             return true;
374                         }
375                 }
376
377                 node = parent;
378                 parent = parent.parent;
379             }
380
381             return false;
382         }
383
384         /**
385          * Checks whether a given Identifier node exists inside of a function node which can be used later.
386          *
387          * "can be used later" means:
388          * - the function is assigned to a variable.
389          * - the function is bound to a property and the object can be used later.
390          * - the function is bound as an argument of a function call.
391          *
392          * If a reference exists in a function which can be used later, the reference is read when the function is called.
393          * @param {ASTNode} id An Identifier node to check.
394          * @param {ASTNode} rhsNode The RHS node of the previous assignment.
395          * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
396          * @private
397          */
398         function isInsideOfStorableFunction(id, rhsNode) {
399             const funcNode = astUtils.getUpperFunction(id);
400
401             return (
402                 funcNode &&
403                 isInside(funcNode, rhsNode) &&
404                 isStorableFunction(funcNode, rhsNode)
405             );
406         }
407
408         /**
409          * Checks whether a given reference is a read to update itself or not.
410          * @param {eslint-scope.Reference} ref A reference to check.
411          * @param {ASTNode} rhsNode The RHS node of the previous assignment.
412          * @returns {boolean} The reference is a read to update itself.
413          * @private
414          */
415         function isReadForItself(ref, rhsNode) {
416             const id = ref.identifier;
417             const parent = id.parent;
418             const grandparent = parent.parent;
419
420             return ref.isRead() && (
421
422                 // self update. e.g. `a += 1`, `a++`
423                 (// in RHS of an assignment for itself. e.g. `a = a + 1`
424                     ((
425                         parent.type === "AssignmentExpression" &&
426                     grandparent.type === "ExpressionStatement" &&
427                     parent.left === id
428                     ) ||
429                 (
430                     parent.type === "UpdateExpression" &&
431                     grandparent.type === "ExpressionStatement"
432                 ) || rhsNode &&
433                 isInside(id, rhsNode) &&
434                 !isInsideOfStorableFunction(id, rhsNode)))
435             );
436         }
437
438         /**
439          * Determine if an identifier is used either in for-in loops.
440          * @param {Reference} ref The reference to check.
441          * @returns {boolean} whether reference is used in the for-in loops
442          * @private
443          */
444         function isForInRef(ref) {
445             let target = ref.identifier.parent;
446
447
448             // "for (var ...) { return; }"
449             if (target.type === "VariableDeclarator") {
450                 target = target.parent.parent;
451             }
452
453             if (target.type !== "ForInStatement") {
454                 return false;
455             }
456
457             // "for (...) { return; }"
458             if (target.body.type === "BlockStatement") {
459                 target = target.body.body[0];
460
461             // "for (...) return;"
462             } else {
463                 target = target.body;
464             }
465
466             // For empty loop body
467             if (!target) {
468                 return false;
469             }
470
471             return target.type === "ReturnStatement";
472         }
473
474         /**
475          * Determines if the variable is used.
476          * @param {Variable} variable The variable to check.
477          * @returns {boolean} True if the variable is used
478          * @private
479          */
480         function isUsedVariable(variable) {
481             const functionNodes = getFunctionDefinitions(variable),
482                 isFunctionDefinition = functionNodes.length > 0;
483             let rhsNode = null;
484
485             return variable.references.some(ref => {
486                 if (isForInRef(ref)) {
487                     return true;
488                 }
489
490                 const forItself = isReadForItself(ref, rhsNode);
491
492                 rhsNode = getRhsNode(ref, rhsNode);
493
494                 return (
495                     isReadRef(ref) &&
496                     !forItself &&
497                     !(isFunctionDefinition && isSelfReference(ref, functionNodes))
498                 );
499             });
500         }
501
502         /**
503          * Checks whether the given variable is after the last used parameter.
504          * @param {eslint-scope.Variable} variable The variable to check.
505          * @returns {boolean} `true` if the variable is defined after the last
506          * used parameter.
507          */
508         function isAfterLastUsedArg(variable) {
509             const def = variable.defs[0];
510             const params = context.getDeclaredVariables(def.node);
511             const posteriorParams = params.slice(params.indexOf(variable) + 1);
512
513             // If any used parameters occur after this parameter, do not report.
514             return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
515         }
516
517         /**
518          * Gets an array of variables without read references.
519          * @param {Scope} scope an eslint-scope Scope object.
520          * @param {Variable[]} unusedVars an array that saving result.
521          * @returns {Variable[]} unused variables of the scope and descendant scopes.
522          * @private
523          */
524         function collectUnusedVariables(scope, unusedVars) {
525             const variables = scope.variables;
526             const childScopes = scope.childScopes;
527             let i, l;
528
529             if (scope.type !== "global" || config.vars === "all") {
530                 for (i = 0, l = variables.length; i < l; ++i) {
531                     const variable = variables[i];
532
533                     // skip a variable of class itself name in the class scope
534                     if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
535                         continue;
536                     }
537
538                     // skip function expression names and variables marked with markVariableAsUsed()
539                     if (scope.functionExpressionScope || variable.eslintUsed) {
540                         continue;
541                     }
542
543                     // skip implicit "arguments" variable
544                     if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
545                         continue;
546                     }
547
548                     // explicit global variables don't have definitions.
549                     const def = variable.defs[0];
550
551                     if (def) {
552                         const type = def.type;
553
554                         // skip catch variables
555                         if (type === "CatchClause") {
556                             if (config.caughtErrors === "none") {
557                                 continue;
558                             }
559
560                             // skip ignored parameters
561                             if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
562                                 continue;
563                             }
564                         }
565
566                         if (type === "Parameter") {
567
568                             // skip any setter argument
569                             if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
570                                 continue;
571                             }
572
573                             // if "args" option is "none", skip any parameter
574                             if (config.args === "none") {
575                                 continue;
576                             }
577
578                             // skip ignored parameters
579                             if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
580                                 continue;
581                             }
582
583                             // if "args" option is "after-used", skip used variables
584                             if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) {
585                                 continue;
586                             }
587                         } else {
588
589                             // skip ignored variables
590                             if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
591                                 continue;
592                             }
593                         }
594                     }
595
596                     if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
597                         unusedVars.push(variable);
598                     }
599                 }
600             }
601
602             for (i = 0, l = childScopes.length; i < l; ++i) {
603                 collectUnusedVariables(childScopes[i], unusedVars);
604             }
605
606             return unusedVars;
607         }
608
609         //--------------------------------------------------------------------------
610         // Public
611         //--------------------------------------------------------------------------
612
613         return {
614             "Program:exit"(programNode) {
615                 const unusedVars = collectUnusedVariables(context.getScope(), []);
616
617                 for (let i = 0, l = unusedVars.length; i < l; ++i) {
618                     const unusedVar = unusedVars[i];
619
620                     // Report the first declaration.
621                     if (unusedVar.defs.length > 0) {
622                         context.report({
623                             node: unusedVar.references.length ? unusedVar.references[
624                                 unusedVar.references.length - 1
625                             ].identifier : unusedVar.identifiers[0],
626                             messageId: "unusedVar",
627                             data: unusedVar.references.some(ref => ref.isWrite())
628                                 ? getAssignedMessageData(unusedVar)
629                                 : getDefinedMessageData(unusedVar)
630                         });
631
632                     // If there are no regular declaration, report the first `/*globals*/` comment directive.
633                     } else if (unusedVar.eslintExplicitGlobalComments) {
634                         const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
635
636                         context.report({
637                             node: programNode,
638                             loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
639                             messageId: "unusedVar",
640                             data: getDefinedMessageData(unusedVar)
641                         });
642                     }
643                 }
644             }
645         };
646
647     }
648 };