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 utils = require("tsutils");
21 var ts = require("typescript");
22 var Lint = require("../index");
23 var OPTION_DESTRUCTURING_ALL = "all";
24 var OPTION_DESTRUCTURING_ANY = "any";
25 var Rule = /** @class */ (function (_super) {
26 tslib_1.__extends(Rule, _super);
28 return _super !== null && _super.apply(this, arguments) || this;
30 /* tslint:enable:object-literal-sort-keys */
31 Rule.FAILURE_STRING_FACTORY = function (identifier, blockScoped) {
32 return "Identifier '" + identifier + "' is never reassigned; use 'const' instead of '" + (blockScoped ? "let" : "var") + "'.";
34 Rule.prototype.apply = function (sourceFile) {
36 destructuringAll: this.ruleArguments.length !== 0 &&
37 this.ruleArguments[0].destructuring ===
38 OPTION_DESTRUCTURING_ALL,
40 var preferConstWalker = new PreferConstWalker(sourceFile, this.ruleName, options);
41 return this.applyWithWalker(preferConstWalker);
43 /* tslint:disable:object-literal-sort-keys */
45 ruleName: "prefer-const",
46 description: "Requires that variable declarations use `const` instead of `let` and `var` if possible.",
47 descriptionDetails: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n If a variable is only assigned to once when it is declared, it should be declared using 'const'"], ["\n If a variable is only assigned to once when it is declared, it should be declared using 'const'"]))),
49 optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n An optional object containing the property \"destructuring\" with two possible values:\n\n * \"", "\" (default) - If any variable in destructuring can be const, this rule warns for those variables.\n * \"", "\" - Only warns if all variables in destructuring can be const."], ["\n An optional object containing the property \"destructuring\" with two possible values:\n\n * \"", "\" (default) - If any variable in destructuring can be const, this rule warns for those variables.\n * \"", "\" - Only warns if all variables in destructuring can be const."])), OPTION_DESTRUCTURING_ANY, OPTION_DESTRUCTURING_ALL),
55 enum: [OPTION_DESTRUCTURING_ALL, OPTION_DESTRUCTURING_ANY],
59 optionExamples: [true, [true, { destructuring: OPTION_DESTRUCTURING_ALL }]],
60 type: "maintainability",
61 typescriptOnly: false,
64 }(Lint.Rules.AbstractRule));
66 var Scope = /** @class */ (function () {
67 function Scope(functionScope) {
68 this.variables = new Map();
69 this.reassigned = new Set();
70 // if no functionScope is provided we are in the process of creating a new function scope, which for consistency links to itself
71 this.functionScope = functionScope === undefined ? this : functionScope;
73 Scope.prototype.addVariable = function (identifier, declarationInfo, destructuringInfo) {
74 // block scoped variables go to the block scope, function scoped variables to the containing function scope
75 var scope = declarationInfo.isBlockScoped ? this : this.functionScope;
76 scope.variables.set(identifier.text, {
77 declarationInfo: declarationInfo,
78 destructuringInfo: destructuringInfo,
79 identifier: identifier,
85 var PreferConstWalker = /** @class */ (function (_super) {
86 tslib_1.__extends(PreferConstWalker, _super);
87 function PreferConstWalker() {
88 var _this = _super !== null && _super.apply(this, arguments) || this;
89 _this.scope = new Scope();
92 PreferConstWalker.prototype.walk = function (sourceFile) {
94 // don't check anything on declaration files
95 if (sourceFile.isDeclarationFile) {
98 this.scope = new Scope();
99 var cb = function (node) {
100 var savedScope = _this.scope;
101 var boundary = utils.isScopeBoundary(node);
102 if (boundary !== 0 /* None */) {
103 if (boundary === 1 /* Function */) {
104 if (node.kind === ts.SyntaxKind.ModuleDeclaration &&
105 utils.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
106 // don't check ambient namespaces
109 _this.scope = new Scope();
110 if (utils.isFunctionDeclaration(node) ||
111 utils.isMethodDeclaration(node) ||
112 utils.isFunctionExpression(node) ||
113 utils.isArrowFunction(node) ||
114 utils.isConstructorDeclaration(node)) {
115 // special handling for function parameters
116 // each parameter initializer can only reassign preceding parameters of variables of the containing scope
117 if (node.body !== undefined) {
118 for (var _i = 0, _a = node.parameters; _i < _a.length; _i++) {
121 _this.settle(savedScope);
124 _this.onScopeEnd(savedScope);
126 _this.scope = savedScope;
131 _this.scope = new Scope(_this.scope.functionScope);
132 if ((utils.isForInStatement(node) || utils.isForOfStatement(node)) &&
133 node.initializer.kind !== ts.SyntaxKind.VariableDeclarationList) {
134 _this.handleExpression(node.initializer);
138 if (node.kind === ts.SyntaxKind.VariableDeclarationList) {
139 _this.handleVariableDeclaration(node);
141 else if (node.kind === ts.SyntaxKind.CatchClause) {
142 if (node.variableDeclaration !== undefined) {
143 _this.handleBindingName(node.variableDeclaration.name, {
149 else if (node.kind === ts.SyntaxKind.Parameter) {
150 if (node.parent.kind !== ts.SyntaxKind.IndexSignature) {
151 _this.handleBindingName(node.name, {
157 else if (utils.isPostfixUnaryExpression(node) ||
158 (utils.isPrefixUnaryExpression(node) &&
159 (node.operator === ts.SyntaxKind.PlusPlusToken ||
160 node.operator === ts.SyntaxKind.MinusMinusToken))) {
161 if (utils.isIdentifier(node.operand)) {
162 _this.scope.reassigned.add(node.operand.text);
165 else if (utils.isBinaryExpression(node) &&
166 utils.isAssignmentKind(node.operatorToken.kind)) {
167 _this.handleExpression(node.left);
169 if (boundary !== 0 /* None */) {
170 ts.forEachChild(node, cb);
171 _this.onScopeEnd(savedScope);
172 _this.scope = savedScope;
175 return ts.forEachChild(node, cb);
178 if (ts.isExternalModule(sourceFile)) {
179 ts.forEachChild(sourceFile, cb);
183 return ts.forEachChild(sourceFile, cb);
186 PreferConstWalker.prototype.handleExpression = function (node) {
188 case ts.SyntaxKind.Identifier:
189 this.scope.reassigned.add(node.text);
191 case ts.SyntaxKind.ParenthesizedExpression:
192 this.handleExpression(node.expression);
194 case ts.SyntaxKind.ArrayLiteralExpression:
195 for (var _i = 0, _a = node.elements; _i < _a.length; _i++) {
196 var element = _a[_i];
197 if (element.kind === ts.SyntaxKind.SpreadElement) {
198 this.handleExpression(element.expression);
201 this.handleExpression(element);
205 case ts.SyntaxKind.ObjectLiteralExpression:
206 for (var _b = 0, _c = node.properties; _b < _c.length; _b++) {
207 var property = _c[_b];
208 switch (property.kind) {
209 case ts.SyntaxKind.ShorthandPropertyAssignment:
210 this.scope.reassigned.add(property.name.text);
212 case ts.SyntaxKind.SpreadAssignment:
213 if (property.name !== undefined) {
214 this.scope.reassigned.add(property.name.text);
217 // handle `...(variable)`
218 this.handleExpression(property.expression);
222 this.handleExpression(property.initializer);
227 PreferConstWalker.prototype.handleBindingName = function (name, declarationInfo) {
229 if (name.kind === ts.SyntaxKind.Identifier) {
230 this.scope.addVariable(name, declarationInfo);
233 var destructuringInfo_1 = {
234 reassignedSiblings: false,
236 utils.forEachDestructuringIdentifier(name, function (declaration) {
237 return _this.scope.addVariable(declaration.name, declarationInfo, destructuringInfo_1);
241 PreferConstWalker.prototype.handleVariableDeclaration = function (declarationList) {
243 var kind = utils.getVariableDeclarationKind(declarationList);
244 if (kind === 2 /* Const */ ||
245 utils.hasModifier(declarationList.parent.modifiers, ts.SyntaxKind.ExportKeyword, ts.SyntaxKind.DeclareKeyword)) {
248 isBlockScoped: kind !== 0 /* Var */,
253 allInitialized: declarationList.parent.kind === ts.SyntaxKind.ForOfStatement ||
254 declarationList.parent.kind === ts.SyntaxKind.ForInStatement ||
255 declarationList.declarations.every(function (declaration) { return declaration.initializer !== undefined; }),
257 declarationList: declarationList,
258 isBlockScoped: kind === 1 /* Let */,
259 isForLoop: declarationList.parent.kind === ts.SyntaxKind.ForStatement ||
260 declarationList.parent.kind === ts.SyntaxKind.ForOfStatement,
261 reassignedSiblings: false,
264 for (var _i = 0, _a = declarationList.declarations; _i < _a.length; _i++) {
265 var declaration = _a[_i];
266 this.handleBindingName(declaration.name, declarationInfo);
269 PreferConstWalker.prototype.settle = function (parent) {
270 var _a = this.scope, variables = _a.variables, reassigned = _a.reassigned;
271 reassigned.forEach(function (name) {
272 var variableInfo = variables.get(name);
273 if (variableInfo !== undefined) {
274 if (variableInfo.declarationInfo.canBeConst) {
275 variableInfo.reassigned = true;
276 variableInfo.declarationInfo.reassignedSiblings = true;
277 if (variableInfo.destructuringInfo !== undefined) {
278 variableInfo.destructuringInfo.reassignedSiblings = true;
282 else if (parent !== undefined) {
283 // if the reassigned variable was not declared in this scope we defer to the parent scope
284 parent.reassigned.add(name);
289 PreferConstWalker.prototype.onScopeEnd = function (parent) {
292 var appliedFixes = new Set();
293 this.scope.variables.forEach(function (info, name) {
294 if (info.declarationInfo.canBeConst &&
296 // don't add failures for reassigned variables in for loop initializer
297 !(info.declarationInfo.reassignedSiblings && info.declarationInfo.isForLoop) &&
298 // if {destructuring: "all"} is set, only add a failure if all variables in a destructuring assignment can be const
299 (!_this.options.destructuringAll ||
300 info.destructuringInfo === undefined ||
301 !info.destructuringInfo.reassignedSiblings)) {
303 // only apply fixes if the VariableDeclarationList has no reassigned variables
304 // and the variable is block scoped aka `let` and initialized
305 if (info.declarationInfo.allInitialized &&
306 !info.declarationInfo.reassignedSiblings &&
307 info.declarationInfo.isBlockScoped &&
308 !appliedFixes.has(info.declarationInfo.declarationList)) {
309 fix = new Lint.Replacement(info.declarationInfo.declarationList.getStart(_this.sourceFile), 3, "const");
310 // add only one fixer per VariableDeclarationList
311 appliedFixes.add(info.declarationInfo.declarationList);
313 _this.addFailureAtNode(info.identifier, Rule.FAILURE_STRING_FACTORY(name, info.declarationInfo.isBlockScoped), fix);
317 return PreferConstWalker;
318 }(Lint.AbstractWalker));
319 var templateObject_1, templateObject_2;