UNPKG

6.12 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag declared but unused variables
3 * @author Ilya Volodin
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = function(context) {
13
14 var allowUnusedGlobals = (context.options[0] !== "all"),
15 variables = {};
16
17 function lookupVariableName(name) {
18
19 // Convoluted check in case name is "hasOwnProperty"
20 if (!Object.prototype.hasOwnProperty.call(variables, name)) {
21 return null;
22 }
23 return variables[name];
24 }
25
26 function lookupVariable(variable) {
27 var candidates = lookupVariableName(variable.name);
28 if (candidates) {
29 return candidates.filter(function (candidate) {
30 return variable.identifiers.some(function (identifier) {
31 return candidate.node === identifier;
32 });
33 })[0];
34 }
35 return candidates;
36 }
37
38 function populateVariables(node) {
39 var scope = context.getScope(),
40 functionName = node && node.id && node.id.name,
41 parent = context.getAncestors().pop(),
42
43 // check for function foo(){}.bind(this)
44 functionNameUsed = parent && parent.type === "MemberExpression";
45
46 scope.variables.forEach(function(variable) {
47
48 //filter out global variables that we add as part of eslint or arguments variable
49 if (variable.identifiers.length > 0) {
50
51 //make sure that this variable is not already in the array
52 if (!lookupVariable(variable)) {
53
54 if (!lookupVariableName(variable.name)) {
55 variables[variable.name] = [];
56 }
57 variables[variable.name].push({
58 name: variable.name,
59 node: variable.identifiers[0],
60 used: (variable.name === functionName) && functionNameUsed
61 });
62 }
63 }
64 });
65 }
66
67 function populateGlobalVariables() {
68 if (!allowUnusedGlobals) {
69 populateVariables();
70 }
71 }
72
73 function findVariable(name) {
74 var scope = context.getScope();
75 var scopeVariable = [];
76
77 function filter(variable) {
78 return variable.name === name;
79 }
80
81 while (scopeVariable.length === 0) {
82 scopeVariable = scope.variables.filter(filter);
83 if (scopeVariable.length === 0) {
84 if (!scope.upper) {
85 return null;
86 }
87 scope = scope.upper;
88 }
89 }
90
91 return lookupVariable(scopeVariable[0]);
92 }
93
94 function isFunction(node) {
95 return node && node.type && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression");
96 }
97
98 function markIgnorableUnusedVariables(usedVariable) {
99
100 /* When variables are declared as parameters in a FunctionExpression or
101 * FunctionDeclaration, they can go unused so long as at least one
102 * to the right of them is used.
103 */
104
105 // Find the FunctionExpressions and FunctionDeclarations in which this used variable
106 // may be a parameter
107 var parent = usedVariable.node.parent;
108 if (isFunction(parent)) {
109 // Get a list of the param names used in the ancestor Function
110 var fnParamNames = parent.params.map(function(param){
111 return param.name;
112 });
113 // Check if the used variable exists among those params
114 var variableIndex = fnParamNames.indexOf(usedVariable.name);
115 // This will be true if this variable appears in the param list of an ancestor Function Expression
116 // or FunctionDeclaration.
117 if (variableIndex !== -1) {
118 // All params left of the used variables are ignorable.
119 var ignorableVariables = fnParamNames.slice(0, variableIndex);
120 // Mark them as ignorable.
121 ignorableVariables.forEach(function(ignorableVariable){
122
123 (lookupVariableName(ignorableVariable) || []).forEach(function(variable){
124 variable.ignorable = true;
125 });
126 });
127 }
128 }
129 }
130
131 return {
132 "FunctionDeclaration": populateVariables,
133 "FunctionExpression": populateVariables,
134 "Program": populateGlobalVariables,
135 "Identifier": function(node) {
136 var ancestors = context.getAncestors(node);
137 var parent = ancestors.pop();
138 var grandparent = ancestors.pop();
139 /*if it's not an assignment expression find corresponding
140 *variable in the array and mark it as used
141 */
142 if ((parent.type !== "AssignmentExpression" || node !== parent.left) &&
143 (parent.type !== "VariableDeclarator" || (parent.init && parent.init === node)) &&
144 parent.type !== "FunctionDeclaration" &&
145 (parent.type !== "FunctionExpression" ||
146 (grandparent !== null && (grandparent.type === "CallExpression" || grandparent.type === "AssignmentExpression")))) {
147
148 var variable = findVariable(node.name);
149
150 if (variable) {
151 variable.used = true;
152 markIgnorableUnusedVariables(variable, ancestors);
153 }
154
155 }
156 },
157
158 "Program:exit": function() {
159 Object.keys(variables)
160 .forEach(function (name) {
161 variables[name].forEach(function (variable) {
162 if (variable.used || variable.ignorable) {
163 return;
164 }
165 context.report(variable.node, "{{var}} is defined but never used", {"var": variable.name});
166 });
167 });
168 }
169 };
170
171};