Skip to content

Commit

Permalink
Update: Rule "eqeqeq" to have more specific null handling (fixes #6543)…
Browse files Browse the repository at this point in the history
… (#6849)

* Update: Rule "eqeqeq" to have more specific null handling (fixes #6543)

* Fix schema as proposed from PR feedback
  • Loading branch information
sstur authored and gyandeeps committed Aug 11, 2016
1 parent e8cb7f9 commit 3e879fc
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 35 deletions.
29 changes: 10 additions & 19 deletions docs/rules/eqeqeq.md
Expand Up @@ -31,7 +31,7 @@ if (obj.getStuff() != undefined) { }

### always

The `"always"` option (default) enforces the use of `===` and `!==` in every situation.
The `"always"` option (default) enforces the use of `===` and `!==` in every situation (except when you opt-in to more specific handling of `null` [see below]).

Examples of **incorrect** code for the `"always"` option:

Expand Down Expand Up @@ -67,6 +67,13 @@ foo === null

```

This rule optionally takes a second argument, which should be an object with the following supported properties:

* `"null"`: Customize how this rule treats `null` literals. Possible values:
* `always` (default) - Always use === or !==.
* `never` - Never use === or !== with `null`.
* `ignore` - Do not apply this rule to `null`.

### smart

The `"smart"` option enforces the use of `===` and `!==` except for these cases:
Expand Down Expand Up @@ -105,26 +112,10 @@ foo == null

### allow-null

The `"allow-null"` option will enforce `===` and `!==` in your code with one exception - it permits comparing to `null` to check for `null` or `undefined` in a single expression.

Examples of **incorrect** code for the `"allow-null"` option:

```js
/*eslint eqeqeq: ["error", "allow-null"]*/

bananas != 1
typeof foo == 'undefined'
'hello' != 'world'
0 == 0
foo == undefined
```

Examples of **correct** code for the `"allow-null"` option:
**Deprecated:** Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell eslint to always enforce strict equality except when comparing with the `null` literal.

```js
/*eslint eqeqeq: ["error", "allow-null"]*/

foo == null
["error", "always", {"null": "ignore"}]
```

## When Not To Use It
Expand Down
79 changes: 64 additions & 15 deletions lib/rules/eqeqeq.js
Expand Up @@ -17,16 +17,50 @@ module.exports = {
recommended: false
},

schema: [
{
enum: ["always", "smart", "allow-null"]
}
]
schema: {
anyOf: [
{
type: "array",
items: [
{
enum: ["always"]
},
{
type: "object",
properties: {
null: {
enum: ["always", "never", "ignore"]
}
},
additionalProperties: false
}
],
additionalItems: false
},
{
type: "array",
items: [
{
enum: ["smart", "allow-null"]
}
],
additionalItems: false
}
]
}
},

create: function(context) {
const config = context.options[0] || "always";
const options = context.options[1] || {};
const sourceCode = context.getSourceCode();

const nullOption = (config === "always") ?
options.null || "always" :
"ignore";
const enforceRuleForNull = (nullOption === "always");
const enforceInverseRuleForNull = (nullOption === "never");

/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
Expand Down Expand Up @@ -81,28 +115,43 @@ module.exports = {
return {line: opToken.loc.start.line, column: opToken.loc.start.column};
}

/**
* Reports a message for this rule.
* @param {ASTNode} node The binary expression node that was checked
* @param {string} message The message to report
* @returns {void}
* @private
*/
function report(node, message) {
context.report({
node: node,
loc: getOperatorLocation(node),
message: message,
data: { op: node.operator.charAt(0) }
});
}

return {
BinaryExpression: function(node) {
const isNull = isNullCheck(node);

if (node.operator !== "==" && node.operator !== "!=") {
if (enforceInverseRuleForNull && isNull) {
report(node, "Expected '{{op}}=' and instead saw '{{op}}=='.");
}
return;
}

if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
areLiteralsAndSameType(node) || isNullCheck(node))) {
if (config === "smart" && (isTypeOfBinary(node) ||
areLiteralsAndSameType(node) || isNull)) {
return;
}

if (context.options[0] === "allow-null" && isNullCheck(node)) {
if (!enforceRuleForNull && isNull) {
return;
}

context.report({
node: node,
loc: getOperatorLocation(node),
message: "Expected '{{op}}=' and instead saw '{{op}}'.",
data: { op: node.operator }
});

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

Expand Down
21 changes: 20 additions & 1 deletion tests/lib/rules/eqeqeq.js
Expand Up @@ -31,7 +31,18 @@ ruleTester.run("eqeqeq", rule, {
{ code: "null == a", options: ["smart"] },
{ code: "a == null", options: ["smart"] },
{ code: "null == a", options: ["allow-null"] },
{ code: "a == null", options: ["allow-null"] }
{ code: "a == null", options: ["allow-null"] },
{ code: "a == null", options: ["always", {null: "ignore"}] },
{ code: "a != null", options: ["always", {null: "ignore"}] },
{ code: "a !== null", options: ["always", {null: "ignore"}] },
{ code: "a === null", options: ["always", {null: "always"}] },
{ code: "a !== null", options: ["always", {null: "always"}] },
{ code: "null === null", options: ["always", {null: "always"}] },
{ code: "null !== null", options: ["always", {null: "always"}] },
{ code: "a == null", options: ["always", {null: "never"}] },
{ code: "a != null", options: ["always", {null: "never"}] },
{ code: "null == null", options: ["always", {null: "never"}] },
{ code: "null != null", options: ["always", {null: "never"}] }
],
invalid: [
{ code: "a == b", errors: [{ message: "Expected '===' and instead saw '=='.", type: "BinaryExpression"}] },
Expand All @@ -55,6 +66,14 @@ ruleTester.run("eqeqeq", rule, {
{ 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: "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: "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: "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 Down

0 comments on commit 3e879fc

Please sign in to comment.