space-infix-ops.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * @fileoverview Require spaces around infix operators
  3. * @author Michael Ficarra
  4. */
  5. "use strict";
  6. const { isEqToken } = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../shared/types').Rule} */
  11. module.exports = {
  12. meta: {
  13. type: "layout",
  14. docs: {
  15. description: "Require spacing around infix operators",
  16. recommended: false,
  17. url: "https://eslint.org/docs/rules/space-infix-ops"
  18. },
  19. fixable: "whitespace",
  20. schema: [
  21. {
  22. type: "object",
  23. properties: {
  24. int32Hint: {
  25. type: "boolean",
  26. default: false
  27. }
  28. },
  29. additionalProperties: false
  30. }
  31. ],
  32. messages: {
  33. missingSpace: "Operator '{{operator}}' must be spaced."
  34. }
  35. },
  36. create(context) {
  37. const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
  38. const sourceCode = context.getSourceCode();
  39. /**
  40. * Returns the first token which violates the rule
  41. * @param {ASTNode} left The left node of the main node
  42. * @param {ASTNode} right The right node of the main node
  43. * @param {string} op The operator of the main node
  44. * @returns {Object} The violator token or null
  45. * @private
  46. */
  47. function getFirstNonSpacedToken(left, right, op) {
  48. const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
  49. const prev = sourceCode.getTokenBefore(operator);
  50. const next = sourceCode.getTokenAfter(operator);
  51. if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) {
  52. return operator;
  53. }
  54. return null;
  55. }
  56. /**
  57. * Reports an AST node as a rule violation
  58. * @param {ASTNode} mainNode The node to report
  59. * @param {Object} culpritToken The token which has a problem
  60. * @returns {void}
  61. * @private
  62. */
  63. function report(mainNode, culpritToken) {
  64. context.report({
  65. node: mainNode,
  66. loc: culpritToken.loc,
  67. messageId: "missingSpace",
  68. data: {
  69. operator: culpritToken.value
  70. },
  71. fix(fixer) {
  72. const previousToken = sourceCode.getTokenBefore(culpritToken);
  73. const afterToken = sourceCode.getTokenAfter(culpritToken);
  74. let fixString = "";
  75. if (culpritToken.range[0] - previousToken.range[1] === 0) {
  76. fixString = " ";
  77. }
  78. fixString += culpritToken.value;
  79. if (afterToken.range[0] - culpritToken.range[1] === 0) {
  80. fixString += " ";
  81. }
  82. return fixer.replaceText(culpritToken, fixString);
  83. }
  84. });
  85. }
  86. /**
  87. * Check if the node is binary then report
  88. * @param {ASTNode} node node to evaluate
  89. * @returns {void}
  90. * @private
  91. */
  92. function checkBinary(node) {
  93. const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left;
  94. const rightNode = node.right;
  95. // search for = in AssignmentPattern nodes
  96. const operator = node.operator || "=";
  97. const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
  98. if (nonSpacedNode) {
  99. if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
  100. report(node, nonSpacedNode);
  101. }
  102. }
  103. }
  104. /**
  105. * Check if the node is conditional
  106. * @param {ASTNode} node node to evaluate
  107. * @returns {void}
  108. * @private
  109. */
  110. function checkConditional(node) {
  111. const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
  112. const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
  113. if (nonSpacedConsequentNode) {
  114. report(node, nonSpacedConsequentNode);
  115. }
  116. if (nonSpacedAlternateNode) {
  117. report(node, nonSpacedAlternateNode);
  118. }
  119. }
  120. /**
  121. * Check if the node is a variable
  122. * @param {ASTNode} node node to evaluate
  123. * @returns {void}
  124. * @private
  125. */
  126. function checkVar(node) {
  127. const leftNode = (node.id.typeAnnotation) ? node.id.typeAnnotation : node.id;
  128. const rightNode = node.init;
  129. if (rightNode) {
  130. const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
  131. if (nonSpacedNode) {
  132. report(node, nonSpacedNode);
  133. }
  134. }
  135. }
  136. return {
  137. AssignmentExpression: checkBinary,
  138. AssignmentPattern: checkBinary,
  139. BinaryExpression: checkBinary,
  140. LogicalExpression: checkBinary,
  141. ConditionalExpression: checkConditional,
  142. VariableDeclarator: checkVar,
  143. PropertyDefinition(node) {
  144. if (!node.value) {
  145. return;
  146. }
  147. /*
  148. * Because of computed properties and type annotations, some
  149. * tokens may exist between `node.key` and `=`.
  150. * Therefore, find the `=` from the right.
  151. */
  152. const operatorToken = sourceCode.getTokenBefore(node.value, isEqToken);
  153. const leftToken = sourceCode.getTokenBefore(operatorToken);
  154. const rightToken = sourceCode.getTokenAfter(operatorToken);
  155. if (
  156. !sourceCode.isSpaceBetweenTokens(leftToken, operatorToken) ||
  157. !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken)
  158. ) {
  159. report(node, operatorToken);
  160. }
  161. }
  162. };
  163. }
  164. };