| /** |
| * @fileoverview Rule to disallow unnecessary labels |
| * @author Toru Nagashima |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../types').Rule.RuleModule} */ |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "Disallow unnecessary labels", |
| recommended: false, |
| frozen: true, |
| url: "https://eslint.org/docs/latest/rules/no-extra-label", |
| }, |
| |
| schema: [], |
| fixable: "code", |
| |
| messages: { |
| unexpected: "This label '{{name}}' is unnecessary.", |
| }, |
| }, |
| |
| create(context) { |
| const sourceCode = context.sourceCode; |
| let scopeInfo = null; |
| |
| /** |
| * Creates a new scope with a breakable statement. |
| * @param {ASTNode} node A node to create. This is a BreakableStatement. |
| * @returns {void} |
| */ |
| function enterBreakableStatement(node) { |
| scopeInfo = { |
| label: |
| node.parent.type === "LabeledStatement" |
| ? node.parent.label |
| : null, |
| breakable: true, |
| upper: scopeInfo, |
| }; |
| } |
| |
| /** |
| * Removes the top scope of the stack. |
| * @returns {void} |
| */ |
| function exitBreakableStatement() { |
| scopeInfo = scopeInfo.upper; |
| } |
| |
| /** |
| * Creates a new scope with a labeled statement. |
| * |
| * This ignores it if the body is a breakable statement. |
| * In this case it's handled in the `enterBreakableStatement` function. |
| * @param {ASTNode} node A node to create. This is a LabeledStatement. |
| * @returns {void} |
| */ |
| function enterLabeledStatement(node) { |
| if (!astUtils.isBreakableStatement(node.body)) { |
| scopeInfo = { |
| label: node.label, |
| breakable: false, |
| upper: scopeInfo, |
| }; |
| } |
| } |
| |
| /** |
| * Removes the top scope of the stack. |
| * |
| * This ignores it if the body is a breakable statement. |
| * In this case it's handled in the `exitBreakableStatement` function. |
| * @param {ASTNode} node A node. This is a LabeledStatement. |
| * @returns {void} |
| */ |
| function exitLabeledStatement(node) { |
| if (!astUtils.isBreakableStatement(node.body)) { |
| scopeInfo = scopeInfo.upper; |
| } |
| } |
| |
| /** |
| * Reports a given control node if it's unnecessary. |
| * @param {ASTNode} node A node. This is a BreakStatement or a |
| * ContinueStatement. |
| * @returns {void} |
| */ |
| function reportIfUnnecessary(node) { |
| if (!node.label) { |
| return; |
| } |
| |
| const labelNode = node.label; |
| |
| for (let info = scopeInfo; info !== null; info = info.upper) { |
| if ( |
| info.breakable || |
| (info.label && info.label.name === labelNode.name) |
| ) { |
| if ( |
| info.breakable && |
| info.label && |
| info.label.name === labelNode.name |
| ) { |
| context.report({ |
| node: labelNode, |
| messageId: "unexpected", |
| data: labelNode, |
| fix(fixer) { |
| const breakOrContinueToken = |
| sourceCode.getFirstToken(node); |
| |
| if ( |
| sourceCode.commentsExistBetween( |
| breakOrContinueToken, |
| labelNode, |
| ) |
| ) { |
| return null; |
| } |
| |
| return fixer.removeRange([ |
| breakOrContinueToken.range[1], |
| labelNode.range[1], |
| ]); |
| }, |
| }); |
| } |
| return; |
| } |
| } |
| } |
| |
| return { |
| WhileStatement: enterBreakableStatement, |
| "WhileStatement:exit": exitBreakableStatement, |
| DoWhileStatement: enterBreakableStatement, |
| "DoWhileStatement:exit": exitBreakableStatement, |
| ForStatement: enterBreakableStatement, |
| "ForStatement:exit": exitBreakableStatement, |
| ForInStatement: enterBreakableStatement, |
| "ForInStatement:exit": exitBreakableStatement, |
| ForOfStatement: enterBreakableStatement, |
| "ForOfStatement:exit": exitBreakableStatement, |
| SwitchStatement: enterBreakableStatement, |
| "SwitchStatement:exit": exitBreakableStatement, |
| LabeledStatement: enterLabeledStatement, |
| "LabeledStatement:exit": exitLabeledStatement, |
| BreakStatement: reportIfUnnecessary, |
| ContinueStatement: reportIfUnnecessary, |
| }; |
| }, |
| }; |