space-unary-ops.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /**
  2. * @fileoverview This rule should require or disallow spaces before or after unary operations.
  3. * @author Marcin Kumorek
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. /** @type {import('../types').Rule.RuleModule} */
  15. module.exports = {
  16. meta: {
  17. deprecated: {
  18. message: "Formatting rules are being moved out of ESLint core.",
  19. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  20. deprecatedSince: "8.53.0",
  21. availableUntil: "11.0.0",
  22. replacedBy: [
  23. {
  24. message:
  25. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  26. url: "https://eslint.style/guide/migration",
  27. plugin: {
  28. name: "@stylistic/eslint-plugin",
  29. url: "https://eslint.style",
  30. },
  31. rule: {
  32. name: "space-unary-ops",
  33. url: "https://eslint.style/rules/space-unary-ops",
  34. },
  35. },
  36. ],
  37. },
  38. type: "layout",
  39. docs: {
  40. description:
  41. "Enforce consistent spacing before or after unary operators",
  42. recommended: false,
  43. url: "https://eslint.org/docs/latest/rules/space-unary-ops",
  44. },
  45. fixable: "whitespace",
  46. schema: [
  47. {
  48. type: "object",
  49. properties: {
  50. words: {
  51. type: "boolean",
  52. default: true,
  53. },
  54. nonwords: {
  55. type: "boolean",
  56. default: false,
  57. },
  58. overrides: {
  59. type: "object",
  60. additionalProperties: {
  61. type: "boolean",
  62. },
  63. },
  64. },
  65. additionalProperties: false,
  66. },
  67. ],
  68. messages: {
  69. unexpectedBefore:
  70. "Unexpected space before unary operator '{{operator}}'.",
  71. unexpectedAfter:
  72. "Unexpected space after unary operator '{{operator}}'.",
  73. unexpectedAfterWord:
  74. "Unexpected space after unary word operator '{{word}}'.",
  75. wordOperator:
  76. "Unary word operator '{{word}}' must be followed by whitespace.",
  77. operator:
  78. "Unary operator '{{operator}}' must be followed by whitespace.",
  79. beforeUnaryExpressions:
  80. "Space is required before unary expressions '{{token}}'.",
  81. },
  82. },
  83. create(context) {
  84. const options = context.options[0] || { words: true, nonwords: false };
  85. const sourceCode = context.sourceCode;
  86. //--------------------------------------------------------------------------
  87. // Helpers
  88. //--------------------------------------------------------------------------
  89. /**
  90. * Check if the node is the first "!" in a "!!" convert to Boolean expression
  91. * @param {ASTnode} node AST node
  92. * @returns {boolean} Whether or not the node is first "!" in "!!"
  93. */
  94. function isFirstBangInBangBangExpression(node) {
  95. return (
  96. node &&
  97. node.type === "UnaryExpression" &&
  98. node.argument.operator === "!" &&
  99. node.argument &&
  100. node.argument.type === "UnaryExpression" &&
  101. node.argument.operator === "!"
  102. );
  103. }
  104. /**
  105. * Checks if an override exists for a given operator.
  106. * @param {string} operator Operator
  107. * @returns {boolean} Whether or not an override has been provided for the operator
  108. */
  109. function overrideExistsForOperator(operator) {
  110. return (
  111. options.overrides && Object.hasOwn(options.overrides, operator)
  112. );
  113. }
  114. /**
  115. * Gets the value that the override was set to for this operator
  116. * @param {string} operator Operator
  117. * @returns {boolean} Whether or not an override enforces a space with this operator
  118. */
  119. function overrideEnforcesSpaces(operator) {
  120. return options.overrides[operator];
  121. }
  122. /**
  123. * Verify Unary Word Operator has spaces after the word operator
  124. * @param {ASTnode} node AST node
  125. * @param {Object} firstToken first token from the AST node
  126. * @param {Object} secondToken second token from the AST node
  127. * @param {string} word The word to be used for reporting
  128. * @returns {void}
  129. */
  130. function verifyWordHasSpaces(node, firstToken, secondToken, word) {
  131. if (secondToken.range[0] === firstToken.range[1]) {
  132. context.report({
  133. node,
  134. messageId: "wordOperator",
  135. data: {
  136. word,
  137. },
  138. fix(fixer) {
  139. return fixer.insertTextAfter(firstToken, " ");
  140. },
  141. });
  142. }
  143. }
  144. /**
  145. * Verify Unary Word Operator doesn't have spaces after the word operator
  146. * @param {ASTnode} node AST node
  147. * @param {Object} firstToken first token from the AST node
  148. * @param {Object} secondToken second token from the AST node
  149. * @param {string} word The word to be used for reporting
  150. * @returns {void}
  151. */
  152. function verifyWordDoesntHaveSpaces(
  153. node,
  154. firstToken,
  155. secondToken,
  156. word,
  157. ) {
  158. if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
  159. if (secondToken.range[0] > firstToken.range[1]) {
  160. context.report({
  161. node,
  162. messageId: "unexpectedAfterWord",
  163. data: {
  164. word,
  165. },
  166. fix(fixer) {
  167. return fixer.removeRange([
  168. firstToken.range[1],
  169. secondToken.range[0],
  170. ]);
  171. },
  172. });
  173. }
  174. }
  175. }
  176. /**
  177. * Check Unary Word Operators for spaces after the word operator
  178. * @param {ASTnode} node AST node
  179. * @param {Object} firstToken first token from the AST node
  180. * @param {Object} secondToken second token from the AST node
  181. * @param {string} word The word to be used for reporting
  182. * @returns {void}
  183. */
  184. function checkUnaryWordOperatorForSpaces(
  185. node,
  186. firstToken,
  187. secondToken,
  188. word,
  189. ) {
  190. if (overrideExistsForOperator(word)) {
  191. if (overrideEnforcesSpaces(word)) {
  192. verifyWordHasSpaces(node, firstToken, secondToken, word);
  193. } else {
  194. verifyWordDoesntHaveSpaces(
  195. node,
  196. firstToken,
  197. secondToken,
  198. word,
  199. );
  200. }
  201. } else if (options.words) {
  202. verifyWordHasSpaces(node, firstToken, secondToken, word);
  203. } else {
  204. verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
  205. }
  206. }
  207. /**
  208. * Verifies YieldExpressions satisfy spacing requirements
  209. * @param {ASTnode} node AST node
  210. * @returns {void}
  211. */
  212. function checkForSpacesAfterYield(node) {
  213. const tokens = sourceCode.getFirstTokens(node, 3),
  214. word = "yield";
  215. if (!node.argument || node.delegate) {
  216. return;
  217. }
  218. checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
  219. }
  220. /**
  221. * Verifies AwaitExpressions satisfy spacing requirements
  222. * @param {ASTNode} node AwaitExpression AST node
  223. * @returns {void}
  224. */
  225. function checkForSpacesAfterAwait(node) {
  226. const tokens = sourceCode.getFirstTokens(node, 3);
  227. checkUnaryWordOperatorForSpaces(
  228. node,
  229. tokens[0],
  230. tokens[1],
  231. "await",
  232. );
  233. }
  234. /**
  235. * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator
  236. * @param {ASTnode} node AST node
  237. * @param {Object} firstToken First token in the expression
  238. * @param {Object} secondToken Second token in the expression
  239. * @returns {void}
  240. */
  241. function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
  242. if (node.prefix) {
  243. if (isFirstBangInBangBangExpression(node)) {
  244. return;
  245. }
  246. if (firstToken.range[1] === secondToken.range[0]) {
  247. context.report({
  248. node,
  249. messageId: "operator",
  250. data: {
  251. operator: firstToken.value,
  252. },
  253. fix(fixer) {
  254. return fixer.insertTextAfter(firstToken, " ");
  255. },
  256. });
  257. }
  258. } else {
  259. if (firstToken.range[1] === secondToken.range[0]) {
  260. context.report({
  261. node,
  262. messageId: "beforeUnaryExpressions",
  263. data: {
  264. token: secondToken.value,
  265. },
  266. fix(fixer) {
  267. return fixer.insertTextBefore(secondToken, " ");
  268. },
  269. });
  270. }
  271. }
  272. }
  273. /**
  274. * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator
  275. * @param {ASTnode} node AST node
  276. * @param {Object} firstToken First token in the expression
  277. * @param {Object} secondToken Second token in the expression
  278. * @returns {void}
  279. */
  280. function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
  281. if (node.prefix) {
  282. if (secondToken.range[0] > firstToken.range[1]) {
  283. context.report({
  284. node,
  285. messageId: "unexpectedAfter",
  286. data: {
  287. operator: firstToken.value,
  288. },
  289. fix(fixer) {
  290. if (
  291. astUtils.canTokensBeAdjacent(
  292. firstToken,
  293. secondToken,
  294. )
  295. ) {
  296. return fixer.removeRange([
  297. firstToken.range[1],
  298. secondToken.range[0],
  299. ]);
  300. }
  301. return null;
  302. },
  303. });
  304. }
  305. } else {
  306. if (secondToken.range[0] > firstToken.range[1]) {
  307. context.report({
  308. node,
  309. messageId: "unexpectedBefore",
  310. data: {
  311. operator: secondToken.value,
  312. },
  313. fix(fixer) {
  314. return fixer.removeRange([
  315. firstToken.range[1],
  316. secondToken.range[0],
  317. ]);
  318. },
  319. });
  320. }
  321. }
  322. }
  323. /**
  324. * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements
  325. * @param {ASTnode} node AST node
  326. * @returns {void}
  327. */
  328. function checkForSpaces(node) {
  329. const tokens =
  330. node.type === "UpdateExpression" && !node.prefix
  331. ? sourceCode.getLastTokens(node, 2)
  332. : sourceCode.getFirstTokens(node, 2);
  333. const firstToken = tokens[0];
  334. const secondToken = tokens[1];
  335. if (
  336. (node.type === "NewExpression" || node.prefix) &&
  337. firstToken.type === "Keyword"
  338. ) {
  339. checkUnaryWordOperatorForSpaces(
  340. node,
  341. firstToken,
  342. secondToken,
  343. firstToken.value,
  344. );
  345. return;
  346. }
  347. const operator = node.prefix ? tokens[0].value : tokens[1].value;
  348. if (overrideExistsForOperator(operator)) {
  349. if (overrideEnforcesSpaces(operator)) {
  350. verifyNonWordsHaveSpaces(node, firstToken, secondToken);
  351. } else {
  352. verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
  353. }
  354. } else if (options.nonwords) {
  355. verifyNonWordsHaveSpaces(node, firstToken, secondToken);
  356. } else {
  357. verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
  358. }
  359. }
  360. //--------------------------------------------------------------------------
  361. // Public
  362. //--------------------------------------------------------------------------
  363. return {
  364. UnaryExpression: checkForSpaces,
  365. UpdateExpression: checkForSpaces,
  366. NewExpression: checkForSpaces,
  367. YieldExpression: checkForSpacesAfterYield,
  368. AwaitExpression: checkForSpacesAfterAwait,
  369. };
  370. },
  371. };