space-before-blocks.js 7.1 KB

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