semi-style.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * @fileoverview Rule to enforce location of semicolons.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. const SELECTOR = [
  14. "BreakStatement", "ContinueStatement", "DebuggerStatement",
  15. "DoWhileStatement", "ExportAllDeclaration",
  16. "ExportDefaultDeclaration", "ExportNamedDeclaration",
  17. "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
  18. "ThrowStatement", "VariableDeclaration", "PropertyDefinition"
  19. ].join(",");
  20. /**
  21. * Get the child node list of a given node.
  22. * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
  23. * `ClassBody#body`, or `SwitchCase#consequent`.
  24. * This is used to check whether a node is the first/last child.
  25. * @param {Node} node A node to get child node list.
  26. * @returns {Node[]|null} The child node list.
  27. */
  28. function getChildren(node) {
  29. const t = node.type;
  30. if (
  31. t === "BlockStatement" ||
  32. t === "StaticBlock" ||
  33. t === "Program" ||
  34. t === "ClassBody"
  35. ) {
  36. return node.body;
  37. }
  38. if (t === "SwitchCase") {
  39. return node.consequent;
  40. }
  41. return null;
  42. }
  43. /**
  44. * Check whether a given node is the last statement in the parent block.
  45. * @param {Node} node A node to check.
  46. * @returns {boolean} `true` if the node is the last statement in the parent block.
  47. */
  48. function isLastChild(node) {
  49. const t = node.parent.type;
  50. if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword.
  51. return true;
  52. }
  53. if (t === "DoWhileStatement") { // before `while` keyword.
  54. return true;
  55. }
  56. const nodeList = getChildren(node.parent);
  57. return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc.
  58. }
  59. /** @type {import('../shared/types').Rule} */
  60. module.exports = {
  61. meta: {
  62. type: "layout",
  63. docs: {
  64. description: "Enforce location of semicolons",
  65. recommended: false,
  66. url: "https://eslint.org/docs/rules/semi-style"
  67. },
  68. schema: [{ enum: ["last", "first"] }],
  69. fixable: "whitespace",
  70. messages: {
  71. expectedSemiColon: "Expected this semicolon to be at {{pos}}."
  72. }
  73. },
  74. create(context) {
  75. const sourceCode = context.getSourceCode();
  76. const option = context.options[0] || "last";
  77. /**
  78. * Check the given semicolon token.
  79. * @param {Token} semiToken The semicolon token to check.
  80. * @param {"first"|"last"} expected The expected location to check.
  81. * @returns {void}
  82. */
  83. function check(semiToken, expected) {
  84. const prevToken = sourceCode.getTokenBefore(semiToken);
  85. const nextToken = sourceCode.getTokenAfter(semiToken);
  86. const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
  87. const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
  88. if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) {
  89. context.report({
  90. loc: semiToken.loc,
  91. messageId: "expectedSemiColon",
  92. data: {
  93. pos: (expected === "last")
  94. ? "the end of the previous line"
  95. : "the beginning of the next line"
  96. },
  97. fix(fixer) {
  98. if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) {
  99. return null;
  100. }
  101. const start = prevToken ? prevToken.range[1] : semiToken.range[0];
  102. const end = nextToken ? nextToken.range[0] : semiToken.range[1];
  103. const text = (expected === "last") ? ";\n" : "\n;";
  104. return fixer.replaceTextRange([start, end], text);
  105. }
  106. });
  107. }
  108. }
  109. return {
  110. [SELECTOR](node) {
  111. if (option === "first" && isLastChild(node)) {
  112. return;
  113. }
  114. const lastToken = sourceCode.getLastToken(node);
  115. if (astUtils.isSemicolonToken(lastToken)) {
  116. check(lastToken, option);
  117. }
  118. },
  119. ForStatement(node) {
  120. const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken);
  121. const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken);
  122. if (firstSemi) {
  123. check(firstSemi, "last");
  124. }
  125. if (secondSemi) {
  126. check(secondSemi, "last");
  127. }
  128. }
  129. };
  130. }
  131. };