UNPKG

16.6 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const scope_manager_1 = require("@typescript-eslint/scope-manager");
24const util = __importStar(require("../util"));
25exports.default = util.createRule({
26 name: 'no-shadow',
27 meta: {
28 type: 'suggestion',
29 docs: {
30 description: 'Disallow variable declarations from shadowing variables declared in the outer scope',
31 recommended: false,
32 extendsBaseRule: true,
33 },
34 schema: [
35 {
36 type: 'object',
37 properties: {
38 builtinGlobals: {
39 type: 'boolean',
40 },
41 hoist: {
42 enum: ['all', 'functions', 'never'],
43 },
44 allow: {
45 type: 'array',
46 items: {
47 type: 'string',
48 },
49 },
50 ignoreTypeValueShadow: {
51 type: 'boolean',
52 },
53 ignoreFunctionTypeParameterNameValueShadow: {
54 type: 'boolean',
55 },
56 },
57 additionalProperties: false,
58 },
59 ],
60 messages: {
61 noShadow: "'{{name}}' is already declared in the upper scope.",
62 },
63 },
64 defaultOptions: [
65 {
66 allow: [],
67 builtinGlobals: false,
68 hoist: 'functions',
69 ignoreTypeValueShadow: true,
70 ignoreFunctionTypeParameterNameValueShadow: true,
71 },
72 ],
73 create(context, [options]) {
74 /**
75 * Check if a scope is a TypeScript module augmenting the global namespace.
76 */
77 function isGlobalAugmentation(scope) {
78 return ((scope.type === scope_manager_1.ScopeType.tsModule && !!scope.block.global) ||
79 (!!scope.upper && isGlobalAugmentation(scope.upper)));
80 }
81 /**
82 * Check if variable is a `this` parameter.
83 */
84 function isThisParam(variable) {
85 return (variable.defs[0].type === scope_manager_1.DefinitionType.Parameter &&
86 variable.name === 'this');
87 }
88 function isTypeImport(definition) {
89 return ((definition === null || definition === void 0 ? void 0 : definition.type) === scope_manager_1.DefinitionType.ImportBinding &&
90 (definition.parent.importKind === 'type' ||
91 (definition.node.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
92 definition.node.importKind === 'type')));
93 }
94 function isTypeValueShadow(variable, shadowed) {
95 if (options.ignoreTypeValueShadow !== true) {
96 return false;
97 }
98 if (!('isValueVariable' in variable)) {
99 // this shouldn't happen...
100 return false;
101 }
102 const [firstDefinition] = shadowed.defs;
103 const isShadowedValue = !('isValueVariable' in shadowed) ||
104 !firstDefinition ||
105 (!isTypeImport(firstDefinition) && shadowed.isValueVariable);
106 return variable.isValueVariable !== isShadowedValue;
107 }
108 function isFunctionTypeParameterNameValueShadow(variable, shadowed) {
109 if (options.ignoreFunctionTypeParameterNameValueShadow !== true) {
110 return false;
111 }
112 if (!('isValueVariable' in variable)) {
113 // this shouldn't happen...
114 return false;
115 }
116 const isShadowedValue = 'isValueVariable' in shadowed ? shadowed.isValueVariable : true;
117 if (!isShadowedValue) {
118 return false;
119 }
120 const id = variable.identifiers[0];
121 return util.isFunctionType(id.parent);
122 }
123 function isGenericOfStaticMethod(variable) {
124 if (!('isTypeVariable' in variable)) {
125 // this shouldn't happen...
126 return false;
127 }
128 if (!variable.isTypeVariable) {
129 return false;
130 }
131 if (variable.identifiers.length === 0) {
132 return false;
133 }
134 const typeParameter = variable.identifiers[0].parent;
135 if ((typeParameter === null || typeParameter === void 0 ? void 0 : typeParameter.type) !== utils_1.AST_NODE_TYPES.TSTypeParameter) {
136 return false;
137 }
138 const typeParameterDecl = typeParameter.parent;
139 if ((typeParameterDecl === null || typeParameterDecl === void 0 ? void 0 : typeParameterDecl.type) !== utils_1.AST_NODE_TYPES.TSTypeParameterDeclaration) {
140 return false;
141 }
142 const functionExpr = typeParameterDecl.parent;
143 if (!functionExpr ||
144 (functionExpr.type !== utils_1.AST_NODE_TYPES.FunctionExpression &&
145 functionExpr.type !== utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression)) {
146 return false;
147 }
148 const methodDefinition = functionExpr.parent;
149 if ((methodDefinition === null || methodDefinition === void 0 ? void 0 : methodDefinition.type) !== utils_1.AST_NODE_TYPES.MethodDefinition) {
150 return false;
151 }
152 return methodDefinition.static;
153 }
154 function isGenericOfClassDecl(variable) {
155 if (!('isTypeVariable' in variable)) {
156 // this shouldn't happen...
157 return false;
158 }
159 if (!variable.isTypeVariable) {
160 return false;
161 }
162 if (variable.identifiers.length === 0) {
163 return false;
164 }
165 const typeParameter = variable.identifiers[0].parent;
166 if ((typeParameter === null || typeParameter === void 0 ? void 0 : typeParameter.type) !== utils_1.AST_NODE_TYPES.TSTypeParameter) {
167 return false;
168 }
169 const typeParameterDecl = typeParameter.parent;
170 if ((typeParameterDecl === null || typeParameterDecl === void 0 ? void 0 : typeParameterDecl.type) !== utils_1.AST_NODE_TYPES.TSTypeParameterDeclaration) {
171 return false;
172 }
173 const classDecl = typeParameterDecl.parent;
174 return (classDecl === null || classDecl === void 0 ? void 0 : classDecl.type) === utils_1.AST_NODE_TYPES.ClassDeclaration;
175 }
176 function isGenericOfAStaticMethodShadow(variable, shadowed) {
177 return (isGenericOfStaticMethod(variable) && isGenericOfClassDecl(shadowed));
178 }
179 function isImportDeclaration(definition) {
180 return definition.type === utils_1.AST_NODE_TYPES.ImportDeclaration;
181 }
182 function isExternalModuleDeclarationWithName(scope, name) {
183 return (scope.type === scope_manager_1.ScopeType.tsModule &&
184 scope.block.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration &&
185 scope.block.id.type === utils_1.AST_NODE_TYPES.Literal &&
186 scope.block.id.value === name);
187 }
188 function isExternalDeclarationMerging(scope, variable, shadowed) {
189 var _a;
190 const [firstDefinition] = shadowed.defs;
191 const [secondDefinition] = variable.defs;
192 return (isTypeImport(firstDefinition) &&
193 isImportDeclaration(firstDefinition.parent) &&
194 isExternalModuleDeclarationWithName(scope, firstDefinition.parent.source.value) &&
195 secondDefinition.node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration &&
196 ((_a = secondDefinition.node.parent) === null || _a === void 0 ? void 0 : _a.type) ===
197 utils_1.AST_NODE_TYPES.ExportNamedDeclaration);
198 }
199 /**
200 * Check if variable name is allowed.
201 * @param variable The variable to check.
202 * @returns Whether or not the variable name is allowed.
203 */
204 function isAllowed(variable) {
205 return options.allow.indexOf(variable.name) !== -1;
206 }
207 /**
208 * Checks if a variable of the class name in the class scope of ClassDeclaration.
209 *
210 * ClassDeclaration creates two variables of its name into its outer scope and its class scope.
211 * So we should ignore the variable in the class scope.
212 * @param variable The variable to check.
213 * @returns Whether or not the variable of the class name in the class scope of ClassDeclaration.
214 */
215 function isDuplicatedClassNameVariable(variable) {
216 const block = variable.scope.block;
217 return (block.type === utils_1.AST_NODE_TYPES.ClassDeclaration &&
218 block.id === variable.identifiers[0]);
219 }
220 /**
221 * Checks if a variable of the class name in the class scope of TSEnumDeclaration.
222 *
223 * TSEnumDeclaration creates two variables of its name into its outer scope and its class scope.
224 * So we should ignore the variable in the class scope.
225 * @param variable The variable to check.
226 * @returns Whether or not the variable of the class name in the class scope of TSEnumDeclaration.
227 */
228 function isDuplicatedEnumNameVariable(variable) {
229 const block = variable.scope.block;
230 return (block.type === utils_1.AST_NODE_TYPES.TSEnumDeclaration &&
231 block.id === variable.identifiers[0]);
232 }
233 /**
234 * Checks if a variable is inside the initializer of scopeVar.
235 *
236 * To avoid reporting at declarations such as `var a = function a() {};`.
237 * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`.
238 * @param variable The variable to check.
239 * @param scopeVar The scope variable to look for.
240 * @returns Whether or not the variable is inside initializer of scopeVar.
241 */
242 function isOnInitializer(variable, scopeVar) {
243 var _a;
244 const outerScope = scopeVar.scope;
245 const outerDef = scopeVar.defs[0];
246 const outer = (_a = outerDef === null || outerDef === void 0 ? void 0 : outerDef.parent) === null || _a === void 0 ? void 0 : _a.range;
247 const innerScope = variable.scope;
248 const innerDef = variable.defs[0];
249 const inner = innerDef === null || innerDef === void 0 ? void 0 : innerDef.name.range;
250 return !!(outer &&
251 inner &&
252 outer[0] < inner[0] &&
253 inner[1] < outer[1] &&
254 ((innerDef.type === scope_manager_1.DefinitionType.FunctionName &&
255 innerDef.node.type === utils_1.AST_NODE_TYPES.FunctionExpression) ||
256 innerDef.node.type === utils_1.AST_NODE_TYPES.ClassExpression) &&
257 outerScope === innerScope.upper);
258 }
259 /**
260 * Get a range of a variable's identifier node.
261 * @param variable The variable to get.
262 * @returns The range of the variable's identifier node.
263 */
264 function getNameRange(variable) {
265 const def = variable.defs[0];
266 return def === null || def === void 0 ? void 0 : def.name.range;
267 }
268 /**
269 * Checks if a variable is in TDZ of scopeVar.
270 * @param variable The variable to check.
271 * @param scopeVar The variable of TDZ.
272 * @returns Whether or not the variable is in TDZ of scopeVar.
273 */
274 function isInTdz(variable, scopeVar) {
275 const outerDef = scopeVar.defs[0];
276 const inner = getNameRange(variable);
277 const outer = getNameRange(scopeVar);
278 return !!(inner &&
279 outer &&
280 inner[1] < outer[0] &&
281 // Excepts FunctionDeclaration if is {"hoist":"function"}.
282 (options.hoist !== 'functions' ||
283 !outerDef ||
284 outerDef.node.type !== utils_1.AST_NODE_TYPES.FunctionDeclaration));
285 }
286 /**
287 * Checks the current context for shadowed variables.
288 * @param {Scope} scope Fixme
289 */
290 function checkForShadows(scope) {
291 // ignore global augmentation
292 if (isGlobalAugmentation(scope)) {
293 return;
294 }
295 const variables = scope.variables;
296 for (const variable of variables) {
297 // ignore "arguments"
298 if (variable.identifiers.length === 0) {
299 continue;
300 }
301 // this params are pseudo-params that cannot be shadowed
302 if (isThisParam(variable)) {
303 continue;
304 }
305 // ignore variables of a class name in the class scope of ClassDeclaration
306 if (isDuplicatedClassNameVariable(variable)) {
307 continue;
308 }
309 // ignore variables of a class name in the class scope of ClassDeclaration
310 if (isDuplicatedEnumNameVariable(variable)) {
311 continue;
312 }
313 // ignore configured allowed names
314 if (isAllowed(variable)) {
315 continue;
316 }
317 // Gets shadowed variable.
318 const shadowed = scope.upper
319 ? utils_1.ASTUtils.findVariable(scope.upper, variable.name)
320 : null;
321 if (!shadowed) {
322 continue;
323 }
324 // ignore type value variable shadowing if configured
325 if (isTypeValueShadow(variable, shadowed)) {
326 continue;
327 }
328 // ignore function type parameter name shadowing if configured
329 if (isFunctionTypeParameterNameValueShadow(variable, shadowed)) {
330 continue;
331 }
332 // ignore static class method generic shadowing class generic
333 // this is impossible for the scope analyser to understand
334 // so we have to handle this manually in this rule
335 if (isGenericOfAStaticMethodShadow(variable, shadowed)) {
336 continue;
337 }
338 if (isExternalDeclarationMerging(scope, variable, shadowed)) {
339 continue;
340 }
341 const isESLintGlobal = 'writeable' in shadowed;
342 if ((shadowed.identifiers.length > 0 ||
343 (options.builtinGlobals && isESLintGlobal)) &&
344 !isOnInitializer(variable, shadowed) &&
345 !(options.hoist !== 'all' && isInTdz(variable, shadowed))) {
346 context.report({
347 node: variable.identifiers[0],
348 messageId: 'noShadow',
349 data: {
350 name: variable.name,
351 },
352 });
353 }
354 }
355 }
356 return {
357 'Program:exit'() {
358 const globalScope = context.getScope();
359 const stack = globalScope.childScopes.slice();
360 while (stack.length) {
361 const scope = stack.pop();
362 stack.push(...scope.childScopes);
363 checkForShadows(scope);
364 }
365 },
366 };
367 },
368});
369//# sourceMappingURL=no-shadow.js.map
\No newline at end of file