semi-spacing.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /**
  2. * @fileoverview Validates spacing before and after semicolon
  3. * @author Mathias Schreck
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. const astUtils = require("./utils/ast-utils");
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. /** @type {import('../types').Rule.RuleModule} */
  12. module.exports = {
  13. meta: {
  14. deprecated: {
  15. message: "Formatting rules are being moved out of ESLint core.",
  16. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  17. deprecatedSince: "8.53.0",
  18. availableUntil: "11.0.0",
  19. replacedBy: [
  20. {
  21. message:
  22. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  23. url: "https://eslint.style/guide/migration",
  24. plugin: {
  25. name: "@stylistic/eslint-plugin",
  26. url: "https://eslint.style",
  27. },
  28. rule: {
  29. name: "semi-spacing",
  30. url: "https://eslint.style/rules/semi-spacing",
  31. },
  32. },
  33. ],
  34. },
  35. type: "layout",
  36. docs: {
  37. description:
  38. "Enforce consistent spacing before and after semicolons",
  39. recommended: false,
  40. url: "https://eslint.org/docs/latest/rules/semi-spacing",
  41. },
  42. fixable: "whitespace",
  43. schema: [
  44. {
  45. type: "object",
  46. properties: {
  47. before: {
  48. type: "boolean",
  49. default: false,
  50. },
  51. after: {
  52. type: "boolean",
  53. default: true,
  54. },
  55. },
  56. additionalProperties: false,
  57. },
  58. ],
  59. messages: {
  60. unexpectedWhitespaceBefore:
  61. "Unexpected whitespace before semicolon.",
  62. unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.",
  63. missingWhitespaceBefore: "Missing whitespace before semicolon.",
  64. missingWhitespaceAfter: "Missing whitespace after semicolon.",
  65. },
  66. },
  67. create(context) {
  68. const config = context.options[0],
  69. sourceCode = context.sourceCode;
  70. let requireSpaceBefore = false,
  71. requireSpaceAfter = true;
  72. if (typeof config === "object") {
  73. requireSpaceBefore = config.before;
  74. requireSpaceAfter = config.after;
  75. }
  76. /**
  77. * Checks if a given token has leading whitespace.
  78. * @param {Object} token The token to check.
  79. * @returns {boolean} True if the given token has leading space, false if not.
  80. */
  81. function hasLeadingSpace(token) {
  82. const tokenBefore = sourceCode.getTokenBefore(token);
  83. return (
  84. tokenBefore &&
  85. astUtils.isTokenOnSameLine(tokenBefore, token) &&
  86. sourceCode.isSpaceBetweenTokens(tokenBefore, token)
  87. );
  88. }
  89. /**
  90. * Checks if a given token has trailing whitespace.
  91. * @param {Object} token The token to check.
  92. * @returns {boolean} True if the given token has trailing space, false if not.
  93. */
  94. function hasTrailingSpace(token) {
  95. const tokenAfter = sourceCode.getTokenAfter(token);
  96. return (
  97. tokenAfter &&
  98. astUtils.isTokenOnSameLine(token, tokenAfter) &&
  99. sourceCode.isSpaceBetweenTokens(token, tokenAfter)
  100. );
  101. }
  102. /**
  103. * Checks if the given token is the last token in its line.
  104. * @param {Token} token The token to check.
  105. * @returns {boolean} Whether or not the token is the last in its line.
  106. */
  107. function isLastTokenInCurrentLine(token) {
  108. const tokenAfter = sourceCode.getTokenAfter(token);
  109. return !(
  110. tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)
  111. );
  112. }
  113. /**
  114. * Checks if the given token is the first token in its line
  115. * @param {Token} token The token to check.
  116. * @returns {boolean} Whether or not the token is the first in its line.
  117. */
  118. function isFirstTokenInCurrentLine(token) {
  119. const tokenBefore = sourceCode.getTokenBefore(token);
  120. return !(
  121. tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)
  122. );
  123. }
  124. /**
  125. * Checks if the next token of a given token is a closing parenthesis.
  126. * @param {Token} token The token to check.
  127. * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
  128. */
  129. function isBeforeClosingParen(token) {
  130. const nextToken = sourceCode.getTokenAfter(token);
  131. return (
  132. (nextToken && astUtils.isClosingBraceToken(nextToken)) ||
  133. astUtils.isClosingParenToken(nextToken)
  134. );
  135. }
  136. /**
  137. * Report location example :
  138. *
  139. * for unexpected space `before`
  140. *
  141. * var a = 'b' ;
  142. * ^^^
  143. *
  144. * for unexpected space `after`
  145. *
  146. * var a = 'b'; c = 10;
  147. * ^^
  148. *
  149. * Reports if the given token has invalid spacing.
  150. * @param {Token} token The semicolon token to check.
  151. * @param {ASTNode} node The corresponding node of the token.
  152. * @returns {void}
  153. */
  154. function checkSemicolonSpacing(token, node) {
  155. if (astUtils.isSemicolonToken(token)) {
  156. if (hasLeadingSpace(token)) {
  157. if (!requireSpaceBefore) {
  158. const tokenBefore = sourceCode.getTokenBefore(token);
  159. const loc = {
  160. start: tokenBefore.loc.end,
  161. end: token.loc.start,
  162. };
  163. context.report({
  164. node,
  165. loc,
  166. messageId: "unexpectedWhitespaceBefore",
  167. fix(fixer) {
  168. return fixer.removeRange([
  169. tokenBefore.range[1],
  170. token.range[0],
  171. ]);
  172. },
  173. });
  174. }
  175. } else {
  176. if (requireSpaceBefore) {
  177. const loc = token.loc;
  178. context.report({
  179. node,
  180. loc,
  181. messageId: "missingWhitespaceBefore",
  182. fix(fixer) {
  183. return fixer.insertTextBefore(token, " ");
  184. },
  185. });
  186. }
  187. }
  188. if (
  189. !isFirstTokenInCurrentLine(token) &&
  190. !isLastTokenInCurrentLine(token) &&
  191. !isBeforeClosingParen(token)
  192. ) {
  193. if (hasTrailingSpace(token)) {
  194. if (!requireSpaceAfter) {
  195. const tokenAfter = sourceCode.getTokenAfter(token);
  196. const loc = {
  197. start: token.loc.end,
  198. end: tokenAfter.loc.start,
  199. };
  200. context.report({
  201. node,
  202. loc,
  203. messageId: "unexpectedWhitespaceAfter",
  204. fix(fixer) {
  205. return fixer.removeRange([
  206. token.range[1],
  207. tokenAfter.range[0],
  208. ]);
  209. },
  210. });
  211. }
  212. } else {
  213. if (requireSpaceAfter) {
  214. const loc = token.loc;
  215. context.report({
  216. node,
  217. loc,
  218. messageId: "missingWhitespaceAfter",
  219. fix(fixer) {
  220. return fixer.insertTextAfter(token, " ");
  221. },
  222. });
  223. }
  224. }
  225. }
  226. }
  227. }
  228. /**
  229. * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
  230. * @param {ASTNode} node The node to check.
  231. * @returns {void}
  232. */
  233. function checkNode(node) {
  234. const token = sourceCode.getLastToken(node);
  235. checkSemicolonSpacing(token, node);
  236. }
  237. return {
  238. VariableDeclaration: checkNode,
  239. ExpressionStatement: checkNode,
  240. BreakStatement: checkNode,
  241. ContinueStatement: checkNode,
  242. DebuggerStatement: checkNode,
  243. DoWhileStatement: checkNode,
  244. ReturnStatement: checkNode,
  245. ThrowStatement: checkNode,
  246. ImportDeclaration: checkNode,
  247. ExportNamedDeclaration: checkNode,
  248. ExportAllDeclaration: checkNode,
  249. ExportDefaultDeclaration: checkNode,
  250. ForStatement(node) {
  251. if (node.init) {
  252. checkSemicolonSpacing(
  253. sourceCode.getTokenAfter(node.init),
  254. node,
  255. );
  256. }
  257. if (node.test) {
  258. checkSemicolonSpacing(
  259. sourceCode.getTokenAfter(node.test),
  260. node,
  261. );
  262. }
  263. },
  264. PropertyDefinition: checkNode,
  265. };
  266. },
  267. };