summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/no-unused-vars.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/eslint/lib/rules/no-unused-vars.js')
-rw-r--r--tools/node_modules/eslint/lib/rules/no-unused-vars.js647
1 files changed, 647 insertions, 0 deletions
diff --git a/tools/node_modules/eslint/lib/rules/no-unused-vars.js b/tools/node_modules/eslint/lib/rules/no-unused-vars.js
new file mode 100644
index 0000000000..05940d5932
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/no-unused-vars.js
@@ -0,0 +1,647 @@
+/**
+ * @fileoverview Rule to flag declared but unused variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const lodash = require("lodash");
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "disallow unused variables",
+ category: "Variables",
+ recommended: true
+ },
+
+ schema: [
+ {
+ oneOf: [
+ {
+ enum: ["all", "local"]
+ },
+ {
+ type: "object",
+ properties: {
+ vars: {
+ enum: ["all", "local"]
+ },
+ varsIgnorePattern: {
+ type: "string"
+ },
+ args: {
+ enum: ["all", "after-used", "none"]
+ },
+ ignoreRestSiblings: {
+ type: "boolean"
+ },
+ argsIgnorePattern: {
+ type: "string"
+ },
+ caughtErrors: {
+ enum: ["all", "none"]
+ },
+ caughtErrorsIgnorePattern: {
+ type: "string"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ const REST_PROPERTY_TYPE = /^(?:Experimental)?RestProperty$/;
+
+ const config = {
+ vars: "all",
+ args: "after-used",
+ ignoreRestSiblings: false,
+ caughtErrors: "none"
+ };
+
+ const firstOption = context.options[0];
+
+ if (firstOption) {
+ if (typeof firstOption === "string") {
+ config.vars = firstOption;
+ } else {
+ config.vars = firstOption.vars || config.vars;
+ config.args = firstOption.args || config.args;
+ config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
+ config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
+
+ if (firstOption.varsIgnorePattern) {
+ config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern);
+ }
+
+ if (firstOption.argsIgnorePattern) {
+ config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern);
+ }
+
+ if (firstOption.caughtErrorsIgnorePattern) {
+ config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern);
+ }
+ }
+ }
+
+ /**
+ * Generate the warning message about the variable being
+ * defined and unused, including the ignore pattern if configured.
+ * @param {Variable} unusedVar - eslint-scope variable object.
+ * @returns {string} The warning message to be used with this unused variable.
+ */
+ function getDefinedMessage(unusedVar) {
+ let type;
+ let pattern;
+
+ if (config.varsIgnorePattern) {
+ type = "vars";
+ pattern = config.varsIgnorePattern.toString();
+ }
+
+ if (unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type) {
+ const defType = unusedVar.defs[0].type;
+
+ if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) {
+ type = "args";
+ pattern = config.caughtErrorsIgnorePattern.toString();
+ } else if (defType === "Parameter" && config.argsIgnorePattern) {
+ type = "args";
+ pattern = config.argsIgnorePattern.toString();
+ }
+ }
+
+ const additional = type ? ` Allowed unused ${type} must match ${pattern}.` : "";
+
+ return `'{{name}}' is defined but never used.${additional}`;
+ }
+
+ /**
+ * Generate the warning message about the variable being
+ * assigned and unused, including the ignore pattern if configured.
+ * @returns {string} The warning message to be used with this unused variable.
+ */
+ function getAssignedMessage() {
+ const additional = config.varsIgnorePattern ? ` Allowed unused vars must match ${config.varsIgnorePattern.toString()}.` : "";
+
+ return `'{{name}}' is assigned a value but never used.${additional}`;
+ }
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ const STATEMENT_TYPE = /(?:Statement|Declaration)$/;
+
+ /**
+ * Determines if a given variable is being exported from a module.
+ * @param {Variable} variable - eslint-scope variable object.
+ * @returns {boolean} True if the variable is exported, false if not.
+ * @private
+ */
+ function isExported(variable) {
+
+ const definition = variable.defs[0];
+
+ if (definition) {
+
+ let node = definition.node;
+
+ if (node.type === "VariableDeclarator") {
+ node = node.parent;
+ } else if (definition.type === "Parameter") {
+ return false;
+ }
+
+ return node.parent.type.indexOf("Export") === 0;
+ }
+ return false;
+
+ }
+
+ /**
+ * Determines if a variable has a sibling rest property
+ * @param {Variable} variable - eslint-scope variable object.
+ * @returns {boolean} True if the variable is exported, false if not.
+ * @private
+ */
+ function hasRestSpreadSibling(variable) {
+ if (config.ignoreRestSiblings) {
+ return variable.defs.some(def => {
+ const propertyNode = def.name.parent;
+ const patternNode = propertyNode.parent;
+
+ return (
+ propertyNode.type === "Property" &&
+ patternNode.type === "ObjectPattern" &&
+ REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type)
+ );
+ });
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if a reference is a read operation.
+ * @param {Reference} ref - An eslint-scope Reference
+ * @returns {boolean} whether the given reference represents a read operation
+ * @private
+ */
+ function isReadRef(ref) {
+ return ref.isRead();
+ }
+
+ /**
+ * 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, nodes) {
+ let scope = ref.from;
+
+ while (scope) {
+ if (nodes.indexOf(scope.block) >= 0) {
+ return true;
+ }
+
+ scope = scope.upper;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks the position of given nodes.
+ *
+ * @param {ASTNode} inner - A node which is expected as inside.
+ * @param {ASTNode} outer - A node which is expected as outside.
+ * @returns {boolean} `true` if the `inner` node exists in the `outer` node.
+ * @private
+ */
+ function isInside(inner, outer) {
+ return (
+ inner.range[0] >= outer.range[0] &&
+ inner.range[1] <= outer.range[1]
+ );
+ }
+
+ /**
+ * If a given reference is left-hand side of an assignment, this gets
+ * the right-hand side node of the assignment.
+ *
+ * In the following cases, this returns null.
+ *
+ * - The reference is not the LHS of an assignment expression.
+ * - The reference is inside of a loop.
+ * - The reference is inside of a function scope which is different from
+ * the declaration.
+ *
+ * @param {eslint-scope.Reference} ref - A reference to check.
+ * @param {ASTNode} prevRhsNode - The previous RHS node.
+ * @returns {ASTNode|null} The RHS node or null.
+ * @private
+ */
+ function getRhsNode(ref, prevRhsNode) {
+ const id = ref.identifier;
+ const parent = id.parent;
+ const granpa = parent.parent;
+ const refScope = ref.from.variableScope;
+ const varScope = ref.resolved.scope.variableScope;
+ const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
+
+ /*
+ * Inherits the previous node if this reference is in the node.
+ * This is for `a = a + a`-like code.
+ */
+ if (prevRhsNode && isInside(id, prevRhsNode)) {
+ return prevRhsNode;
+ }
+
+ if (parent.type === "AssignmentExpression" &&
+ granpa.type === "ExpressionStatement" &&
+ id === parent.left &&
+ !canBeUsedLater
+ ) {
+ return parent.right;
+ }
+ return null;
+ }
+
+ /**
+ * Checks whether a given function node is stored to somewhere or not.
+ * If the function node is stored, the function can be used later.
+ *
+ * @param {ASTNode} funcNode - A function node to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} `true` if under the following conditions:
+ * - the funcNode is assigned to a variable.
+ * - the funcNode is bound as an argument of a function call.
+ * - the function is bound to a property and the object satisfies above conditions.
+ * @private
+ */
+ function isStorableFunction(funcNode, rhsNode) {
+ let node = funcNode;
+ let parent = funcNode.parent;
+
+ while (parent && isInside(parent, rhsNode)) {
+ switch (parent.type) {
+ case "SequenceExpression":
+ if (parent.expressions[parent.expressions.length - 1] !== node) {
+ return false;
+ }
+ break;
+
+ case "CallExpression":
+ case "NewExpression":
+ return parent.callee !== node;
+
+ case "AssignmentExpression":
+ case "TaggedTemplateExpression":
+ case "YieldExpression":
+ return true;
+
+ default:
+ if (STATEMENT_TYPE.test(parent.type)) {
+
+ /*
+ * If it encountered statements, this is a complex pattern.
+ * Since analyzeing complex patterns is hard, this returns `true` to avoid false positive.
+ */
+ return true;
+ }
+ }
+
+ node = parent;
+ parent = parent.parent;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a given Identifier node exists inside of a function node which can be used later.
+ *
+ * "can be used later" means:
+ * - the function is assigned to a variable.
+ * - the function is bound to a property and the object can be used later.
+ * - the function is bound as an argument of a function call.
+ *
+ * If a reference exists in a function which can be used later, the reference is read when the function is called.
+ *
+ * @param {ASTNode} id - An Identifier node to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later.
+ * @private
+ */
+ function isInsideOfStorableFunction(id, rhsNode) {
+ const funcNode = astUtils.getUpperFunction(id);
+
+ return (
+ funcNode &&
+ isInside(funcNode, rhsNode) &&
+ isStorableFunction(funcNode, rhsNode)
+ );
+ }
+
+ /**
+ * Checks whether a given reference is a read to update itself or not.
+ *
+ * @param {eslint-scope.Reference} ref - A reference to check.
+ * @param {ASTNode} rhsNode - The RHS node of the previous assignment.
+ * @returns {boolean} The reference is a read to update itself.
+ * @private
+ */
+ function isReadForItself(ref, rhsNode) {
+ const id = ref.identifier;
+ const parent = id.parent;
+ const granpa = parent.parent;
+
+ return ref.isRead() && (
+
+ // self update. e.g. `a += 1`, `a++`
+ (
+ parent.type === "AssignmentExpression" &&
+ granpa.type === "ExpressionStatement" &&
+ parent.left === id
+ ) ||
+ (
+ parent.type === "UpdateExpression" &&
+ granpa.type === "ExpressionStatement"
+ ) ||
+
+ // in RHS of an assignment for itself. e.g. `a = a + 1`
+ (
+ rhsNode &&
+ isInside(id, rhsNode) &&
+ !isInsideOfStorableFunction(id, rhsNode)
+ )
+ );
+ }
+
+ /**
+ * Determine if an identifier is used either in for-in loops.
+ *
+ * @param {Reference} ref - The reference to check.
+ * @returns {boolean} whether reference is used in the for-in loops
+ * @private
+ */
+ function isForInRef(ref) {
+ let target = ref.identifier.parent;
+
+
+ // "for (var ...) { return; }"
+ if (target.type === "VariableDeclarator") {
+ target = target.parent.parent;
+ }
+
+ if (target.type !== "ForInStatement") {
+ return false;
+ }
+
+ // "for (...) { return; }"
+ if (target.body.type === "BlockStatement") {
+ target = target.body.body[0];
+
+ // "for (...) return;"
+ } else {
+ target = target.body;
+ }
+
+ // For empty loop body
+ if (!target) {
+ return false;
+ }
+
+ return target.type === "ReturnStatement";
+ }
+
+ /**
+ * Determines if the variable is used.
+ * @param {Variable} variable - The variable to check.
+ * @returns {boolean} True if the variable is used
+ * @private
+ */
+ function isUsedVariable(variable) {
+ const functionNodes = variable.defs.filter(def => def.type === "FunctionName").map(def => def.node),
+ isFunctionDefinition = functionNodes.length > 0;
+ let rhsNode = null;
+
+ return variable.references.some(ref => {
+ if (isForInRef(ref)) {
+ return true;
+ }
+
+ const forItself = isReadForItself(ref, rhsNode);
+
+ rhsNode = getRhsNode(ref, rhsNode);
+
+ return (
+ isReadRef(ref) &&
+ !forItself &&
+ !(isFunctionDefinition && isSelfReference(ref, functionNodes))
+ );
+ });
+ }
+
+ /**
+ * Checks whether the given variable is the last parameter in the non-ignored parameters.
+ *
+ * @param {eslint-scope.Variable} variable - The variable to check.
+ * @returns {boolean} `true` if the variable is the last.
+ */
+ function isLastInNonIgnoredParameters(variable) {
+ const def = variable.defs[0];
+
+ // This is the last.
+ if (def.index === def.node.params.length - 1) {
+ return true;
+ }
+
+ // if all parameters preceded by this variable are ignored and unused, this is the last.
+ if (config.argsIgnorePattern) {
+ const params = context.getDeclaredVariables(def.node);
+ const posteriorParams = params.slice(params.indexOf(variable) + 1);
+
+ if (posteriorParams.every(v => v.references.length === 0 && config.argsIgnorePattern.test(v.name))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets an array of variables without read references.
+ * @param {Scope} scope - an eslint-scope Scope object.
+ * @param {Variable[]} unusedVars - an array that saving result.
+ * @returns {Variable[]} unused variables of the scope and descendant scopes.
+ * @private
+ */
+ function collectUnusedVariables(scope, unusedVars) {
+ const variables = scope.variables;
+ const childScopes = scope.childScopes;
+ let i, l;
+
+ if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) {
+ for (i = 0, l = variables.length; i < l; ++i) {
+ const 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 and variables marked with markVariableAsUsed()
+ if (scope.functionExpressionScope || variable.eslintUsed) {
+ continue;
+ }
+
+ // skip implicit "arguments" variable
+ if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) {
+ continue;
+ }
+
+ // explicit global variables don't have definitions.
+ const def = variable.defs[0];
+
+ if (def) {
+ const type = def.type;
+
+ // skip catch variables
+ if (type === "CatchClause") {
+ if (config.caughtErrors === "none") {
+ continue;
+ }
+
+ // skip ignored parameters
+ if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) {
+ continue;
+ }
+ }
+
+ if (type === "Parameter") {
+
+ // skip any setter argument
+ if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") {
+ continue;
+ }
+
+ // if "args" option is "none", skip any parameter
+ if (config.args === "none") {
+ continue;
+ }
+
+ // skip ignored parameters
+ if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) {
+ continue;
+ }
+
+ // if "args" option is "after-used", skip all but the last parameter
+ if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isLastInNonIgnoredParameters(variable)) {
+ continue;
+ }
+ } else {
+
+ // skip ignored variables
+ if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) {
+ continue;
+ }
+ }
+ }
+
+ if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) {
+ unusedVars.push(variable);
+ }
+ }
+ }
+
+ for (i = 0, l = childScopes.length; i < l; ++i) {
+ collectUnusedVariables(childScopes[i], unusedVars);
+ }
+
+ return unusedVars;
+ }
+
+ /**
+ * Gets the index of a given variable name in a given comment.
+ * @param {eslint-scope.Variable} variable - A variable to get.
+ * @param {ASTNode} comment - A comment node which includes the variable name.
+ * @returns {number} The index of the variable name's location.
+ * @private
+ */
+ function getColumnInComment(variable, comment) {
+ const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(variable.name)}(?:$|[\\s,:])`, "g");
+
+ // To ignore the first text "global".
+ namePattern.lastIndex = comment.value.indexOf("global") + 6;
+
+ // Search a given variable name.
+ const match = namePattern.exec(comment.value);
+
+ return match ? match.index + 1 : 0;
+ }
+
+ /**
+ * Creates the correct location of a given variables.
+ * The location is at its name string in a `/*global` comment.
+ *
+ * @param {eslint-scope.Variable} variable - A variable to get its location.
+ * @returns {{line: number, column: number}} The location object for the variable.
+ * @private
+ */
+ function getLocation(variable) {
+ const comment = variable.eslintExplicitGlobalComment;
+
+ return sourceCode.getLocFromIndex(comment.range[0] + 2 + getColumnInComment(variable, comment));
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program:exit"(programNode) {
+ const unusedVars = collectUnusedVariables(context.getScope(), []);
+
+ for (let i = 0, l = unusedVars.length; i < l; ++i) {
+ const unusedVar = unusedVars[i];
+
+ if (unusedVar.eslintExplicitGlobal) {
+ context.report({
+ node: programNode,
+ loc: getLocation(unusedVar),
+ message: getDefinedMessage(unusedVar),
+ data: unusedVar
+ });
+ } else if (unusedVar.defs.length > 0) {
+ context.report({
+ node: unusedVar.identifiers[0],
+ message: unusedVar.references.some(ref => ref.isWrite())
+ ? getAssignedMessage()
+ : getDefinedMessage(unusedVar),
+ data: unusedVar
+ });
+ }
+ }
+ }
+ };
+
+ }
+};