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