Skip to content

Commit

Permalink
Update: add a fixer for certain statically-verifiable eqeqeq cases (#…
Browse files Browse the repository at this point in the history
…7389)

* Update: add a fixer for certain statically-verifiable `eqeqeq` cases

* Add note to docs to clarify which cases can be fixed
  • Loading branch information
not-an-aardvark authored and nzakas committed Nov 1, 2016
1 parent 0dea0ac commit e58cead
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 25 deletions.
4 changes: 4 additions & 0 deletions docs/rules/eqeqeq.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Require === and !== (eqeqeq)

(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixes problems reported by this rule.

It is considered good practice to use the type-safe equality operators `===` and `!==` instead of their regular counterparts `==` and `!=`.

The reason for this is that `==` and `!=` do type coercion which follows the rather obscure [Abstract Equality Comparison Algorithm](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3).
Expand Down Expand Up @@ -27,6 +29,8 @@ if ("" == text) { }
if (obj.getStuff() != undefined) { }
```

The `--fix` option on the command line automatically fixes some problems reported by this rule. A problem is only fixed if one of the operands is a `typeof` expression, or if both operands are literals with the same type.

## Options

### always
Expand Down
26 changes: 19 additions & 7 deletions lib/rules/eqeqeq.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ module.exports = {
additionalItems: false
}
]
}
},

fixable: "code"
},

create(context) {
Expand Down Expand Up @@ -118,16 +120,26 @@ module.exports = {
/**
* Reports a message for this rule.
* @param {ASTNode} node The binary expression node that was checked
* @param {string} message The message to report
* @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
* @returns {void}
* @private
*/
function report(node, message) {
function report(node, expectedOperator) {
context.report({
node,
loc: getOperatorLocation(node),
message,
data: { op: node.operator.charAt(0) }
message: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.",
data: {expectedOperator, actualOperator: node.operator},
fix(fixer) {

// If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);

return fixer.replaceText(operatorToken, expectedOperator);
}
return null;
}
});
}

Expand All @@ -137,7 +149,7 @@ module.exports = {

if (node.operator !== "==" && node.operator !== "!=") {
if (enforceInverseRuleForNull && isNull) {
report(node, "Expected '{{op}}=' and instead saw '{{op}}=='.");
report(node, node.operator.slice(0, -1));
}
return;
}
Expand All @@ -151,7 +163,7 @@ module.exports = {
return;
}

report(node, "Expected '{{op}}==' and instead saw '{{op}}='.");
report(node, `${node.operator}=`);
}
};

Expand Down
38 changes: 20 additions & 18 deletions tests/lib/rules/eqeqeq.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,33 @@ ruleTester.run("eqeqeq", rule, {
invalid: [
{ code: "a == b", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "a != b", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", options: ["always"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'string' != typeof a", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "true == true", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "2 == 3", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "2 == 3", options: ["always"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", options: ["always"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", output: "typeof a === 'number'", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", output: "typeof a === 'number'", options: ["always"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'string' != typeof a", output: "'string' !== typeof a", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "true == true", output: "true === true", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "2 == 3", output: "2 === 3", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "2 == 3", output: "2 === 3", options: ["always"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", output: "'hello' !== 'world'", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", output: "'hello' !== 'world'", options: ["always"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "a == null", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "a == null", options: ["always"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "null != a", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "true == 1", options: ["smart"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "0 != '1'", options: ["smart"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "'wee' == /wee/", options: ["smart"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'string' != typeof a", options: ["allow-null"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", options: ["allow-null"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "2 == 3", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "true == true", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "typeof a == 'number'", output: "typeof a === 'number'", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "'string' != typeof a", output: "'string' !== typeof a", options: ["allow-null"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "'hello' != 'world'", output: "'hello' !== 'world'", options: ["allow-null"], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "2 == 3", output: "2 === 3", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "true == true", output: "true === true", options: ["allow-null"], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "true == null", options: ["always", {null: "always"}], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "true != null", options: ["always", {null: "always"}], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "null == null", options: ["always", {null: "always"}], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "null != null", options: ["always", {null: "always"}], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "null == null", output: "null === null", options: ["always", {null: "always"}], errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
{ code: "null != null", output: "null !== null", options: ["always", {null: "always"}], errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression"}] },
{ code: "true === null", options: ["always", {null: "never"}], errors: [{ message: "Expected '==' and instead saw '==='.", type: "BinaryExpression"}] },
{ code: "true !== null", options: ["always", {null: "never"}], errors: [{ message: "Expected '!=' and instead saw '!=='.", type: "BinaryExpression"}] },
{ code: "null === null", options: ["always", {null: "never"}], errors: [{ message: "Expected '==' and instead saw '==='.", type: "BinaryExpression"}] },
{ code: "null !== null", options: ["always", {null: "never"}], errors: [{ message: "Expected '!=' and instead saw '!=='.", type: "BinaryExpression"}] },
{ code: "null === null", output: "null == null", options: ["always", {null: "never"}], errors: [{ message: "Expected '==' and instead saw '==='.", type: "BinaryExpression"}] },
{ code: "null !== null", output: "null != null", options: ["always", {null: "never"}], errors: [{ message: "Expected '!=' and instead saw '!=='.", type: "BinaryExpression"}] },
{ code: "a\n==\nb", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression", line: 2 }] },
{ code: "(a) == b", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression", line: 1 }] },
{ code: "(a) != b", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression", line: 1 }] },
Expand All @@ -83,5 +83,7 @@ ruleTester.run("eqeqeq", rule, {
{ code: "(a) != (b)", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression", line: 1 }] },
{ code: "(a == b) == (c)", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression", line: 1 }, { message: "Expected '===' and instead saw '=='.", type: "BinaryExpression", line: 1 }] },
{ code: "(a != b) != (c)", errors: [{ message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression", line: 1 }, { message: "Expected '!==' and instead saw '!='.", type: "BinaryExpression", line: 1 }] }
]

// If no output is provided, assert that the output is the same as the original code.
].map(invalidCase => Object.assign({output: invalidCase.code}, invalidCase))
});

0 comments on commit e58cead

Please sign in to comment.