UNPKG

4.88 kBJavaScriptView Raw
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 *
44 * @param {ASTNode} node - A node to create. This is a BreakableStatement.
45 * @returns {void}
46 */
47 function enterBreakableStatement(node) {
48 scopeInfo = {
49 label: node.parent.type === "LabeledStatement" ? node.parent.label : null,
50 breakable: true,
51 upper: scopeInfo
52 };
53 }
54
55 /**
56 * Removes the top scope of the stack.
57 *
58 * @returns {void}
59 */
60 function exitBreakableStatement() {
61 scopeInfo = scopeInfo.upper;
62 }
63
64 /**
65 * Creates a new scope with a labeled statement.
66 *
67 * This ignores it if the body is a breakable statement.
68 * In this case it's handled in the `enterBreakableStatement` function.
69 *
70 * @param {ASTNode} node - A node to create. This is a LabeledStatement.
71 * @returns {void}
72 */
73 function enterLabeledStatement(node) {
74 if (!astUtils.isBreakableStatement(node.body)) {
75 scopeInfo = {
76 label: node.label,
77 breakable: false,
78 upper: scopeInfo
79 };
80 }
81 }
82
83 /**
84 * Removes the top scope of the stack.
85 *
86 * This ignores it if the body is a breakable statement.
87 * In this case it's handled in the `exitBreakableStatement` function.
88 *
89 * @param {ASTNode} node - A node. This is a LabeledStatement.
90 * @returns {void}
91 */
92 function exitLabeledStatement(node) {
93 if (!astUtils.isBreakableStatement(node.body)) {
94 scopeInfo = scopeInfo.upper;
95 }
96 }
97
98 /**
99 * Reports a given control node if it's unnecessary.
100 *
101 * @param {ASTNode} node - A node. This is a BreakStatement or a
102 * ContinueStatement.
103 * @returns {void}
104 */
105 function reportIfUnnecessary(node) {
106 if (!node.label) {
107 return;
108 }
109
110 const labelNode = node.label;
111
112 for (let info = scopeInfo; info !== null; info = info.upper) {
113 if (info.breakable || info.label && info.label.name === labelNode.name) {
114 if (info.breakable && info.label && info.label.name === labelNode.name) {
115 context.report({
116 node: labelNode,
117 messageId: "unexpected",
118 data: labelNode,
119 fix: fixer => fixer.removeRange([sourceCode.getFirstToken(node).range[1], labelNode.range[1]])
120 });
121 }
122 return;
123 }
124 }
125 }
126
127 return {
128 WhileStatement: enterBreakableStatement,
129 "WhileStatement:exit": exitBreakableStatement,
130 DoWhileStatement: enterBreakableStatement,
131 "DoWhileStatement:exit": exitBreakableStatement,
132 ForStatement: enterBreakableStatement,
133 "ForStatement:exit": exitBreakableStatement,
134 ForInStatement: enterBreakableStatement,
135 "ForInStatement:exit": exitBreakableStatement,
136 ForOfStatement: enterBreakableStatement,
137 "ForOfStatement:exit": exitBreakableStatement,
138 SwitchStatement: enterBreakableStatement,
139 "SwitchStatement:exit": exitBreakableStatement,
140 LabeledStatement: enterLabeledStatement,
141 "LabeledStatement:exit": exitLabeledStatement,
142 BreakStatement: reportIfUnnecessary,
143 ContinueStatement: reportIfUnnecessary
144 };
145 }
146};