space-before-blocks.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /**
  2. * @fileoverview A rule to ensure whitespace before blocks.
  3. * @author Mathias Schreck <https://github.com/lo1tuma>
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. /**
  15. * Checks whether the given node represents the body of a function.
  16. * @param {ASTNode} node the node to check.
  17. * @returns {boolean} `true` if the node is function body.
  18. */
  19. function isFunctionBody(node) {
  20. const parent = node.parent;
  21. return (
  22. node.type === "BlockStatement" &&
  23. astUtils.isFunction(parent) &&
  24. parent.body === node
  25. );
  26. }
  27. //------------------------------------------------------------------------------
  28. // Rule Definition
  29. //------------------------------------------------------------------------------
  30. /** @type {import('../types').Rule.RuleModule} */
  31. module.exports = {
  32. meta: {
  33. deprecated: {
  34. message: "Formatting rules are being moved out of ESLint core.",
  35. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  36. deprecatedSince: "8.53.0",
  37. availableUntil: "11.0.0",
  38. replacedBy: [
  39. {
  40. message:
  41. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  42. url: "https://eslint.style/guide/migration",
  43. plugin: {
  44. name: "@stylistic/eslint-plugin",
  45. url: "https://eslint.style",
  46. },
  47. rule: {
  48. name: "space-before-blocks",
  49. url: "https://eslint.style/rules/space-before-blocks",
  50. },
  51. },
  52. ],
  53. },
  54. type: "layout",
  55. docs: {
  56. description: "Enforce consistent spacing before blocks",
  57. recommended: false,
  58. url: "https://eslint.org/docs/latest/rules/space-before-blocks",
  59. },
  60. fixable: "whitespace",
  61. schema: [
  62. {
  63. oneOf: [
  64. {
  65. enum: ["always", "never"],
  66. },
  67. {
  68. type: "object",
  69. properties: {
  70. keywords: {
  71. enum: ["always", "never", "off"],
  72. },
  73. functions: {
  74. enum: ["always", "never", "off"],
  75. },
  76. classes: {
  77. enum: ["always", "never", "off"],
  78. },
  79. },
  80. additionalProperties: false,
  81. },
  82. ],
  83. },
  84. ],
  85. messages: {
  86. unexpectedSpace: "Unexpected space before opening brace.",
  87. missingSpace: "Missing space before opening brace.",
  88. },
  89. },
  90. create(context) {
  91. const config = context.options[0],
  92. sourceCode = context.sourceCode;
  93. let alwaysFunctions = true,
  94. alwaysKeywords = true,
  95. alwaysClasses = true,
  96. neverFunctions = false,
  97. neverKeywords = false,
  98. neverClasses = false;
  99. if (typeof config === "object") {
  100. alwaysFunctions = config.functions === "always";
  101. alwaysKeywords = config.keywords === "always";
  102. alwaysClasses = config.classes === "always";
  103. neverFunctions = config.functions === "never";
  104. neverKeywords = config.keywords === "never";
  105. neverClasses = config.classes === "never";
  106. } else if (config === "never") {
  107. alwaysFunctions = false;
  108. alwaysKeywords = false;
  109. alwaysClasses = false;
  110. neverFunctions = true;
  111. neverKeywords = true;
  112. neverClasses = true;
  113. }
  114. /**
  115. * Checks whether the spacing before the given block is already controlled by another rule:
  116. * - `arrow-spacing` checks spaces after `=>`.
  117. * - `keyword-spacing` checks spaces after keywords in certain contexts.
  118. * - `switch-colon-spacing` checks spaces after `:` of switch cases.
  119. * @param {Token} precedingToken first token before the block.
  120. * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
  121. * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
  122. */
  123. function isConflicted(precedingToken, node) {
  124. return (
  125. astUtils.isArrowToken(precedingToken) ||
  126. (astUtils.isKeywordToken(precedingToken) &&
  127. !isFunctionBody(node)) ||
  128. (astUtils.isColonToken(precedingToken) &&
  129. node.parent &&
  130. node.parent.type === "SwitchCase" &&
  131. precedingToken ===
  132. astUtils.getSwitchCaseColonToken(
  133. node.parent,
  134. sourceCode,
  135. ))
  136. );
  137. }
  138. /**
  139. * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
  140. * @param {ASTNode|Token} node The AST node of a BlockStatement.
  141. * @returns {void} undefined.
  142. */
  143. function checkPrecedingSpace(node) {
  144. const precedingToken = sourceCode.getTokenBefore(node);
  145. if (
  146. precedingToken &&
  147. !isConflicted(precedingToken, node) &&
  148. astUtils.isTokenOnSameLine(precedingToken, node)
  149. ) {
  150. const hasSpace = sourceCode.isSpaceBetweenTokens(
  151. precedingToken,
  152. node,
  153. );
  154. let requireSpace;
  155. let requireNoSpace;
  156. if (isFunctionBody(node)) {
  157. requireSpace = alwaysFunctions;
  158. requireNoSpace = neverFunctions;
  159. } else if (node.type === "ClassBody") {
  160. requireSpace = alwaysClasses;
  161. requireNoSpace = neverClasses;
  162. } else {
  163. requireSpace = alwaysKeywords;
  164. requireNoSpace = neverKeywords;
  165. }
  166. if (requireSpace && !hasSpace) {
  167. context.report({
  168. node,
  169. messageId: "missingSpace",
  170. fix(fixer) {
  171. return fixer.insertTextBefore(node, " ");
  172. },
  173. });
  174. } else if (requireNoSpace && hasSpace) {
  175. context.report({
  176. node,
  177. messageId: "unexpectedSpace",
  178. fix(fixer) {
  179. return fixer.removeRange([
  180. precedingToken.range[1],
  181. node.range[0],
  182. ]);
  183. },
  184. });
  185. }
  186. }
  187. }
  188. /**
  189. * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
  190. * @param {ASTNode} node The node of a SwitchStatement.
  191. * @returns {void} undefined.
  192. */
  193. function checkSpaceBeforeCaseBlock(node) {
  194. const cases = node.cases;
  195. let openingBrace;
  196. if (cases.length > 0) {
  197. openingBrace = sourceCode.getTokenBefore(cases[0]);
  198. } else {
  199. openingBrace = sourceCode.getLastToken(node, 1);
  200. }
  201. checkPrecedingSpace(openingBrace);
  202. }
  203. return {
  204. BlockStatement: checkPrecedingSpace,
  205. ClassBody: checkPrecedingSpace,
  206. SwitchStatement: checkSpaceBeforeCaseBlock,
  207. };
  208. },
  209. };