preferConstRule.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright 2013 Palantir Technologies, Inc.
  5. *
  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
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  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.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. var tslib_1 = require("tslib");
  20. var ts = require("typescript");
  21. var Lint = require("../index");
  22. var utils = require("tsutils");
  23. var OPTION_DESTRUCTURING_ALL = "all";
  24. var OPTION_DESTRUCTURING_ANY = "any";
  25. var Rule = /** @class */ (function (_super) {
  26. tslib_1.__extends(Rule, _super);
  27. function Rule() {
  28. return _super !== null && _super.apply(this, arguments) || this;
  29. }
  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") + "'.";
  33. };
  34. Rule.prototype.apply = function (sourceFile) {
  35. var options = {
  36. destructuringAll: this.ruleArguments.length !== 0 &&
  37. this.ruleArguments[0].destructuring === OPTION_DESTRUCTURING_ALL,
  38. };
  39. var preferConstWalker = new PreferConstWalker(sourceFile, this.ruleName, options);
  40. return this.applyWithWalker(preferConstWalker);
  41. };
  42. /* tslint:disable:object-literal-sort-keys */
  43. Rule.metadata = {
  44. ruleName: "prefer-const",
  45. description: "Requires that variable declarations use `const` instead of `let` and `var` if possible.",
  46. 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'"]))),
  47. hasFix: true,
  48. 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),
  49. options: {
  50. type: "object",
  51. properties: {
  52. destructuring: {
  53. type: "string",
  54. enum: [OPTION_DESTRUCTURING_ALL, OPTION_DESTRUCTURING_ANY],
  55. },
  56. },
  57. },
  58. optionExamples: [
  59. true,
  60. [true, { destructuring: OPTION_DESTRUCTURING_ALL }],
  61. ],
  62. type: "maintainability",
  63. typescriptOnly: false,
  64. };
  65. return Rule;
  66. }(Lint.Rules.AbstractRule));
  67. exports.Rule = Rule;
  68. var Scope = /** @class */ (function () {
  69. function Scope(functionScope) {
  70. this.variables = new Map();
  71. this.reassigned = new Set();
  72. // if no functionScope is provided we are in the process of creating a new function scope, which for consistency links to itself
  73. this.functionScope = functionScope === undefined ? this : functionScope;
  74. }
  75. Scope.prototype.addVariable = function (identifier, declarationInfo, destructuringInfo) {
  76. // block scoped variables go to the block scope, function scoped variables to the containing function scope
  77. var scope = declarationInfo.isBlockScoped ? this : this.functionScope;
  78. scope.variables.set(identifier.text, {
  79. declarationInfo: declarationInfo,
  80. destructuringInfo: destructuringInfo,
  81. identifier: identifier,
  82. reassigned: false,
  83. });
  84. };
  85. return Scope;
  86. }());
  87. var PreferConstWalker = /** @class */ (function (_super) {
  88. tslib_1.__extends(PreferConstWalker, _super);
  89. function PreferConstWalker() {
  90. return _super !== null && _super.apply(this, arguments) || this;
  91. }
  92. PreferConstWalker.prototype.walk = function (sourceFile) {
  93. var _this = this;
  94. // don't check anything on declaration files
  95. if (sourceFile.isDeclarationFile) {
  96. return;
  97. }
  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 && utils.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
  105. // don't check ambient namespaces
  106. return;
  107. }
  108. _this.scope = new Scope();
  109. if (utils.isFunctionDeclaration(node) ||
  110. utils.isMethodDeclaration(node) ||
  111. utils.isFunctionExpression(node) ||
  112. utils.isArrowFunction(node) ||
  113. utils.isConstructorDeclaration(node)) {
  114. // special handling for function parameters
  115. // each parameter initializer can only reassign preceding parameters of variables of the containing scope
  116. if (node.body !== undefined) {
  117. for (var _i = 0, _a = node.parameters; _i < _a.length; _i++) {
  118. var param = _a[_i];
  119. cb(param);
  120. _this.settle(savedScope);
  121. }
  122. cb(node.body);
  123. _this.onScopeEnd(savedScope);
  124. }
  125. _this.scope = savedScope;
  126. return;
  127. }
  128. }
  129. else {
  130. _this.scope = new Scope(_this.scope.functionScope);
  131. if ((utils.isForInStatement(node) || utils.isForOfStatement(node)) &&
  132. node.initializer.kind !== ts.SyntaxKind.VariableDeclarationList) {
  133. _this.handleExpression(node.initializer);
  134. }
  135. }
  136. }
  137. if (node.kind === ts.SyntaxKind.VariableDeclarationList) {
  138. _this.handleVariableDeclaration(node);
  139. }
  140. else if (node.kind === ts.SyntaxKind.CatchClause) {
  141. if (node.variableDeclaration !== undefined) {
  142. _this.handleBindingName(node.variableDeclaration.name, {
  143. canBeConst: false,
  144. isBlockScoped: true,
  145. });
  146. }
  147. }
  148. else if (node.kind === ts.SyntaxKind.Parameter) {
  149. if (node.parent.kind !== ts.SyntaxKind.IndexSignature) {
  150. _this.handleBindingName(node.name, {
  151. canBeConst: false,
  152. isBlockScoped: true,
  153. });
  154. }
  155. }
  156. else if (utils.isPostfixUnaryExpression(node) ||
  157. utils.isPrefixUnaryExpression(node) &&
  158. (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken)) {
  159. if (utils.isIdentifier(node.operand)) {
  160. _this.scope.reassigned.add(node.operand.text);
  161. }
  162. }
  163. else if (utils.isBinaryExpression(node) && utils.isAssignmentKind(node.operatorToken.kind)) {
  164. _this.handleExpression(node.left);
  165. }
  166. if (boundary !== 0 /* None */) {
  167. ts.forEachChild(node, cb);
  168. _this.onScopeEnd(savedScope);
  169. _this.scope = savedScope;
  170. }
  171. else {
  172. return ts.forEachChild(node, cb);
  173. }
  174. };
  175. if (ts.isExternalModule(sourceFile)) {
  176. ts.forEachChild(sourceFile, cb);
  177. this.onScopeEnd();
  178. }
  179. else {
  180. return ts.forEachChild(sourceFile, cb);
  181. }
  182. };
  183. PreferConstWalker.prototype.handleExpression = function (node) {
  184. switch (node.kind) {
  185. case ts.SyntaxKind.Identifier:
  186. this.scope.reassigned.add(node.text);
  187. break;
  188. case ts.SyntaxKind.ParenthesizedExpression:
  189. this.handleExpression(node.expression);
  190. break;
  191. case ts.SyntaxKind.ArrayLiteralExpression:
  192. for (var _i = 0, _a = node.elements; _i < _a.length; _i++) {
  193. var element = _a[_i];
  194. if (element.kind === ts.SyntaxKind.SpreadElement) {
  195. this.handleExpression(element.expression);
  196. }
  197. else {
  198. this.handleExpression(element);
  199. }
  200. }
  201. break;
  202. case ts.SyntaxKind.ObjectLiteralExpression:
  203. for (var _b = 0, _c = node.properties; _b < _c.length; _b++) {
  204. var property = _c[_b];
  205. switch (property.kind) {
  206. case ts.SyntaxKind.ShorthandPropertyAssignment:
  207. this.scope.reassigned.add(property.name.text);
  208. break;
  209. case ts.SyntaxKind.SpreadAssignment:
  210. if (property.name !== undefined) {
  211. this.scope.reassigned.add(property.name.text);
  212. }
  213. else {
  214. // handle `...(variable)`
  215. this.handleExpression(property.expression);
  216. }
  217. break;
  218. default:
  219. this.handleExpression(property.initializer);
  220. }
  221. }
  222. }
  223. };
  224. PreferConstWalker.prototype.handleBindingName = function (name, declarationInfo) {
  225. var _this = this;
  226. if (name.kind === ts.SyntaxKind.Identifier) {
  227. this.scope.addVariable(name, declarationInfo);
  228. }
  229. else {
  230. var destructuringInfo_1 = {
  231. reassignedSiblings: false,
  232. };
  233. utils.forEachDestructuringIdentifier(name, function (declaration) { return _this.scope.addVariable(declaration.name, declarationInfo, destructuringInfo_1); });
  234. }
  235. };
  236. PreferConstWalker.prototype.handleVariableDeclaration = function (declarationList) {
  237. var declarationInfo;
  238. var kind = utils.getVariableDeclarationKind(declarationList);
  239. if (kind === 2 /* Const */ ||
  240. utils.hasModifier(declarationList.parent.modifiers, ts.SyntaxKind.ExportKeyword, ts.SyntaxKind.DeclareKeyword)) {
  241. declarationInfo = {
  242. canBeConst: false,
  243. isBlockScoped: kind !== 0 /* Var */,
  244. };
  245. }
  246. else {
  247. declarationInfo = {
  248. allInitialized: declarationList.parent.kind === ts.SyntaxKind.ForOfStatement ||
  249. declarationList.parent.kind === ts.SyntaxKind.ForInStatement ||
  250. declarationList.declarations.every(function (declaration) { return declaration.initializer !== undefined; }),
  251. canBeConst: true,
  252. declarationList: declarationList,
  253. isBlockScoped: kind === 1 /* Let */,
  254. isForLoop: declarationList.parent.kind === ts.SyntaxKind.ForStatement ||
  255. declarationList.parent.kind === ts.SyntaxKind.ForOfStatement,
  256. reassignedSiblings: false,
  257. };
  258. }
  259. for (var _i = 0, _a = declarationList.declarations; _i < _a.length; _i++) {
  260. var declaration = _a[_i];
  261. this.handleBindingName(declaration.name, declarationInfo);
  262. }
  263. };
  264. PreferConstWalker.prototype.settle = function (parent) {
  265. var _a = this.scope, variables = _a.variables, reassigned = _a.reassigned;
  266. reassigned.forEach(function (name) {
  267. var variableInfo = variables.get(name);
  268. if (variableInfo !== undefined) {
  269. if (variableInfo.declarationInfo.canBeConst) {
  270. variableInfo.reassigned = true;
  271. variableInfo.declarationInfo.reassignedSiblings = true;
  272. if (variableInfo.destructuringInfo !== undefined) {
  273. variableInfo.destructuringInfo.reassignedSiblings = true;
  274. }
  275. }
  276. }
  277. else if (parent !== undefined) {
  278. // if the reassigned variable was not declared in this scope we defer to the parent scope
  279. parent.reassigned.add(name);
  280. }
  281. });
  282. reassigned.clear();
  283. };
  284. PreferConstWalker.prototype.onScopeEnd = function (parent) {
  285. var _this = this;
  286. this.settle(parent);
  287. var appliedFixes = new Set();
  288. this.scope.variables.forEach(function (info, name) {
  289. if (info.declarationInfo.canBeConst &&
  290. !info.reassigned &&
  291. // don't add failures for reassigned variables in for loop initializer
  292. !(info.declarationInfo.reassignedSiblings && info.declarationInfo.isForLoop) &&
  293. // if {destructuring: "all"} is set, only add a failure if all variables in a destructuring assignment can be const
  294. (!_this.options.destructuringAll ||
  295. info.destructuringInfo === undefined ||
  296. !info.destructuringInfo.reassignedSiblings)) {
  297. var fix = void 0;
  298. // only apply fixes if the VariableDeclarationList has no reassigned variables
  299. // and the variable is block scoped aka `let` and initialized
  300. if (info.declarationInfo.allInitialized &&
  301. !info.declarationInfo.reassignedSiblings &&
  302. info.declarationInfo.isBlockScoped &&
  303. !appliedFixes.has(info.declarationInfo.declarationList)) {
  304. fix = new Lint.Replacement(info.declarationInfo.declarationList.getStart(_this.sourceFile), 3, "const");
  305. // add only one fixer per VariableDeclarationList
  306. appliedFixes.add(info.declarationInfo.declarationList);
  307. }
  308. _this.addFailureAtNode(info.identifier, Rule.FAILURE_STRING_FACTORY(name, info.declarationInfo.isBlockScoped), fix);
  309. }
  310. });
  311. };
  312. return PreferConstWalker;
  313. }(Lint.AbstractWalker));
  314. var templateObject_1, templateObject_2;