]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule that warns when identifier names that are | |
6f036462 | 3 | * specified in the configuration are used. |
eb39fafa DC |
4 | * @author Keith Cirkel (http://keithcirkel.co.uk) |
5 | */ | |
6 | ||
7 | "use strict"; | |
8 | ||
9 | //------------------------------------------------------------------------------ | |
10 | // Helpers | |
11 | //------------------------------------------------------------------------------ | |
12 | ||
13 | /** | |
14 | * Checks whether the given node represents assignment target in a normal assignment or destructuring. | |
15 | * @param {ASTNode} node The node to check. | |
16 | * @returns {boolean} `true` if the node is assignment target. | |
17 | */ | |
18 | function isAssignmentTarget(node) { | |
19 | const parent = node.parent; | |
20 | ||
21 | return ( | |
22 | ||
23 | // normal assignment | |
24 | ( | |
25 | parent.type === "AssignmentExpression" && | |
26 | parent.left === node | |
27 | ) || | |
28 | ||
29 | // destructuring | |
30 | parent.type === "ArrayPattern" || | |
31 | parent.type === "RestElement" || | |
32 | ( | |
33 | parent.type === "Property" && | |
34 | parent.value === node && | |
35 | parent.parent.type === "ObjectPattern" | |
36 | ) || | |
37 | ( | |
38 | parent.type === "AssignmentPattern" && | |
39 | parent.left === node | |
40 | ) | |
41 | ); | |
42 | } | |
43 | ||
44 | /** | |
45 | * Checks whether the given node represents an imported name that is renamed in the same import/export specifier. | |
46 | * | |
47 | * Examples: | |
48 | * import { a as b } from 'mod'; // node `a` is renamed import | |
49 | * export { a as b } from 'mod'; // node `a` is renamed import | |
50 | * @param {ASTNode} node `Identifier` node to check. | |
51 | * @returns {boolean} `true` if the node is a renamed import. | |
52 | */ | |
53 | function isRenamedImport(node) { | |
54 | const parent = node.parent; | |
55 | ||
56 | return ( | |
57 | ( | |
58 | parent.type === "ImportSpecifier" && | |
59 | parent.imported !== parent.local && | |
60 | parent.imported === node | |
61 | ) || | |
62 | ( | |
63 | parent.type === "ExportSpecifier" && | |
64 | parent.parent.source && // re-export | |
65 | parent.local !== parent.exported && | |
66 | parent.local === node | |
67 | ) | |
68 | ); | |
69 | } | |
70 | ||
71 | /** | |
72 | * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring. | |
73 | * | |
74 | * Examples: | |
75 | * const { a : b } = foo; // node `a` is renamed node. | |
76 | * @param {ASTNode} node `Identifier` node to check. | |
77 | * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. | |
78 | */ | |
79 | function isRenamedInDestructuring(node) { | |
80 | const parent = node.parent; | |
81 | ||
82 | return ( | |
83 | ( | |
84 | !parent.computed && | |
85 | parent.type === "Property" && | |
86 | parent.parent.type === "ObjectPattern" && | |
87 | parent.value !== node && | |
88 | parent.key === node | |
89 | ) | |
90 | ); | |
91 | } | |
92 | ||
93 | /** | |
94 | * Checks whether the given node represents shorthand definition of a property in an object literal. | |
95 | * @param {ASTNode} node `Identifier` node to check. | |
96 | * @returns {boolean} `true` if the node is a shorthand property definition. | |
97 | */ | |
98 | function isShorthandPropertyDefinition(node) { | |
99 | const parent = node.parent; | |
100 | ||
101 | return ( | |
102 | parent.type === "Property" && | |
103 | parent.parent.type === "ObjectExpression" && | |
104 | parent.shorthand | |
105 | ); | |
106 | } | |
107 | ||
108 | //------------------------------------------------------------------------------ | |
109 | // Rule Definition | |
110 | //------------------------------------------------------------------------------ | |
111 | ||
112 | module.exports = { | |
113 | meta: { | |
6f036462 TL |
114 | deprecated: true, |
115 | replacedBy: ["id-denylist"], | |
116 | ||
eb39fafa DC |
117 | type: "suggestion", |
118 | ||
119 | docs: { | |
120 | description: "disallow specified identifiers", | |
121 | category: "Stylistic Issues", | |
122 | recommended: false, | |
123 | url: "https://eslint.org/docs/rules/id-blacklist" | |
124 | }, | |
125 | ||
126 | schema: { | |
127 | type: "array", | |
128 | items: { | |
129 | type: "string" | |
130 | }, | |
131 | uniqueItems: true | |
132 | }, | |
133 | messages: { | |
6f036462 | 134 | restricted: "Identifier '{{name}}' is restricted." |
eb39fafa DC |
135 | } |
136 | }, | |
137 | ||
138 | create(context) { | |
139 | ||
6f036462 | 140 | const denyList = new Set(context.options); |
eb39fafa DC |
141 | const reportedNodes = new Set(); |
142 | ||
143 | let globalScope; | |
144 | ||
145 | /** | |
6f036462 | 146 | * Checks whether the given name is restricted. |
eb39fafa | 147 | * @param {string} name The name to check. |
6f036462 | 148 | * @returns {boolean} `true` if the name is restricted. |
eb39fafa DC |
149 | * @private |
150 | */ | |
6f036462 TL |
151 | function isRestricted(name) { |
152 | return denyList.has(name); | |
eb39fafa DC |
153 | } |
154 | ||
155 | /** | |
156 | * Checks whether the given node represents a reference to a global variable that is not declared in the source code. | |
157 | * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. | |
158 | * @param {ASTNode} node `Identifier` node to check. | |
159 | * @returns {boolean} `true` if the node is a reference to a global variable. | |
160 | */ | |
161 | function isReferenceToGlobalVariable(node) { | |
162 | const variable = globalScope.set.get(node.name); | |
163 | ||
164 | return variable && variable.defs.length === 0 && | |
165 | variable.references.some(ref => ref.identifier === node); | |
166 | } | |
167 | ||
168 | /** | |
169 | * Determines whether the given node should be checked. | |
170 | * @param {ASTNode} node `Identifier` node. | |
171 | * @returns {boolean} `true` if the node should be checked. | |
172 | */ | |
173 | function shouldCheck(node) { | |
174 | const parent = node.parent; | |
175 | ||
176 | /* | |
177 | * Member access has special rules for checking property names. | |
6f036462 TL |
178 | * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. |
179 | * Write access isn't allowed, because it potentially creates a new property with a restricted name. | |
eb39fafa DC |
180 | */ |
181 | if ( | |
182 | parent.type === "MemberExpression" && | |
183 | parent.property === node && | |
184 | !parent.computed | |
185 | ) { | |
186 | return isAssignmentTarget(parent); | |
187 | } | |
188 | ||
189 | return ( | |
190 | parent.type !== "CallExpression" && | |
191 | parent.type !== "NewExpression" && | |
192 | !isRenamedImport(node) && | |
193 | !isRenamedInDestructuring(node) && | |
194 | !( | |
195 | isReferenceToGlobalVariable(node) && | |
196 | !isShorthandPropertyDefinition(node) | |
197 | ) | |
198 | ); | |
199 | } | |
200 | ||
201 | /** | |
202 | * Reports an AST node as a rule violation. | |
203 | * @param {ASTNode} node The node to report. | |
204 | * @returns {void} | |
205 | * @private | |
206 | */ | |
207 | function report(node) { | |
208 | if (!reportedNodes.has(node)) { | |
209 | context.report({ | |
210 | node, | |
6f036462 | 211 | messageId: "restricted", |
eb39fafa DC |
212 | data: { |
213 | name: node.name | |
214 | } | |
215 | }); | |
216 | reportedNodes.add(node); | |
217 | } | |
218 | } | |
219 | ||
220 | return { | |
221 | ||
222 | Program() { | |
223 | globalScope = context.getScope(); | |
224 | }, | |
225 | ||
226 | Identifier(node) { | |
6f036462 | 227 | if (isRestricted(node.name) && shouldCheck(node)) { |
eb39fafa DC |
228 | report(node); |
229 | } | |
230 | } | |
231 | }; | |
232 | } | |
233 | }; |