no-unused-labels.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /**
  2. * @fileoverview Rule to disallow unused labels.
  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. /** @type {import('../types').Rule.RuleModule} */
  14. module.exports = {
  15. meta: {
  16. type: "suggestion",
  17. docs: {
  18. description: "Disallow unused labels",
  19. recommended: true,
  20. url: "https://eslint.org/docs/latest/rules/no-unused-labels",
  21. },
  22. schema: [],
  23. fixable: "code",
  24. messages: {
  25. unused: "'{{name}}:' is defined but never used.",
  26. },
  27. },
  28. create(context) {
  29. const sourceCode = context.sourceCode;
  30. let scopeInfo = null;
  31. /**
  32. * Adds a scope info to the stack.
  33. * @param {ASTNode} node A node to add. This is a LabeledStatement.
  34. * @returns {void}
  35. */
  36. function enterLabeledScope(node) {
  37. scopeInfo = {
  38. label: node.label.name,
  39. used: false,
  40. upper: scopeInfo,
  41. };
  42. }
  43. /**
  44. * Checks if a `LabeledStatement` node is fixable.
  45. * For a node to be fixable, there must be no comments between the label and the body.
  46. * Furthermore, is must be possible to remove the label without turning the body statement into a
  47. * directive after other fixes are applied.
  48. * @param {ASTNode} node The node to evaluate.
  49. * @returns {boolean} Whether or not the node is fixable.
  50. */
  51. function isFixable(node) {
  52. /*
  53. * Only perform a fix if there are no comments between the label and the body. This will be the case
  54. * when there is exactly one token/comment (the ":") between the label and the body.
  55. */
  56. if (
  57. sourceCode.getTokenAfter(node.label, {
  58. includeComments: true,
  59. }) !==
  60. sourceCode.getTokenBefore(node.body, { includeComments: true })
  61. ) {
  62. return false;
  63. }
  64. // Looking for the node's deepest ancestor which is not a `LabeledStatement`.
  65. let ancestor = node.parent;
  66. while (ancestor.type === "LabeledStatement") {
  67. ancestor = ancestor.parent;
  68. }
  69. if (
  70. ancestor.type === "Program" ||
  71. (ancestor.type === "BlockStatement" &&
  72. astUtils.isFunction(ancestor.parent))
  73. ) {
  74. const { body } = node;
  75. if (
  76. body.type === "ExpressionStatement" &&
  77. ((body.expression.type === "Literal" &&
  78. typeof body.expression.value === "string") ||
  79. astUtils.isStaticTemplateLiteral(body.expression))
  80. ) {
  81. return false; // potential directive
  82. }
  83. }
  84. return true;
  85. }
  86. /**
  87. * Removes the top of the stack.
  88. * At the same time, this reports the label if it's never used.
  89. * @param {ASTNode} node A node to report. This is a LabeledStatement.
  90. * @returns {void}
  91. */
  92. function exitLabeledScope(node) {
  93. if (!scopeInfo.used) {
  94. context.report({
  95. node: node.label,
  96. messageId: "unused",
  97. data: node.label,
  98. fix: isFixable(node)
  99. ? fixer =>
  100. fixer.removeRange([
  101. node.range[0],
  102. node.body.range[0],
  103. ])
  104. : null,
  105. });
  106. }
  107. scopeInfo = scopeInfo.upper;
  108. }
  109. /**
  110. * Marks the label of a given node as used.
  111. * @param {ASTNode} node A node to mark. This is a BreakStatement or
  112. * ContinueStatement.
  113. * @returns {void}
  114. */
  115. function markAsUsed(node) {
  116. if (!node.label) {
  117. return;
  118. }
  119. const label = node.label.name;
  120. let info = scopeInfo;
  121. while (info) {
  122. if (info.label === label) {
  123. info.used = true;
  124. break;
  125. }
  126. info = info.upper;
  127. }
  128. }
  129. return {
  130. LabeledStatement: enterLabeledScope,
  131. "LabeledStatement:exit": exitLabeledScope,
  132. BreakStatement: markAsUsed,
  133. ContinueStatement: markAsUsed,
  134. };
  135. },
  136. };