space-infix-ops.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /**
  2. * @fileoverview Require spaces around infix operators
  3. * @author Michael Ficarra
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. const { isEqToken } = require("./utils/ast-utils");
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. /** @type {import('../types').Rule.RuleModule} */
  12. module.exports = {
  13. meta: {
  14. deprecated: {
  15. message: "Formatting rules are being moved out of ESLint core.",
  16. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  17. deprecatedSince: "8.53.0",
  18. availableUntil: "11.0.0",
  19. replacedBy: [
  20. {
  21. message:
  22. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  23. url: "https://eslint.style/guide/migration",
  24. plugin: {
  25. name: "@stylistic/eslint-plugin",
  26. url: "https://eslint.style",
  27. },
  28. rule: {
  29. name: "space-infix-ops",
  30. url: "https://eslint.style/rules/space-infix-ops",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description: "Require spacing around infix operators",
  38. recommended: false,
  39. url: "https://eslint.org/docs/latest/rules/space-infix-ops",
  40. },
  41. fixable: "whitespace",
  42. schema: [
  43. {
  44. type: "object",
  45. properties: {
  46. int32Hint: {
  47. type: "boolean",
  48. default: false,
  49. },
  50. },
  51. additionalProperties: false,
  52. },
  53. ],
  54. messages: {
  55. missingSpace: "Operator '{{operator}}' must be spaced.",
  56. },
  57. },
  58. create(context) {
  59. const int32Hint = context.options[0]
  60. ? context.options[0].int32Hint === true
  61. : false;
  62. const sourceCode = context.sourceCode;
  63. /**
  64. * Returns the first token which violates the rule
  65. * @param {ASTNode} left The left node of the main node
  66. * @param {ASTNode} right The right node of the main node
  67. * @param {string} op The operator of the main node
  68. * @returns {Object} The violator token or null
  69. * @private
  70. */
  71. function getFirstNonSpacedToken(left, right, op) {
  72. const operator = sourceCode.getFirstTokenBetween(
  73. left,
  74. right,
  75. token => token.value === op,
  76. );
  77. const prev = sourceCode.getTokenBefore(operator);
  78. const next = sourceCode.getTokenAfter(operator);
  79. if (
  80. !sourceCode.isSpaceBetweenTokens(prev, operator) ||
  81. !sourceCode.isSpaceBetweenTokens(operator, next)
  82. ) {
  83. return operator;
  84. }
  85. return null;
  86. }
  87. /**
  88. * Reports an AST node as a rule violation
  89. * @param {ASTNode} mainNode The node to report
  90. * @param {Object} culpritToken The token which has a problem
  91. * @returns {void}
  92. * @private
  93. */
  94. function report(mainNode, culpritToken) {
  95. context.report({
  96. node: mainNode,
  97. loc: culpritToken.loc,
  98. messageId: "missingSpace",
  99. data: {
  100. operator: culpritToken.value,
  101. },
  102. fix(fixer) {
  103. const previousToken =
  104. sourceCode.getTokenBefore(culpritToken);
  105. const afterToken = sourceCode.getTokenAfter(culpritToken);
  106. let fixString = "";
  107. if (culpritToken.range[0] - previousToken.range[1] === 0) {
  108. fixString = " ";
  109. }
  110. fixString += culpritToken.value;
  111. if (afterToken.range[0] - culpritToken.range[1] === 0) {
  112. fixString += " ";
  113. }
  114. return fixer.replaceText(culpritToken, fixString);
  115. },
  116. });
  117. }
  118. /**
  119. * Check if the node is binary then report
  120. * @param {ASTNode} node node to evaluate
  121. * @returns {void}
  122. * @private
  123. */
  124. function checkBinary(node) {
  125. const leftNode = node.left.typeAnnotation
  126. ? node.left.typeAnnotation
  127. : node.left;
  128. const rightNode = node.right;
  129. // search for = in AssignmentPattern nodes
  130. const operator = node.operator || "=";
  131. const nonSpacedNode = getFirstNonSpacedToken(
  132. leftNode,
  133. rightNode,
  134. operator,
  135. );
  136. if (nonSpacedNode) {
  137. if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
  138. report(node, nonSpacedNode);
  139. }
  140. }
  141. }
  142. /**
  143. * Check if the node is conditional
  144. * @param {ASTNode} node node to evaluate
  145. * @returns {void}
  146. * @private
  147. */
  148. function checkConditional(node) {
  149. const nonSpacedConsequentNode = getFirstNonSpacedToken(
  150. node.test,
  151. node.consequent,
  152. "?",
  153. );
  154. const nonSpacedAlternateNode = getFirstNonSpacedToken(
  155. node.consequent,
  156. node.alternate,
  157. ":",
  158. );
  159. if (nonSpacedConsequentNode) {
  160. report(node, nonSpacedConsequentNode);
  161. }
  162. if (nonSpacedAlternateNode) {
  163. report(node, nonSpacedAlternateNode);
  164. }
  165. }
  166. /**
  167. * Check if the node is a variable
  168. * @param {ASTNode} node node to evaluate
  169. * @returns {void}
  170. * @private
  171. */
  172. function checkVar(node) {
  173. const leftNode = node.id.typeAnnotation
  174. ? node.id.typeAnnotation
  175. : node.id;
  176. const rightNode = node.init;
  177. if (rightNode) {
  178. const nonSpacedNode = getFirstNonSpacedToken(
  179. leftNode,
  180. rightNode,
  181. "=",
  182. );
  183. if (nonSpacedNode) {
  184. report(node, nonSpacedNode);
  185. }
  186. }
  187. }
  188. return {
  189. AssignmentExpression: checkBinary,
  190. AssignmentPattern: checkBinary,
  191. BinaryExpression: checkBinary,
  192. LogicalExpression: checkBinary,
  193. ConditionalExpression: checkConditional,
  194. VariableDeclarator: checkVar,
  195. PropertyDefinition(node) {
  196. if (!node.value) {
  197. return;
  198. }
  199. /*
  200. * Because of computed properties and type annotations, some
  201. * tokens may exist between `node.key` and `=`.
  202. * Therefore, find the `=` from the right.
  203. */
  204. const operatorToken = sourceCode.getTokenBefore(
  205. node.value,
  206. isEqToken,
  207. );
  208. const leftToken = sourceCode.getTokenBefore(operatorToken);
  209. const rightToken = sourceCode.getTokenAfter(operatorToken);
  210. if (
  211. !sourceCode.isSpaceBetweenTokens(
  212. leftToken,
  213. operatorToken,
  214. ) ||
  215. !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken)
  216. ) {
  217. report(node, operatorToken);
  218. }
  219. },
  220. };
  221. },
  222. };