const astUtils = require("./utils/ast-utils");
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * Bag of data used for formatting the `unusedVar` lint message.
+ * @typedef {Object} UnusedVarMessageData
+ * @property {string} varName The name of the unused var.
+ * @property {'defined'|'assigned a value'} action Description of the vars state.
+ * @property {string} additional Any additional info to be appended at the end.
+ */
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
caughtErrorsIgnorePattern: {
type: "string"
}
- }
+ },
+ additionalProperties: false
}
]
}
- ]
+ ],
+
+ messages: {
+ unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}."
+ }
},
create(context) {
}
/**
- * Generate the warning message about the variable being
- * defined and unused, including the ignore pattern if configured.
+ * Generates the message data 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.
+ * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
*/
- function getDefinedMessage(unusedVar) {
+ function getDefinedMessageData(unusedVar) {
const defType = unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type;
let type;
let pattern;
pattern = config.varsIgnorePattern.toString();
}
- const additional = type ? ` Allowed unused ${type} must match ${pattern}.` : "";
+ const additional = type ? `. Allowed unused ${type} must match ${pattern}` : "";
- return `'{{name}}' is defined but never used.${additional}`;
+ return {
+ varName: unusedVar.name,
+ action: "defined",
+ 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.
+ * @param {Variable} unusedVar eslint-scope variable object.
+ * @returns {UnusedVarMessageData} The message data 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}`;
+ function getAssignedMessageData(unusedVar) {
+ const additional = config.varsIgnorePattern ? `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}` : "";
+
+ return {
+ varName: unusedVar.name,
+ action: "assigned a value",
+ additional
+ };
}
//--------------------------------------------------------------------------
}
+ /**
+ * Checks whether a node is a sibling of the rest property or not.
+ * @param {ASTNode} node a node to check
+ * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
+ */
+ function hasRestSibling(node) {
+ return node.type === "Property" &&
+ node.parent.type === "ObjectPattern" &&
+ REST_PROPERTY_TYPE.test(node.parent.properties[node.parent.properties.length - 1].type);
+ }
+
/**
* Determines if a variable has a sibling rest property
* @param {Variable} variable eslint-scope variable object.
*/
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)
- );
- });
+ const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent));
+ const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent));
+
+ return hasRestSiblingDefinition || hasRestSiblingReference;
}
return false;
function getRhsNode(ref, prevRhsNode) {
const id = ref.identifier;
const parent = id.parent;
- const granpa = parent.parent;
+ const grandparent = parent.parent;
const refScope = ref.from.variableScope;
const varScope = ref.resolved.scope.variableScope;
const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
}
if (parent.type === "AssignmentExpression" &&
- granpa.type === "ExpressionStatement" &&
+ grandparent.type === "ExpressionStatement" &&
id === parent.left &&
!canBeUsedLater
) {
/*
* If it encountered statements, this is a complex pattern.
- * Since analyzeing complex patterns is hard, this returns `true` to avoid false positive.
+ * Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
);
}
+ /**
+ * Checks whether a given node is unused expression or not.
+ * @param {ASTNode} node The node itself
+ * @returns {boolean} The node is an unused expression.
+ * @private
+ */
+ function isUnusedExpression(node) {
+ const parent = node.parent;
+
+ if (parent.type === "ExpressionStatement") {
+ return true;
+ }
+
+ if (parent.type === "SequenceExpression") {
+ const isLastExpression = parent.expressions[parent.expressions.length - 1] === node;
+
+ if (!isLastExpression) {
+ return true;
+ }
+ return isUnusedExpression(parent);
+ }
+
+ return false;
+ }
+
/**
* Checks whether a given reference is a read to update itself or not.
* @param {eslint-scope.Reference} ref A reference to check.
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++`
- (// in RHS of an assignment for itself. e.g. `a = a + 1`
- ((
+ (
+ (
parent.type === "AssignmentExpression" &&
- granpa.type === "ExpressionStatement" &&
- parent.left === id
+ parent.left === id &&
+ isUnusedExpression(parent)
) ||
+ (
+ parent.type === "UpdateExpression" &&
+ isUnusedExpression(parent)
+ )
+ ) ||
+
+ // in RHS of an assignment for itself. e.g. `a = a + 1`
(
- parent.type === "UpdateExpression" &&
- granpa.type === "ExpressionStatement"
- ) || rhsNode &&
- isInside(id, rhsNode) &&
- !isInsideOfStorableFunction(id, rhsNode)))
+ rhsNode &&
+ isInside(id, rhsNode) &&
+ !isInsideOfStorableFunction(id, rhsNode)
+ )
);
}
// Report the first declaration.
if (unusedVar.defs.length > 0) {
+
+ // report last write reference, https://github.com/eslint/eslint/issues/14324
+ const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
+
+ let referenceToReport;
+
+ if (writeReferences.length > 0) {
+ referenceToReport = writeReferences[writeReferences.length - 1];
+ }
+
context.report({
- node: unusedVar.identifiers[0],
- message: unusedVar.references.some(ref => ref.isWrite())
- ? getAssignedMessage()
- : getDefinedMessage(unusedVar),
- data: unusedVar
+ node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
+ messageId: "unusedVar",
+ data: unusedVar.references.some(ref => ref.isWrite())
+ ? getAssignedMessageData(unusedVar)
+ : getDefinedMessageData(unusedVar)
});
// If there are no regular declaration, report the first `/*globals*/` comment directive.
context.report({
node: programNode,
loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
- message: getDefinedMessage(unusedVar),
- data: unusedVar
+ messageId: "unusedVar",
+ data: getDefinedMessageData(unusedVar)
});
}
}