codtracker-js/node_modules/eslint/lib/rules/no-unused-labels.js
2025-04-19 23:12:19 -04:00

155 lines
4.1 KiB
JavaScript

/**
* @fileoverview Rule to disallow unused 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 unused labels',
recommended: true,
url: 'https://eslint.org/docs/latest/rules/no-unused-labels',
},
schema: [],
fixable: 'code',
messages: {
unused: "'{{name}}:' is defined but never used.",
},
},
create(context) {
const sourceCode = context.sourceCode;
let scopeInfo = null;
/**
* Adds a scope info to the stack.
* @param {ASTNode} node A node to add. This is a LabeledStatement.
* @returns {void}
*/
function enterLabeledScope(node) {
scopeInfo = {
label: node.label.name,
used: false,
upper: scopeInfo,
};
}
/**
* Checks if a `LabeledStatement` node is fixable.
* For a node to be fixable, there must be no comments between the label and the body.
* Furthermore, is must be possible to remove the label without turning the body statement into a
* directive after other fixes are applied.
* @param {ASTNode} node The node to evaluate.
* @returns {boolean} Whether or not the node is fixable.
*/
function isFixable(node) {
/*
* Only perform a fix if there are no comments between the label and the body. This will be the case
* when there is exactly one token/comment (the ":") between the label and the body.
*/
if (
sourceCode.getTokenAfter(node.label, {
includeComments: true,
}) !== sourceCode.getTokenBefore(node.body, { includeComments: true })
) {
return false;
}
// Looking for the node's deepest ancestor which is not a `LabeledStatement`.
let ancestor = node.parent;
while (ancestor.type === 'LabeledStatement') {
ancestor = ancestor.parent;
}
if (
ancestor.type === 'Program' ||
(ancestor.type === 'BlockStatement' &&
astUtils.isFunction(ancestor.parent))
) {
const { body } = node;
if (
body.type === 'ExpressionStatement' &&
((body.expression.type === 'Literal' &&
typeof body.expression.value === 'string') ||
astUtils.isStaticTemplateLiteral(body.expression))
) {
return false; // potential directive
}
}
return true;
}
/**
* Removes the top of the stack.
* At the same time, this reports the label if it's never used.
* @param {ASTNode} node A node to report. This is a LabeledStatement.
* @returns {void}
*/
function exitLabeledScope(node) {
if (!scopeInfo.used) {
context.report({
node: node.label,
messageId: 'unused',
data: node.label,
fix:
isFixable(node) ?
(fixer) => fixer.removeRange([node.range[0], node.body.range[0]])
: null,
});
}
scopeInfo = scopeInfo.upper;
}
/**
* Marks the label of a given node as used.
* @param {ASTNode} node A node to mark. This is a BreakStatement or
* ContinueStatement.
* @returns {void}
*/
function markAsUsed(node) {
if (!node.label) {
return;
}
const label = node.label.name;
let info = scopeInfo;
while (info) {
if (info.label === label) {
info.used = true;
break;
}
info = info.upper;
}
}
return {
LabeledStatement: enterLabeledScope,
'LabeledStatement:exit': exitLabeledScope,
BreakStatement: markAsUsed,
ContinueStatement: markAsUsed,
};
},
};