--- /dev/null
+/**
+ * @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
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+
+ docs: {
+ description: "require the use of `===` and `!==`",
+ category: "Best Practices",
+ 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}=`);
+ }
+ };
+
+ }
+};