/** * @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, }; }, };