semi-spacing.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /**
  2. * @fileoverview Validates spacing before and after semicolon
  3. * @author Mathias Schreck
  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 and after semicolons",
  15. category: "Stylistic Issues",
  16. recommended: false,
  17. url: "https://eslint.org/docs/rules/semi-spacing"
  18. },
  19. fixable: "whitespace",
  20. schema: [
  21. {
  22. type: "object",
  23. properties: {
  24. before: {
  25. type: "boolean",
  26. default: false
  27. },
  28. after: {
  29. type: "boolean",
  30. default: true
  31. }
  32. },
  33. additionalProperties: false
  34. }
  35. ]
  36. },
  37. create(context) {
  38. const config = context.options[0],
  39. sourceCode = context.getSourceCode();
  40. let requireSpaceBefore = false,
  41. requireSpaceAfter = true;
  42. if (typeof config === "object") {
  43. requireSpaceBefore = config.before;
  44. requireSpaceAfter = config.after;
  45. }
  46. /**
  47. * Checks if a given token has leading whitespace.
  48. * @param {Object} token The token to check.
  49. * @returns {boolean} True if the given token has leading space, false if not.
  50. */
  51. function hasLeadingSpace(token) {
  52. const tokenBefore = sourceCode.getTokenBefore(token);
  53. return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
  54. }
  55. /**
  56. * Checks if a given token has trailing whitespace.
  57. * @param {Object} token The token to check.
  58. * @returns {boolean} True if the given token has trailing space, false if not.
  59. */
  60. function hasTrailingSpace(token) {
  61. const tokenAfter = sourceCode.getTokenAfter(token);
  62. return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
  63. }
  64. /**
  65. * Checks if the given token is the last token in its line.
  66. * @param {Token} token The token to check.
  67. * @returns {boolean} Whether or not the token is the last in its line.
  68. */
  69. function isLastTokenInCurrentLine(token) {
  70. const tokenAfter = sourceCode.getTokenAfter(token);
  71. return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
  72. }
  73. /**
  74. * Checks if the given token is the first token in its line
  75. * @param {Token} token The token to check.
  76. * @returns {boolean} Whether or not the token is the first in its line.
  77. */
  78. function isFirstTokenInCurrentLine(token) {
  79. const tokenBefore = sourceCode.getTokenBefore(token);
  80. return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
  81. }
  82. /**
  83. * Checks if the next token of a given token is a closing parenthesis.
  84. * @param {Token} token The token to check.
  85. * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
  86. */
  87. function isBeforeClosingParen(token) {
  88. const nextToken = sourceCode.getTokenAfter(token);
  89. return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken));
  90. }
  91. /**
  92. * Reports if the given token has invalid spacing.
  93. * @param {Token} token The semicolon token to check.
  94. * @param {ASTNode} node The corresponding node of the token.
  95. * @returns {void}
  96. */
  97. function checkSemicolonSpacing(token, node) {
  98. if (astUtils.isSemicolonToken(token)) {
  99. const location = token.loc.start;
  100. if (hasLeadingSpace(token)) {
  101. if (!requireSpaceBefore) {
  102. context.report({
  103. node,
  104. loc: location,
  105. message: "Unexpected whitespace before semicolon.",
  106. fix(fixer) {
  107. const tokenBefore = sourceCode.getTokenBefore(token);
  108. return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
  109. }
  110. });
  111. }
  112. } else {
  113. if (requireSpaceBefore) {
  114. context.report({
  115. node,
  116. loc: location,
  117. message: "Missing whitespace before semicolon.",
  118. fix(fixer) {
  119. return fixer.insertTextBefore(token, " ");
  120. }
  121. });
  122. }
  123. }
  124. if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
  125. if (hasTrailingSpace(token)) {
  126. if (!requireSpaceAfter) {
  127. context.report({
  128. node,
  129. loc: location,
  130. message: "Unexpected whitespace after semicolon.",
  131. fix(fixer) {
  132. const tokenAfter = sourceCode.getTokenAfter(token);
  133. return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
  134. }
  135. });
  136. }
  137. } else {
  138. if (requireSpaceAfter) {
  139. context.report({
  140. node,
  141. loc: location,
  142. message: "Missing whitespace after semicolon.",
  143. fix(fixer) {
  144. return fixer.insertTextAfter(token, " ");
  145. }
  146. });
  147. }
  148. }
  149. }
  150. }
  151. }
  152. /**
  153. * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
  154. * @param {ASTNode} node The node to check.
  155. * @returns {void}
  156. */
  157. function checkNode(node) {
  158. const token = sourceCode.getLastToken(node);
  159. checkSemicolonSpacing(token, node);
  160. }
  161. return {
  162. VariableDeclaration: checkNode,
  163. ExpressionStatement: checkNode,
  164. BreakStatement: checkNode,
  165. ContinueStatement: checkNode,
  166. DebuggerStatement: checkNode,
  167. ReturnStatement: checkNode,
  168. ThrowStatement: checkNode,
  169. ImportDeclaration: checkNode,
  170. ExportNamedDeclaration: checkNode,
  171. ExportAllDeclaration: checkNode,
  172. ExportDefaultDeclaration: checkNode,
  173. ForStatement(node) {
  174. if (node.init) {
  175. checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
  176. }
  177. if (node.test) {
  178. checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
  179. }
  180. }
  181. };
  182. }
  183. };