You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
5.6 KiB
174 lines
5.6 KiB
/** |
|
* @fileoverview Rule to flag statements that use != and == instead of !== and === |
|
* @author Nicholas C. Zakas |
|
*/ |
|
|
|
"use strict"; |
|
|
|
//------------------------------------------------------------------------------ |
|
// Requirements |
|
//------------------------------------------------------------------------------ |
|
|
|
const astUtils = require("./utils/ast-utils"); |
|
|
|
//------------------------------------------------------------------------------ |
|
// Rule Definition |
|
//------------------------------------------------------------------------------ |
|
|
|
/** @type {import('../shared/types').Rule} */ |
|
module.exports = { |
|
meta: { |
|
type: "suggestion", |
|
|
|
docs: { |
|
description: "Require the use of `===` and `!==`", |
|
recommended: false, |
|
url: "https://eslint.org/docs/rules/eqeqeq" |
|
}, |
|
|
|
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 |
|
} |
|
] |
|
}, |
|
|
|
fixable: "code", |
|
|
|
messages: { |
|
unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'." |
|
} |
|
}, |
|
|
|
create(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 |
|
* @returns {boolean} if the node is a typeof expression |
|
*/ |
|
function isTypeOf(node) { |
|
return node.type === "UnaryExpression" && node.operator === "typeof"; |
|
} |
|
|
|
/** |
|
* Checks if either operand of a binary expression is a typeof operation |
|
* @param {ASTNode} node The node to check |
|
* @returns {boolean} if one of the operands is typeof |
|
* @private |
|
*/ |
|
function isTypeOfBinary(node) { |
|
return isTypeOf(node.left) || isTypeOf(node.right); |
|
} |
|
|
|
/** |
|
* Checks if operands are literals of the same type (via typeof) |
|
* @param {ASTNode} node The node to check |
|
* @returns {boolean} if operands are of same type |
|
* @private |
|
*/ |
|
function areLiteralsAndSameType(node) { |
|
return node.left.type === "Literal" && node.right.type === "Literal" && |
|
typeof node.left.value === typeof node.right.value; |
|
} |
|
|
|
/** |
|
* Checks if one of the operands is a literal null |
|
* @param {ASTNode} node The node to check |
|
* @returns {boolean} if operands are null |
|
* @private |
|
*/ |
|
function isNullCheck(node) { |
|
return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left); |
|
} |
|
|
|
/** |
|
* Reports a message for this rule. |
|
* @param {ASTNode} node The binary expression node that was checked |
|
* @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==') |
|
* @returns {void} |
|
* @private |
|
*/ |
|
function report(node, expectedOperator) { |
|
const operatorToken = sourceCode.getFirstTokenBetween( |
|
node.left, |
|
node.right, |
|
token => token.value === node.operator |
|
); |
|
|
|
context.report({ |
|
node, |
|
loc: operatorToken.loc, |
|
messageId: "unexpected", |
|
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)) { |
|
return fixer.replaceText(operatorToken, expectedOperator); |
|
} |
|
return null; |
|
} |
|
}); |
|
} |
|
|
|
return { |
|
BinaryExpression(node) { |
|
const isNull = isNullCheck(node); |
|
|
|
if (node.operator !== "==" && node.operator !== "!=") { |
|
if (enforceInverseRuleForNull && isNull) { |
|
report(node, node.operator.slice(0, -1)); |
|
} |
|
return; |
|
} |
|
|
|
if (config === "smart" && (isTypeOfBinary(node) || |
|
areLiteralsAndSameType(node) || isNull)) { |
|
return; |
|
} |
|
|
|
if (!enforceRuleForNull && isNull) { |
|
return; |
|
} |
|
|
|
report(node, `${node.operator}=`); |
|
} |
|
}; |
|
|
|
} |
|
};
|
|
|