• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to disallow unnecessary labels
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19    meta: {
20        type: "suggestion",
21
22        docs: {
23            description: "disallow unnecessary labels",
24            category: "Best Practices",
25            recommended: false,
26            url: "https://eslint.org/docs/rules/no-extra-label"
27        },
28
29        schema: [],
30        fixable: "code",
31
32        messages: {
33            unexpected: "This label '{{name}}' is unnecessary."
34        }
35    },
36
37    create(context) {
38        const sourceCode = context.getSourceCode();
39        let scopeInfo = null;
40
41        /**
42         * Creates a new scope with a breakable statement.
43         * @param {ASTNode} node A node to create. This is a BreakableStatement.
44         * @returns {void}
45         */
46        function enterBreakableStatement(node) {
47            scopeInfo = {
48                label: node.parent.type === "LabeledStatement" ? node.parent.label : null,
49                breakable: true,
50                upper: scopeInfo
51            };
52        }
53
54        /**
55         * Removes the top scope of the stack.
56         * @returns {void}
57         */
58        function exitBreakableStatement() {
59            scopeInfo = scopeInfo.upper;
60        }
61
62        /**
63         * Creates a new scope with a labeled statement.
64         *
65         * This ignores it if the body is a breakable statement.
66         * In this case it's handled in the `enterBreakableStatement` function.
67         * @param {ASTNode} node A node to create. This is a LabeledStatement.
68         * @returns {void}
69         */
70        function enterLabeledStatement(node) {
71            if (!astUtils.isBreakableStatement(node.body)) {
72                scopeInfo = {
73                    label: node.label,
74                    breakable: false,
75                    upper: scopeInfo
76                };
77            }
78        }
79
80        /**
81         * Removes the top scope of the stack.
82         *
83         * This ignores it if the body is a breakable statement.
84         * In this case it's handled in the `exitBreakableStatement` function.
85         * @param {ASTNode} node A node. This is a LabeledStatement.
86         * @returns {void}
87         */
88        function exitLabeledStatement(node) {
89            if (!astUtils.isBreakableStatement(node.body)) {
90                scopeInfo = scopeInfo.upper;
91            }
92        }
93
94        /**
95         * Reports a given control node if it's unnecessary.
96         * @param {ASTNode} node A node. This is a BreakStatement or a
97         *      ContinueStatement.
98         * @returns {void}
99         */
100        function reportIfUnnecessary(node) {
101            if (!node.label) {
102                return;
103            }
104
105            const labelNode = node.label;
106
107            for (let info = scopeInfo; info !== null; info = info.upper) {
108                if (info.breakable || info.label && info.label.name === labelNode.name) {
109                    if (info.breakable && info.label && info.label.name === labelNode.name) {
110                        context.report({
111                            node: labelNode,
112                            messageId: "unexpected",
113                            data: labelNode,
114                            fix(fixer) {
115                                const breakOrContinueToken = sourceCode.getFirstToken(node);
116
117                                if (sourceCode.commentsExistBetween(breakOrContinueToken, labelNode)) {
118                                    return null;
119                                }
120
121                                return fixer.removeRange([breakOrContinueToken.range[1], labelNode.range[1]]);
122                            }
123                        });
124                    }
125                    return;
126                }
127            }
128        }
129
130        return {
131            WhileStatement: enterBreakableStatement,
132            "WhileStatement:exit": exitBreakableStatement,
133            DoWhileStatement: enterBreakableStatement,
134            "DoWhileStatement:exit": exitBreakableStatement,
135            ForStatement: enterBreakableStatement,
136            "ForStatement:exit": exitBreakableStatement,
137            ForInStatement: enterBreakableStatement,
138            "ForInStatement:exit": exitBreakableStatement,
139            ForOfStatement: enterBreakableStatement,
140            "ForOfStatement:exit": exitBreakableStatement,
141            SwitchStatement: enterBreakableStatement,
142            "SwitchStatement:exit": exitBreakableStatement,
143            LabeledStatement: enterLabeledStatement,
144            "LabeledStatement:exit": exitLabeledStatement,
145            BreakStatement: reportIfUnnecessary,
146            ContinueStatement: reportIfUnnecessary
147        };
148    }
149};
150