semi-style.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /**
  2. * @fileoverview Rule to enforce location of semicolons.
  3. * @author Toru Nagashima
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. const SELECTOR = [
  15. "BreakStatement",
  16. "ContinueStatement",
  17. "DebuggerStatement",
  18. "DoWhileStatement",
  19. "ExportAllDeclaration",
  20. "ExportDefaultDeclaration",
  21. "ExportNamedDeclaration",
  22. "ExpressionStatement",
  23. "ImportDeclaration",
  24. "ReturnStatement",
  25. "ThrowStatement",
  26. "VariableDeclaration",
  27. "PropertyDefinition",
  28. ].join(",");
  29. /**
  30. * Get the child node list of a given node.
  31. * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
  32. * `ClassBody#body`, or `SwitchCase#consequent`.
  33. * This is used to check whether a node is the first/last child.
  34. * @param {Node} node A node to get child node list.
  35. * @returns {Node[]|null} The child node list.
  36. */
  37. function getChildren(node) {
  38. const t = node.type;
  39. if (
  40. t === "BlockStatement" ||
  41. t === "StaticBlock" ||
  42. t === "Program" ||
  43. t === "ClassBody"
  44. ) {
  45. return node.body;
  46. }
  47. if (t === "SwitchCase") {
  48. return node.consequent;
  49. }
  50. return null;
  51. }
  52. /**
  53. * Check whether a given node is the last statement in the parent block.
  54. * @param {Node} node A node to check.
  55. * @returns {boolean} `true` if the node is the last statement in the parent block.
  56. */
  57. function isLastChild(node) {
  58. const t = node.parent.type;
  59. if (
  60. t === "IfStatement" &&
  61. node.parent.consequent === node &&
  62. node.parent.alternate
  63. ) {
  64. // before `else` keyword.
  65. return true;
  66. }
  67. if (t === "DoWhileStatement") {
  68. // before `while` keyword.
  69. return true;
  70. }
  71. const nodeList = getChildren(node.parent);
  72. return nodeList !== null && nodeList.at(-1) === node; // before `}` or etc.
  73. }
  74. /** @type {import('../types').Rule.RuleModule} */
  75. module.exports = {
  76. meta: {
  77. deprecated: {
  78. message: "Formatting rules are being moved out of ESLint core.",
  79. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  80. deprecatedSince: "8.53.0",
  81. availableUntil: "11.0.0",
  82. replacedBy: [
  83. {
  84. message:
  85. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  86. url: "https://eslint.style/guide/migration",
  87. plugin: {
  88. name: "@stylistic/eslint-plugin",
  89. url: "https://eslint.style",
  90. },
  91. rule: {
  92. name: "semi-style",
  93. url: "https://eslint.style/rules/semi-style",
  94. },
  95. },
  96. ],
  97. },
  98. type: "layout",
  99. docs: {
  100. description: "Enforce location of semicolons",
  101. recommended: false,
  102. url: "https://eslint.org/docs/latest/rules/semi-style",
  103. },
  104. schema: [{ enum: ["last", "first"] }],
  105. fixable: "whitespace",
  106. messages: {
  107. expectedSemiColon: "Expected this semicolon to be at {{pos}}.",
  108. },
  109. },
  110. create(context) {
  111. const sourceCode = context.sourceCode;
  112. const option = context.options[0] || "last";
  113. /**
  114. * Check the given semicolon token.
  115. * @param {Token} semiToken The semicolon token to check.
  116. * @param {"first"|"last"} expected The expected location to check.
  117. * @returns {void}
  118. */
  119. function check(semiToken, expected) {
  120. const prevToken = sourceCode.getTokenBefore(semiToken);
  121. const nextToken = sourceCode.getTokenAfter(semiToken);
  122. const prevIsSameLine =
  123. !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
  124. const nextIsSameLine =
  125. !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
  126. if (
  127. (expected === "last" && !prevIsSameLine) ||
  128. (expected === "first" && !nextIsSameLine)
  129. ) {
  130. context.report({
  131. loc: semiToken.loc,
  132. messageId: "expectedSemiColon",
  133. data: {
  134. pos:
  135. expected === "last"
  136. ? "the end of the previous line"
  137. : "the beginning of the next line",
  138. },
  139. fix(fixer) {
  140. if (
  141. prevToken &&
  142. nextToken &&
  143. sourceCode.commentsExistBetween(
  144. prevToken,
  145. nextToken,
  146. )
  147. ) {
  148. return null;
  149. }
  150. const start = prevToken
  151. ? prevToken.range[1]
  152. : semiToken.range[0];
  153. const end = nextToken
  154. ? nextToken.range[0]
  155. : semiToken.range[1];
  156. const text = expected === "last" ? ";\n" : "\n;";
  157. return fixer.replaceTextRange([start, end], text);
  158. },
  159. });
  160. }
  161. }
  162. return {
  163. [SELECTOR](node) {
  164. if (option === "first" && isLastChild(node)) {
  165. return;
  166. }
  167. const lastToken = sourceCode.getLastToken(node);
  168. if (astUtils.isSemicolonToken(lastToken)) {
  169. check(lastToken, option);
  170. }
  171. },
  172. ForStatement(node) {
  173. const firstSemi =
  174. node.init &&
  175. sourceCode.getTokenAfter(
  176. node.init,
  177. astUtils.isSemicolonToken,
  178. );
  179. const secondSemi =
  180. node.test &&
  181. sourceCode.getTokenAfter(
  182. node.test,
  183. astUtils.isSemicolonToken,
  184. );
  185. if (firstSemi) {
  186. check(firstSemi, "last");
  187. }
  188. if (secondSemi) {
  189. check(secondSemi, "last");
  190. }
  191. },
  192. };
  193. },
  194. };