Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib/rules/no-unused-vars.js')
-rw-r--r--tools/eslint/lib/rules/no-unused-vars.js218
1 files changed, 145 insertions, 73 deletions
diff --git a/tools/eslint/lib/rules/no-unused-vars.js b/tools/eslint/lib/rules/no-unused-vars.js
index d6ec43bf1d6..6b3b7f334e2 100644
--- a/tools/eslint/lib/rules/no-unused-vars.js
+++ b/tools/eslint/lib/rules/no-unused-vars.js
@@ -27,9 +27,14 @@ module.exports = function(context) {
}
}
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+
/**
* Determines if a given variable is being exported from a module.
- * @param {Variable} variable EScope variable object.
+ * @param {Variable} variable - EScope variable object.
* @returns {boolean} True if the variable is exported, false if not.
* @private
*/
@@ -39,12 +44,14 @@ module.exports = function(context) {
if (definition) {
- definition = definition.node;
- if (definition.type === "VariableDeclarator") {
- definition = definition.parent;
+ var node = definition.node;
+ if (node.type === "VariableDeclarator") {
+ node = node.parent;
+ } else if (definition.type === "Parameter" && node.type === "FunctionDeclaration") {
+ return false;
}
- return definition.parent.type.indexOf("Export") === 0;
+ return node.parent.type.indexOf("Export") === 0;
} else {
return false;
}
@@ -52,7 +59,7 @@ module.exports = function(context) {
/**
* Determines if a reference is a read operation.
- * @param {Reference} ref - an escope Reference
+ * @param {Reference} ref - An escope Reference
* @returns {Boolean} whether the given reference represents a read operation
* @private
*/
@@ -61,120 +68,185 @@ module.exports = function(context) {
}
/**
- * Determine if an identifier is referencing the enclosing function name.
- * @param {Reference} ref The reference to check.
+ * Determine if an identifier is referencing an enclosing function name.
+ * @param {Reference} ref - The reference to check.
+ * @param {ASTNode[]} nodes - The candidate function nodes.
* @returns {boolean} True if it's a self-reference, false if not.
* @private
*/
- function isSelfReference(ref) {
+ function isSelfReference(ref, nodes) {
+ var scope = ref.from;
+
+ while (scope != null) {
+ if (nodes.indexOf(scope.block) >= 0) {
+ return true;
+ }
- if (ref.from.type === "function" && ref.from.block.id) {
- return ref.identifier.name === ref.from.block.id.name;
+ scope = scope.upper;
}
return false;
}
/**
- * Determines if a reference should be counted as a read. A reference should
- * be counted only if it's a read and it's not a reference to the containing
- * function declaration name.
- * @param {Reference} ref The reference to check.
- * @returns {boolean} True if it's a value read reference, false if not.
+ * Determines if the variable is used.
+ * @param {Variable} variable - The variable to check.
+ * @param {Reference[]} references - The variable references to check.
+ * @returns {boolean} True if the variable is used
+ */
+ function isUsedVariable(variable, references) {
+ var functionNodes = variable.defs.filter(function (def) {
+ return def.type === "FunctionName";
+ }).map(function (def) {
+ return def.node;
+ }),
+ isFunctionDefinition = functionNodes.length > 0;
+
+ return references.some(function (ref) {
+ return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes));
+ });
+ }
+
+ /**
+ * Gets unresolved references.
+ * They contains var's, function's, and explicit global variable's.
+ * If `config.vars` is not "all", returns empty map.
+ * @param {Scope} scope - the global scope.
+ * @returns {object} Unresolved references. Keys of the object is its variable name. Values of the object is an array of its references.
* @private
*/
- function isValidReadRef(ref) {
- return isReadRef(ref) && !isSelfReference(ref);
+ function collectUnresolvedReferences(scope) {
+ var unresolvedRefs = Object.create(null);
+
+ if (config.vars === "all") {
+ for (var i = 0, l = scope.through.length; i < l; ++i) {
+ var ref = scope.through[i];
+ var name = ref.identifier.name;
+
+ if (isReadRef(ref)) {
+ if (!unresolvedRefs[name]) {
+ unresolvedRefs[name] = [];
+ }
+ unresolvedRefs[name].push(ref);
+ }
+ }
+ }
+
+ return unresolvedRefs;
}
/**
- * Gets an array of local variables without read references.
- * @param {Scope} scope - an escope Scope object
- * @returns {Variable[]} most of the local variables with no read references
+ * Gets an array of variables without read references.
+ * @param {Scope} scope - an escope Scope object.
+ * @param {object} unresolvedRefs - a map of each variable name and its references.
+ * @param {Variable[]} unusedVars - an array that saving result.
+ * @returns {Variable[]} unused variables of the scope and descendant scopes.
* @private
*/
- function getUnusedLocals(scope) {
- var unused = [];
+ function collectUnusedVariables(scope, unresolvedRefs, unusedVars) {
var variables = scope.variables;
+ var childScopes = scope.childScopes;
+ var i, l;
- if (scope.type !== "global" && scope.type !== "TDZ") {
- for (var i = 0, l = variables.length; i < l; ++i) {
+ if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) {
+ for (i = 0, l = variables.length; i < l; ++i) {
+ var variable = variables[i];
+ // skip a variable of class itself name in the class scope
+ if (scope.type === "class" && scope.block.id === variable.identifiers[0]) {
+ continue;
+ }
// skip function expression names
- if (scope.functionExpressionScope || variables[i].eslintUsed) {
+ if (scope.functionExpressionScope || variable.eslintUsed) {
continue;
}
// skip implicit "arguments" variable
- if (scope.type === "function" && variables[i].name === "arguments" && variables[i].identifiers.length === 0) {
+ if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
continue;
}
- var def = variables[i].defs[0],
- type = def.type;
+ // explicit global variables don't have definitions.
+ var def = variable.defs[0];
+ if (def != null) {
+ var type = def.type;
- // skip catch variables
- if (type === "CatchClause") {
- continue;
- }
+ // skip catch variables
+ if (type === "CatchClause") {
+ continue;
+ }
- // skip any setter argument
- if (type === "Parameter" && def.node.parent.type === "Property" && def.node.parent.kind === "set") {
- continue;
- }
+ // skip any setter argument
+ if (type === "Parameter" && def.node.parent.type === "Property" && def.node.parent.kind === "set") {
+ continue;
+ }
- // if "args" option is "none", skip any parameter
- if (config.args === "none" && type === "Parameter") {
- continue;
- }
+ // if "args" option is "none", skip any parameter
+ if (config.args === "none" && type === "Parameter") {
+ continue;
+ }
- // if "args" option is "after-used", skip all but the last parameter
- if (config.args === "after-used" && type === "Parameter" && variables[i].defs[0].index < variables[i].defs[0].node.params.length - 1) {
- continue;
+ // if "args" option is "after-used", skip all but the last parameter
+ if (config.args === "after-used" && type === "Parameter" && def.index < def.node.params.length - 1) {
+ continue;
+ }
}
- if (variables[i].references.filter(isValidReadRef).length === 0 && !isExported(variables[i])) {
- unused.push(variables[i]);
+ // On global, variables without let/const/class are unresolved.
+ var references = (scope.type === "global" ? unresolvedRefs[variable.name] : null) || variable.references;
+ if (!isUsedVariable(variable, references) && !isExported(variable)) {
+ unusedVars.push(variable);
}
}
}
- return [].concat.apply(unused, scope.childScopes.map(getUnusedLocals));
+ for (i = 0, l = childScopes.length; i < l; ++i) {
+ collectUnusedVariables(childScopes[i], unresolvedRefs, unusedVars);
+ }
+
+ return unusedVars;
}
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
return {
"Program:exit": function(programNode) {
var globalScope = context.getScope();
- var unused = getUnusedLocals(globalScope);
- var i, l;
-
- // determine unused globals
- if (config.vars === "all") {
- var unresolvedRefs = globalScope.through.filter(isValidReadRef).map(function(ref) {
- return ref.identifier.name;
- });
-
- for (i = 0, l = globalScope.variables.length; i < l; ++i) {
- if (unresolvedRefs.indexOf(globalScope.variables[i].name) < 0 &&
- !globalScope.variables[i].eslintUsed && !isExported(globalScope.variables[i])) {
- unused.push(globalScope.variables[i]);
- }
- }
- }
+ var unresolvedRefs = collectUnresolvedReferences(globalScope);
+ var unusedVars = collectUnusedVariables(globalScope, unresolvedRefs, []);
- for (i = 0, l = unused.length; i < l; ++i) {
- if (unused[i].eslintExplicitGlobal) {
- context.report(programNode, MESSAGE, unused[i]);
- } else if (unused[i].defs.length > 0) {
-
- // TODO: Remove when https://github.com/estools/escope/issues/49 is resolved
- if (unused[i].defs[0].type === "ClassName") {
- continue;
- }
+ for (var i = 0, l = unusedVars.length; i < l; ++i) {
+ var unusedVar = unusedVars[i];
- context.report(unused[i].identifiers[0], MESSAGE, unused[i]);
+ if (unusedVar.eslintExplicitGlobal) {
+ context.report(programNode, MESSAGE, unusedVar);
+ } else if (unusedVar.defs.length > 0) {
+ context.report(unusedVar.identifiers[0], MESSAGE, unusedVar);
}
}
}
};
};
+
+module.exports.schema = [
+ {
+ "oneOf": [
+ {
+ "enum": ["all", "local"]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "vars": {
+ "enum": ["all", "local"]
+ },
+ "args": {
+ "enum": ["all", "after-used", "none"]
+ }
+ }
+ }
+ ]
+ }
+];