Skip to content

Commit

Permalink
Update: no-unused-vars ignores read it modifies itself (fixes #6348) (
Browse files Browse the repository at this point in the history
#6535)

* Update: `no-unused-vars` ignores read it modifies itself (fixes #6348)

`no-unused-vars` comes to ignore a read of a modification of itself.

- A read in RHS of an assignment to itself (e.g. `a = a + 1`)
- A read in LHS of an assignment (e.g. `a += 1`)
- Increments/Decrements (e.g. `a++`)

Those are ignored only if those exist at the direct child of an
ExpressionStatement node. This is in order to avoid false positive such
`foo(a = a + 1)`

* Fix: remove unused variables which were found by this fix.

* Chore: add some tests
  • Loading branch information
mysticatea authored and nzakas committed Jun 28, 2016
1 parent d601f6b commit 1290657
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 16 deletions.
7 changes: 6 additions & 1 deletion docs/rules/no-unused-vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ Examples of **incorrect** code for this rule:
/*eslint no-unused-vars: "error"*/
/*global some_unused_var*/

//It checks variables you have defined as global
// It checks variables you have defined as global
some_unused_var = 42;

var x;

// Write-only variables are not considered as used.
var y = 10;
y = 5;

// A read for a modification of itself is not considered as used.
var z = 0;
z = z + 1;

// By default, unused arguments cause warnings.
(function(foo) {
return 5;
Expand Down
5 changes: 1 addition & 4 deletions lib/config/environments.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
// Requirements
//------------------------------------------------------------------------------

var debug = require("debug"),
envs = require("../../conf/environments");
var envs = require("../../conf/environments");

//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------

debug = debug("eslint:enviroments");

var environments = Object.create(null);

/**
Expand Down
80 changes: 78 additions & 2 deletions lib/rules/no-unused-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,73 @@ module.exports = {
return false;
}

/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* @param {escope.Reference} ref - A reference to check.
* @param {ASTNode} prevRhsNode - The previous RHS node.
* @returns {ASTNode} The RHS node.
*/
function getRhsNode(ref, prevRhsNode) {
var id = ref.identifier;
var parent = id.parent;
var granpa = parent.parent;

/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
if (prevRhsNode &&
prevRhsNode.range[0] <= id.range[0] &&
prevRhsNode.range[1] >= id.range[1]
) {
return prevRhsNode;
}

if (parent.type === "AssignmentExpression" &&
granpa.type === "ExpressionStatement" &&
id === parent.left
) {
return parent.right;
}
return null;
}

/**
* Checks whether a given reference is a read to update itself or not.
*
* @param {escope.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.
*/
function isReadForItself(ref, rhsNode) {
var id = ref.identifier;
var parent = id.parent;
var 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 &&
rhsNode.range[0] <= id.range[0] &&
rhsNode.range[1] >= id.range[1]
)
);
}

/**
* Determines if the variable is used.
* @param {Variable} variable - The variable to check.
Expand All @@ -164,10 +231,19 @@ module.exports = {
}).map(function(def) {
return def.node;
}),
isFunctionDefinition = functionNodes.length > 0;
isFunctionDefinition = functionNodes.length > 0,
rhsNode = null;

return variable.references.some(function(ref) {
return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes));
var forItself = isReadForItself(ref, rhsNode);

rhsNode = getRhsNode(ref, rhsNode);

return (
isReadRef(ref) &&
!forItself &&
!(isFunctionDefinition && isSelfReference(ref, functionNodes))
);
});
}

Expand Down
7 changes: 0 additions & 7 deletions tests/lib/config/environments.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@
//------------------------------------------------------------------------------

var assert = require("chai").assert,
proxyquire = require("proxyquire"),
envs = require("../../../conf/environments"),
Environments = require("../../../lib/config/environments");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

proxyquire = proxyquire.noCallThru().noPreserveCache();

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
Expand Down
22 changes: 20 additions & 2 deletions tests/lib/rules/no-unused-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,15 @@ ruleTester.run("no-unused-vars", rule, {
{
code: "try{}catch(err){}",
options: [{vars: "all", args: "all"}]
}
},

// https://github.com/eslint/eslint/issues/6348
{code: "var a = 0, b; b = a = a + 1; foo(b);"},
{code: "var a = 0, b; b = a += a + 1; foo(b);"},
{code: "var a = 0, b; b = a++; foo(b);"},
{code: "function foo(a) { var b = a = a + 1; bar(b) } foo();"},
{code: "function foo(a) { var b = a += a + 1; bar(b) } foo();"},
{code: "function foo(a) { var b = a++; bar(b) } foo();"}
],
invalid: [
{ code: "function foox() { return foox(); }", errors: [{ message: "'foox' is defined but never used", type: "Identifier"}] },
Expand Down Expand Up @@ -336,6 +343,17 @@ ruleTester.run("no-unused-vars", rule, {
}
],
errors: [{message: "'err' is defined but never used"}]
}
},

// Ignore reads for modifications to itself: https://github.com/eslint/eslint/issues/6348
{code: "var a = 0; a = a + 1;", errors: [{message: "'a' is defined but never used"}]},
{code: "var a = 0; a = a + a;", errors: [{message: "'a' is defined but never used"}]},
{code: "var a = 0; a += a + 1;", errors: [{message: "'a' is defined but never used"}]},
{code: "var a = 0; a++;", errors: [{message: "'a' is defined but never used"}]},
{code: "function foo(a) { a = a + 1 } foo();", errors: [{message: "'a' is defined but never used"}]},
{code: "function foo(a) { a += a + 1 } foo();", errors: [{message: "'a' is defined but never used"}]},
{code: "function foo(a) { a++ } foo();", errors: [{message: "'a' is defined but never used"}]},
{code: "var a = 3; a = a * 5 + 6;", errors: [{message: "'a' is defined but never used"}]},
{code: "var a = 2, b = 4; a = a * 2 + b;", errors: [{message: "'a' is defined but never used"}]}
]
});

0 comments on commit 1290657

Please sign in to comment.