no-unsafe-negation.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * @fileoverview Rule to disallow negating the left operand of relational operators
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Checks whether the given operator is `in` or `instanceof`
  15. * @param {string} op The operator type to check.
  16. * @returns {boolean} `true` if the operator is `in` or `instanceof`
  17. */
  18. function isInOrInstanceOfOperator(op) {
  19. return op === "in" || op === "instanceof";
  20. }
  21. /**
  22. * Checks whether the given operator is an ordering relational operator or not.
  23. * @param {string} op The operator type to check.
  24. * @returns {boolean} `true` if the operator is an ordering relational operator.
  25. */
  26. function isOrderingRelationalOperator(op) {
  27. return op === "<" || op === ">" || op === ">=" || op === "<=";
  28. }
  29. /**
  30. * Checks whether the given node is a logical negation expression or not.
  31. * @param {ASTNode} node The node to check.
  32. * @returns {boolean} `true` if the node is a logical negation expression.
  33. */
  34. function isNegation(node) {
  35. return node.type === "UnaryExpression" && node.operator === "!";
  36. }
  37. //------------------------------------------------------------------------------
  38. // Rule Definition
  39. //------------------------------------------------------------------------------
  40. /** @type {import('../types').Rule.RuleModule} */
  41. module.exports = {
  42. meta: {
  43. type: "problem",
  44. defaultOptions: [
  45. {
  46. enforceForOrderingRelations: false,
  47. },
  48. ],
  49. docs: {
  50. description:
  51. "Disallow negating the left operand of relational operators",
  52. recommended: true,
  53. url: "https://eslint.org/docs/latest/rules/no-unsafe-negation",
  54. },
  55. hasSuggestions: true,
  56. schema: [
  57. {
  58. type: "object",
  59. properties: {
  60. enforceForOrderingRelations: {
  61. type: "boolean",
  62. },
  63. },
  64. additionalProperties: false,
  65. },
  66. ],
  67. fixable: null,
  68. messages: {
  69. unexpected:
  70. "Unexpected negating the left operand of '{{operator}}' operator.",
  71. suggestNegatedExpression:
  72. "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.",
  73. suggestParenthesisedNegation:
  74. "Wrap negation in '()' to make the intention explicit. This preserves the current behavior.",
  75. },
  76. },
  77. create(context) {
  78. const sourceCode = context.sourceCode;
  79. const [{ enforceForOrderingRelations }] = context.options;
  80. return {
  81. BinaryExpression(node) {
  82. const operator = node.operator;
  83. const orderingRelationRuleApplies =
  84. enforceForOrderingRelations &&
  85. isOrderingRelationalOperator(operator);
  86. if (
  87. (isInOrInstanceOfOperator(operator) ||
  88. orderingRelationRuleApplies) &&
  89. isNegation(node.left) &&
  90. !astUtils.isParenthesised(sourceCode, node.left)
  91. ) {
  92. context.report({
  93. node,
  94. loc: node.left.loc,
  95. messageId: "unexpected",
  96. data: { operator },
  97. suggest: [
  98. {
  99. messageId: "suggestNegatedExpression",
  100. data: { operator },
  101. fix(fixer) {
  102. const negationToken =
  103. sourceCode.getFirstToken(node.left);
  104. const fixRange = [
  105. negationToken.range[1],
  106. node.range[1],
  107. ];
  108. const text = sourceCode.text.slice(
  109. fixRange[0],
  110. fixRange[1],
  111. );
  112. return fixer.replaceTextRange(
  113. fixRange,
  114. `(${text})`,
  115. );
  116. },
  117. },
  118. {
  119. messageId: "suggestParenthesisedNegation",
  120. fix(fixer) {
  121. return fixer.replaceText(
  122. node.left,
  123. `(${sourceCode.getText(node.left)})`,
  124. );
  125. },
  126. },
  127. ],
  128. });
  129. }
  130. },
  131. };
  132. },
  133. };