4 * Copyright 2013 Palantir Technologies, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 Object.defineProperty(exports, "__esModule", { value: true });
19 var tslib_1 = require("tslib");
20 var tsutils_1 = require("tsutils");
21 var ts = require("typescript");
22 var Lint = require("../index");
23 var Rule = /** @class */ (function (_super) {
24 tslib_1.__extends(Rule, _super);
26 return _super !== null && _super.apply(this, arguments) || this;
28 /* tslint:enable:object-literal-sort-keys */
29 Rule.FAILURE_STRING_FACTORY = function (name) {
30 return "Shadowed name: '" + name + "'";
32 Rule.prototype.apply = function (sourceFile) {
33 return this.applyWithWalker(new NoShadowedVariableWalker(sourceFile, this.ruleName, parseOptions(this.ruleArguments[0])));
35 /* tslint:disable:object-literal-sort-keys */
37 ruleName: "no-shadowed-variable",
38 description: "Disallows shadowing variable declarations.",
39 rationale: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n When a variable in a local scope and a variable in the containing scope have the same name, shadowing occurs.\n Shadowing makes it impossible to access the variable in the containing scope and\n obscures to what value an identifier actually refers. Compare the following snippets:\n\n ```\n const a = 'no shadow';\n function print() {\n console.log(a);\n }\n print(); // logs 'no shadow'.\n ```\n\n ```\n const a = 'no shadow';\n function print() {\n const a = 'shadow'; // TSLint will complain here.\n console.log(a);\n }\n print(); // logs 'shadow'.\n ```\n\n ESLint has [an equivalent rule](https://eslint.org/docs/rules/no-shadow).\n For more background information, refer to\n [this MDN closure doc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Lexical_scoping).\n "], ["\n When a variable in a local scope and a variable in the containing scope have the same name, shadowing occurs.\n Shadowing makes it impossible to access the variable in the containing scope and\n obscures to what value an identifier actually refers. Compare the following snippets:\n\n \\`\\`\\`\n const a = 'no shadow';\n function print() {\n console.log(a);\n }\n print(); // logs 'no shadow'.\n \\`\\`\\`\n\n \\`\\`\\`\n const a = 'no shadow';\n function print() {\n const a = 'shadow'; // TSLint will complain here.\n console.log(a);\n }\n print(); // logs 'shadow'.\n \\`\\`\\`\n\n ESLint has [an equivalent rule](https://eslint.org/docs/rules/no-shadow).\n For more background information, refer to\n [this MDN closure doc](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Lexical_scoping).\n "]))),
40 optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n You can optionally pass an object to disable checking for certain kinds of declarations.\n Possible keys are `\"class\"`, `\"enum\"`, `\"function\"`, `\"import\"`, `\"interface\"`, `\"namespace\"`, `\"typeAlias\"`\n and `\"typeParameter\"`. You can also pass `\"underscore`\" to ignore variable names that begin with `_`.\n Just set the value to `false` for the check you want to disable.\n All checks default to `true`, i.e. are enabled by default.\n Note that you cannot disable variables and parameters.\n\n The option `\"temporalDeadZone\"` defaults to `true` which shows errors when shadowing block scoped declarations in their\n temporal dead zone. When set to `false` parameters, classes, enums and variables declared\n with `let` or `const` are not considered shadowed if the shadowing occurs within their\n [temporal dead zone](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified).\n\n The following example shows how the `\"temporalDeadZone\"` option changes the linting result:\n\n ```ts\n function fn(value) {\n if (value) {\n const tmp = value; // no error on this line if \"temporalDeadZone\" is false\n return tmp;\n }\n let tmp = undefined;\n if (!value) {\n const tmp = value; // this line always contains an error\n return tmp;\n }\n }\n ```\n "], ["\n You can optionally pass an object to disable checking for certain kinds of declarations.\n Possible keys are \\`\"class\"\\`, \\`\"enum\"\\`, \\`\"function\"\\`, \\`\"import\"\\`, \\`\"interface\"\\`, \\`\"namespace\"\\`, \\`\"typeAlias\"\\`\n and \\`\"typeParameter\"\\`. You can also pass \\`\"underscore\\`\" to ignore variable names that begin with \\`_\\`.\n Just set the value to \\`false\\` for the check you want to disable.\n All checks default to \\`true\\`, i.e. are enabled by default.\n Note that you cannot disable variables and parameters.\n\n The option \\`\"temporalDeadZone\"\\` defaults to \\`true\\` which shows errors when shadowing block scoped declarations in their\n temporal dead zone. When set to \\`false\\` parameters, classes, enums and variables declared\n with \\`let\\` or \\`const\\` are not considered shadowed if the shadowing occurs within their\n [temporal dead zone](http://jsrocks.org/2015/01/temporal-dead-zone-tdz-demystified).\n\n The following example shows how the \\`\"temporalDeadZone\"\\` option changes the linting result:\n\n \\`\\`\\`ts\n function fn(value) {\n if (value) {\n const tmp = value; // no error on this line if \"temporalDeadZone\" is false\n return tmp;\n }\n let tmp = undefined;\n if (!value) {\n const tmp = value; // this line always contains an error\n return tmp;\n }\n }\n \\`\\`\\`\n "]))),
44 class: { type: "boolean" },
45 enum: { type: "boolean" },
46 function: { type: "boolean" },
47 import: { type: "boolean" },
48 interface: { type: "boolean" },
49 namespace: { type: "boolean" },
50 typeAlias: { type: "boolean" },
51 typeParameter: { type: "boolean" },
52 temporalDeadZone: { type: "boolean" },
53 underscore: { type: "boolean" },
72 type: "functionality",
73 typescriptOnly: false,
76 }(Lint.Rules.AbstractRule));
78 function parseOptions(option) {
79 return tslib_1.__assign({ class: true, enum: true, function: true, import: true, interface: true, namespace: true, temporalDeadZone: true, typeAlias: true, typeParameter: true, underscore: true }, option);
81 var Scope = /** @class */ (function () {
82 function Scope(functionScope) {
83 this.variables = new Map();
84 this.variablesSeen = new Map();
85 this.reassigned = new Set();
86 // if no functionScope is provided we are in the process of creating a new function scope, which for consistency links to itself
87 this.functionScope = functionScope !== undefined ? functionScope : this;
89 Scope.prototype.addVariable = function (identifier, blockScoped, tdz) {
90 if (blockScoped === void 0) { blockScoped = true; }
91 if (tdz === void 0) { tdz = false; }
92 // block scoped variables go to the block scope, function scoped variables to the containing function scope
93 var scope = blockScoped ? this : this.functionScope;
94 var list = scope.variables.get(identifier.text);
96 identifier: identifier,
99 if (list === undefined) {
100 scope.variables.set(identifier.text, [variableInfo]);
103 list.push(variableInfo);
108 var NoShadowedVariableWalker = /** @class */ (function (_super) {
109 tslib_1.__extends(NoShadowedVariableWalker, _super);
110 function NoShadowedVariableWalker() {
111 var _this = _super !== null && _super.apply(this, arguments) || this;
112 _this.scope = new Scope();
115 NoShadowedVariableWalker.prototype.walk = function (sourceFile) {
117 if (sourceFile.isDeclarationFile) {
120 this.scope = new Scope();
121 var cb = function (node) {
122 var parentScope = _this.scope;
123 if (((_this.options.function && tsutils_1.isFunctionExpression(node)) ||
124 (_this.options.class && tsutils_1.isClassExpression(node))) &&
125 node.name !== undefined) {
126 /* special handling for named function and class expressions:
127 technically the name of the function is only visible inside of it,
128 but variables with the same name declared inside don't cause compiler errors.
129 Therefore we add an additional function scope only for the function name to avoid merging with other declarations */
130 var functionScope = new Scope();
131 functionScope.addVariable(node.name, false);
132 _this.scope = new Scope();
133 if (tsutils_1.isClassExpression(node)) {
134 _this.visitClassLikeDeclaration(node, functionScope, cb);
137 ts.forEachChild(node, cb);
139 _this.onScopeEnd(functionScope);
140 _this.scope = functionScope;
141 _this.onScopeEnd(parentScope);
142 _this.scope = parentScope;
145 /* Visit decorators before entering a function scope.
146 In the AST decorators are children of the declaration they decorate, but we don't want to warn for the following code:
147 @decorator((param) => param)
148 function foo(param) {}
150 if (node.decorators !== undefined) {
151 for (var _i = 0, _a = node.decorators; _i < _a.length; _i++) {
152 var decorator = _a[_i];
153 ts.forEachChild(decorator, cb);
156 var boundary = tsutils_1.isScopeBoundary(node);
157 if (boundary === 2 /* Block */) {
158 _this.scope = new Scope(parentScope.functionScope);
160 else if (boundary === 1 /* Function */) {
161 _this.scope = new Scope();
164 case ts.SyntaxKind.Decorator:
165 return; // handled above
166 case ts.SyntaxKind.VariableDeclarationList:
167 _this.handleVariableDeclarationList(node);
169 case ts.SyntaxKind.TypeParameter:
170 if (_this.options.typeParameter) {
171 _this.scope.addVariable(node.name);
174 case ts.SyntaxKind.FunctionDeclaration:
175 if (_this.options.function &&
176 node.name !== undefined) {
177 parentScope.addVariable(node.name, false);
180 case ts.SyntaxKind.ClassDeclaration:
181 if (_this.options.class && node.name !== undefined) {
182 parentScope.addVariable(node.name, true, true);
185 case ts.SyntaxKind.ClassExpression:
186 _this.visitClassLikeDeclaration(node, parentScope, cb);
187 _this.onScopeEnd(parentScope);
188 _this.scope = parentScope;
190 case ts.SyntaxKind.TypeAliasDeclaration:
191 if (_this.options.typeAlias) {
192 parentScope.addVariable(node.name);
195 case ts.SyntaxKind.EnumDeclaration:
196 if (_this.options.enum) {
197 parentScope.addVariable(node.name, true, true);
200 case ts.SyntaxKind.InterfaceDeclaration:
201 if (_this.options.interface) {
202 parentScope.addVariable(node.name);
205 case ts.SyntaxKind.Parameter:
206 if (node.parent.kind !== ts.SyntaxKind.IndexSignature &&
207 !tsutils_1.isThisParameter(node) &&
208 tsutils_1.isFunctionWithBody(node.parent)) {
209 _this.handleBindingName(node.name, false, true);
212 case ts.SyntaxKind.ModuleDeclaration:
213 if (_this.options.namespace &&
214 node.parent.kind !== ts.SyntaxKind.ModuleDeclaration &&
215 node.name.kind === ts.SyntaxKind.Identifier &&
216 !tsutils_1.isNodeFlagSet(node, ts.NodeFlags.GlobalAugmentation)) {
217 parentScope.addVariable(node.name, false);
219 if (tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
220 _this.onScopeEnd(parentScope);
221 _this.scope = parentScope;
222 return; // don't check any ambient declaration blocks
225 case ts.SyntaxKind.ImportClause:
226 if (_this.options.import && node.name !== undefined) {
227 _this.scope.addVariable(node.name, false);
230 case ts.SyntaxKind.NamespaceImport:
231 case ts.SyntaxKind.ImportSpecifier:
232 case ts.SyntaxKind.ImportEqualsDeclaration:
233 if (_this.options.import) {
234 _this.scope.addVariable(node.name, false);
237 if (boundary !== 0 /* None */) {
238 ts.forEachChild(node, cb);
239 _this.onScopeEnd(parentScope);
240 _this.scope = parentScope;
243 return ts.forEachChild(node, cb);
246 ts.forEachChild(sourceFile, cb);
249 NoShadowedVariableWalker.prototype.visitClassLikeDeclaration = function (declaration, parentScope, cb) {
251 var currentScope = this.scope;
252 ts.forEachChild(declaration, function (node) {
253 if (!tsutils_1.hasModifier(node.modifiers, ts.SyntaxKind.StaticKeyword)) {
256 /* Don't treat static members as children of the class' scope. That avoid shadowed type parameter warnings on static members.
258 static method<T>() {}
261 _this.scope = parentScope;
263 _this.scope = currentScope;
266 NoShadowedVariableWalker.prototype.handleVariableDeclarationList = function (node) {
267 var blockScoped = tsutils_1.isBlockScopedVariableDeclarationList(node);
268 for (var _i = 0, _a = node.declarations; _i < _a.length; _i++) {
269 var variable = _a[_i];
270 this.handleBindingName(variable.name, blockScoped);
273 NoShadowedVariableWalker.prototype.handleBindingName = function (node, blockScoped, tdz) {
274 if (tdz === void 0) { tdz = blockScoped; }
275 if (node.kind === ts.SyntaxKind.Identifier) {
276 this.scope.addVariable(node, blockScoped, tdz);
279 for (var _i = 0, _a = node.elements; _i < _a.length; _i++) {
280 var element = _a[_i];
281 if (element.kind !== ts.SyntaxKind.OmittedExpression) {
282 this.handleBindingName(element.name, blockScoped, tdz);
287 NoShadowedVariableWalker.prototype.onScopeEnd = function (parent) {
289 var _a = this.scope, variables = _a.variables, variablesSeen = _a.variablesSeen;
290 variablesSeen.forEach(function (identifiers, name) {
291 var declarationsInScope = variables.get(name);
292 var _loop_1 = function (identifier) {
293 if (declarationsInScope !== undefined &&
294 (_this.options.temporalDeadZone ||
295 // check if any of the declaration either has no temporal dead zone or is declared before the identifier
296 declarationsInScope.some(function (declaration) {
297 return !declaration.tdz || declaration.identifier.pos < identifier.pos;
299 (_this.options.underscore || !identifier.getText().startsWith("_"))) {
300 _this.addFailureAtNode(identifier, Rule.FAILURE_STRING_FACTORY(name));
302 else if (parent !== undefined) {
303 addOneToList(parent.variablesSeen, name, identifier);
306 for (var _i = 0, identifiers_1 = identifiers; _i < identifiers_1.length; _i++) {
307 var identifier = identifiers_1[_i];
311 if (parent !== undefined) {
312 variables.forEach(function (identifiers, name) {
313 addToList(parent.variablesSeen, name, identifiers);
317 return NoShadowedVariableWalker;
318 }(Lint.AbstractWalker));
319 function addToList(map, name, variables) {
320 var list = map.get(name);
321 if (list === undefined) {
325 for (var _i = 0, variables_1 = variables; _i < variables_1.length; _i++) {
326 var variable = variables_1[_i];
327 list.push(variable.identifier);
330 function addOneToList(map, name, identifier) {
331 var list = map.get(name);
332 if (list === undefined) {
333 map.set(name, [identifier]);
336 list.push(identifier);
339 var templateObject_1, templateObject_2;