no-extra-semi.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /**
  2. * @fileoverview Rule to flag use of unnecessary semicolons
  3. * @author Nicholas C. Zakas
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const FixTracker = require("./utils/fix-tracker");
  11. const astUtils = require("./utils/ast-utils");
  12. //------------------------------------------------------------------------------
  13. // Rule Definition
  14. //------------------------------------------------------------------------------
  15. /** @type {import('../types').Rule.RuleModule} */
  16. module.exports = {
  17. meta: {
  18. deprecated: {
  19. message: "Formatting rules are being moved out of ESLint core.",
  20. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  21. deprecatedSince: "8.53.0",
  22. availableUntil: "11.0.0",
  23. replacedBy: [
  24. {
  25. message:
  26. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  27. url: "https://eslint.style/guide/migration",
  28. plugin: {
  29. name: "@stylistic/eslint-plugin",
  30. url: "https://eslint.style",
  31. },
  32. rule: {
  33. name: "no-extra-semi",
  34. url: "https://eslint.style/rules/no-extra-semi",
  35. },
  36. },
  37. ],
  38. },
  39. type: "suggestion",
  40. docs: {
  41. description: "Disallow unnecessary semicolons",
  42. recommended: false,
  43. url: "https://eslint.org/docs/latest/rules/no-extra-semi",
  44. },
  45. fixable: "code",
  46. schema: [],
  47. messages: {
  48. unexpected: "Unnecessary semicolon.",
  49. },
  50. },
  51. create(context) {
  52. const sourceCode = context.sourceCode;
  53. /**
  54. * Checks if a node or token is fixable.
  55. * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes.
  56. * @param {Token} nodeOrToken The node or token to check.
  57. * @returns {boolean} Whether or not the node is fixable.
  58. */
  59. function isFixable(nodeOrToken) {
  60. const nextToken = sourceCode.getTokenAfter(nodeOrToken);
  61. if (!nextToken || nextToken.type !== "String") {
  62. return true;
  63. }
  64. const stringNode = sourceCode.getNodeByRangeIndex(
  65. nextToken.range[0],
  66. );
  67. return !astUtils.isTopLevelExpressionStatement(stringNode.parent);
  68. }
  69. /**
  70. * Reports an unnecessary semicolon error.
  71. * @param {Node|Token} nodeOrToken A node or a token to be reported.
  72. * @returns {void}
  73. */
  74. function report(nodeOrToken) {
  75. context.report({
  76. node: nodeOrToken,
  77. messageId: "unexpected",
  78. fix: isFixable(nodeOrToken)
  79. ? fixer =>
  80. /*
  81. * Expand the replacement range to include the surrounding
  82. * tokens to avoid conflicting with semi.
  83. * https://github.com/eslint/eslint/issues/7928
  84. */
  85. new FixTracker(fixer, context.sourceCode)
  86. .retainSurroundingTokens(nodeOrToken)
  87. .remove(nodeOrToken)
  88. : null,
  89. });
  90. }
  91. /**
  92. * Checks for a part of a class body.
  93. * This checks tokens from a specified token to a next MethodDefinition or the end of class body.
  94. * @param {Token} firstToken The first token to check.
  95. * @returns {void}
  96. */
  97. function checkForPartOfClassBody(firstToken) {
  98. for (
  99. let token = firstToken;
  100. token.type === "Punctuator" &&
  101. !astUtils.isClosingBraceToken(token);
  102. token = sourceCode.getTokenAfter(token)
  103. ) {
  104. if (astUtils.isSemicolonToken(token)) {
  105. report(token);
  106. }
  107. }
  108. }
  109. return {
  110. /**
  111. * Reports this empty statement, except if the parent node is a loop.
  112. * @param {Node} node A EmptyStatement node to be reported.
  113. * @returns {void}
  114. */
  115. EmptyStatement(node) {
  116. const parent = node.parent,
  117. allowedParentTypes = [
  118. "ForStatement",
  119. "ForInStatement",
  120. "ForOfStatement",
  121. "WhileStatement",
  122. "DoWhileStatement",
  123. "IfStatement",
  124. "LabeledStatement",
  125. "WithStatement",
  126. ];
  127. if (!allowedParentTypes.includes(parent.type)) {
  128. report(node);
  129. }
  130. },
  131. /**
  132. * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body.
  133. * @param {Node} node A ClassBody node to check.
  134. * @returns {void}
  135. */
  136. ClassBody(node) {
  137. checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`.
  138. },
  139. /**
  140. * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body.
  141. * @param {Node} node A MethodDefinition node of the start point.
  142. * @returns {void}
  143. */
  144. "MethodDefinition, PropertyDefinition, StaticBlock"(node) {
  145. checkForPartOfClassBody(sourceCode.getTokenAfter(node));
  146. },
  147. };
  148. },
  149. };