space-infix-ops.js 5.4 KB

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