space-before-blocks.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * @fileoverview A rule to ensure whitespace before blocks.
  3. * @author Mathias Schreck <https://github.com/lo1tuma>
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. module.exports = {
  11. meta: {
  12. type: "layout",
  13. docs: {
  14. description: "enforce consistent spacing before blocks",
  15. category: "Stylistic Issues",
  16. recommended: false,
  17. url: "https://eslint.org/docs/rules/space-before-blocks"
  18. },
  19. fixable: "whitespace",
  20. schema: [
  21. {
  22. oneOf: [
  23. {
  24. enum: ["always", "never"]
  25. },
  26. {
  27. type: "object",
  28. properties: {
  29. keywords: {
  30. enum: ["always", "never", "off"]
  31. },
  32. functions: {
  33. enum: ["always", "never", "off"]
  34. },
  35. classes: {
  36. enum: ["always", "never", "off"]
  37. }
  38. },
  39. additionalProperties: false
  40. }
  41. ]
  42. }
  43. ],
  44. messages: {
  45. unexpectedSpace: "Unexpected space before opening brace.",
  46. missingSpace: "Missing space before opening brace."
  47. }
  48. },
  49. create(context) {
  50. const config = context.options[0],
  51. sourceCode = context.getSourceCode();
  52. let alwaysFunctions = true,
  53. alwaysKeywords = true,
  54. alwaysClasses = true,
  55. neverFunctions = false,
  56. neverKeywords = false,
  57. neverClasses = false;
  58. if (typeof config === "object") {
  59. alwaysFunctions = config.functions === "always";
  60. alwaysKeywords = config.keywords === "always";
  61. alwaysClasses = config.classes === "always";
  62. neverFunctions = config.functions === "never";
  63. neverKeywords = config.keywords === "never";
  64. neverClasses = config.classes === "never";
  65. } else if (config === "never") {
  66. alwaysFunctions = false;
  67. alwaysKeywords = false;
  68. alwaysClasses = false;
  69. neverFunctions = true;
  70. neverKeywords = true;
  71. neverClasses = true;
  72. }
  73. /**
  74. * Checks whether or not a given token is an arrow operator (=>) or a keyword
  75. * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`.
  76. * @param {Token} token A token to check.
  77. * @returns {boolean} `true` if the token is an arrow operator.
  78. */
  79. function isConflicted(token) {
  80. return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword";
  81. }
  82. /**
  83. * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
  84. * @param {ASTNode|Token} node The AST node of a BlockStatement.
  85. * @returns {void} undefined.
  86. */
  87. function checkPrecedingSpace(node) {
  88. const precedingToken = sourceCode.getTokenBefore(node);
  89. if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
  90. const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
  91. const parent = context.getAncestors().pop();
  92. let requireSpace;
  93. let requireNoSpace;
  94. if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
  95. requireSpace = alwaysFunctions;
  96. requireNoSpace = neverFunctions;
  97. } else if (node.type === "ClassBody") {
  98. requireSpace = alwaysClasses;
  99. requireNoSpace = neverClasses;
  100. } else {
  101. requireSpace = alwaysKeywords;
  102. requireNoSpace = neverKeywords;
  103. }
  104. if (requireSpace && !hasSpace) {
  105. context.report({
  106. node,
  107. messageId: "missingSpace",
  108. fix(fixer) {
  109. return fixer.insertTextBefore(node, " ");
  110. }
  111. });
  112. } else if (requireNoSpace && hasSpace) {
  113. context.report({
  114. node,
  115. messageId: "unexpectedSpace",
  116. fix(fixer) {
  117. return fixer.removeRange([precedingToken.range[1], node.range[0]]);
  118. }
  119. });
  120. }
  121. }
  122. }
  123. /**
  124. * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
  125. * @param {ASTNode} node The node of a SwitchStatement.
  126. * @returns {void} undefined.
  127. */
  128. function checkSpaceBeforeCaseBlock(node) {
  129. const cases = node.cases;
  130. let openingBrace;
  131. if (cases.length > 0) {
  132. openingBrace = sourceCode.getTokenBefore(cases[0]);
  133. } else {
  134. openingBrace = sourceCode.getLastToken(node, 1);
  135. }
  136. checkPrecedingSpace(openingBrace);
  137. }
  138. return {
  139. BlockStatement: checkPrecedingSpace,
  140. ClassBody: checkPrecedingSpace,
  141. SwitchStatement: checkSpaceBeforeCaseBlock
  142. };
  143. }
  144. };