semi-spacing.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. /** @type {import('../shared/types').Rule} */
  11. module.exports = {
  12. meta: {
  13. type: "layout",
  14. docs: {
  15. description: "Enforce consistent spacing before and after semicolons",
  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. messages: {
  37. unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.",
  38. unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.",
  39. missingWhitespaceBefore: "Missing whitespace before semicolon.",
  40. missingWhitespaceAfter: "Missing whitespace after semicolon."
  41. }
  42. },
  43. create(context) {
  44. const config = context.options[0],
  45. sourceCode = context.getSourceCode();
  46. let requireSpaceBefore = false,
  47. requireSpaceAfter = true;
  48. if (typeof config === "object") {
  49. requireSpaceBefore = config.before;
  50. requireSpaceAfter = config.after;
  51. }
  52. /**
  53. * Checks if a given token has leading whitespace.
  54. * @param {Object} token The token to check.
  55. * @returns {boolean} True if the given token has leading space, false if not.
  56. */
  57. function hasLeadingSpace(token) {
  58. const tokenBefore = sourceCode.getTokenBefore(token);
  59. return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
  60. }
  61. /**
  62. * Checks if a given token has trailing whitespace.
  63. * @param {Object} token The token to check.
  64. * @returns {boolean} True if the given token has trailing space, false if not.
  65. */
  66. function hasTrailingSpace(token) {
  67. const tokenAfter = sourceCode.getTokenAfter(token);
  68. return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
  69. }
  70. /**
  71. * Checks if the given token is the last token in its line.
  72. * @param {Token} token The token to check.
  73. * @returns {boolean} Whether or not the token is the last in its line.
  74. */
  75. function isLastTokenInCurrentLine(token) {
  76. const tokenAfter = sourceCode.getTokenAfter(token);
  77. return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
  78. }
  79. /**
  80. * Checks if the given token is the first token in its line
  81. * @param {Token} token The token to check.
  82. * @returns {boolean} Whether or not the token is the first in its line.
  83. */
  84. function isFirstTokenInCurrentLine(token) {
  85. const tokenBefore = sourceCode.getTokenBefore(token);
  86. return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
  87. }
  88. /**
  89. * Checks if the next token of a given token is a closing parenthesis.
  90. * @param {Token} token The token to check.
  91. * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
  92. */
  93. function isBeforeClosingParen(token) {
  94. const nextToken = sourceCode.getTokenAfter(token);
  95. return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken));
  96. }
  97. /**
  98. * Report location example :
  99. *
  100. * for unexpected space `before`
  101. *
  102. * var a = 'b' ;
  103. * ^^^
  104. *
  105. * for unexpected space `after`
  106. *
  107. * var a = 'b'; c = 10;
  108. * ^^
  109. *
  110. * Reports if the given token has invalid spacing.
  111. * @param {Token} token The semicolon token to check.
  112. * @param {ASTNode} node The corresponding node of the token.
  113. * @returns {void}
  114. */
  115. function checkSemicolonSpacing(token, node) {
  116. if (astUtils.isSemicolonToken(token)) {
  117. if (hasLeadingSpace(token)) {
  118. if (!requireSpaceBefore) {
  119. const tokenBefore = sourceCode.getTokenBefore(token);
  120. const loc = {
  121. start: tokenBefore.loc.end,
  122. end: token.loc.start
  123. };
  124. context.report({
  125. node,
  126. loc,
  127. messageId: "unexpectedWhitespaceBefore",
  128. fix(fixer) {
  129. return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
  130. }
  131. });
  132. }
  133. } else {
  134. if (requireSpaceBefore) {
  135. const loc = token.loc;
  136. context.report({
  137. node,
  138. loc,
  139. messageId: "missingWhitespaceBefore",
  140. fix(fixer) {
  141. return fixer.insertTextBefore(token, " ");
  142. }
  143. });
  144. }
  145. }
  146. if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
  147. if (hasTrailingSpace(token)) {
  148. if (!requireSpaceAfter) {
  149. const tokenAfter = sourceCode.getTokenAfter(token);
  150. const loc = {
  151. start: token.loc.end,
  152. end: tokenAfter.loc.start
  153. };
  154. context.report({
  155. node,
  156. loc,
  157. messageId: "unexpectedWhitespaceAfter",
  158. fix(fixer) {
  159. return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
  160. }
  161. });
  162. }
  163. } else {
  164. if (requireSpaceAfter) {
  165. const loc = token.loc;
  166. context.report({
  167. node,
  168. loc,
  169. messageId: "missingWhitespaceAfter",
  170. fix(fixer) {
  171. return fixer.insertTextAfter(token, " ");
  172. }
  173. });
  174. }
  175. }
  176. }
  177. }
  178. }
  179. /**
  180. * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
  181. * @param {ASTNode} node The node to check.
  182. * @returns {void}
  183. */
  184. function checkNode(node) {
  185. const token = sourceCode.getLastToken(node);
  186. checkSemicolonSpacing(token, node);
  187. }
  188. return {
  189. VariableDeclaration: checkNode,
  190. ExpressionStatement: checkNode,
  191. BreakStatement: checkNode,
  192. ContinueStatement: checkNode,
  193. DebuggerStatement: checkNode,
  194. DoWhileStatement: checkNode,
  195. ReturnStatement: checkNode,
  196. ThrowStatement: checkNode,
  197. ImportDeclaration: checkNode,
  198. ExportNamedDeclaration: checkNode,
  199. ExportAllDeclaration: checkNode,
  200. ExportDefaultDeclaration: checkNode,
  201. ForStatement(node) {
  202. if (node.init) {
  203. checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
  204. }
  205. if (node.test) {
  206. checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
  207. }
  208. },
  209. PropertyDefinition: checkNode
  210. };
  211. }
  212. };