preferReadonlyRule.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright 2017 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 utils = require("tsutils");
  21. var ts = require("typescript");
  22. var Lint = require("../index");
  23. var typeUtils_1 = require("../language/typeUtils");
  24. var OPTION_ONLY_INLINE_LAMBDAS = "only-inline-lambdas";
  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. Rule.prototype.applyWithProgram = function (sourceFile, program) {
  31. var options = {
  32. onlyInlineLambdas: this.ruleArguments.indexOf(OPTION_ONLY_INLINE_LAMBDAS) !== -1,
  33. };
  34. return this.applyWithFunction(sourceFile, walk, options, program.getTypeChecker());
  35. };
  36. Rule.metadata = {
  37. description: "Requires that private variables are marked as `readonly` if they're never modified outside of the constructor.",
  38. descriptionDetails: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n If a private variable is only assigned to in the constructor, it should be declared as `readonly`.\n "], ["\n If a private variable is only assigned to in the constructor, it should be declared as \\`readonly\\`.\n "]))),
  39. optionExamples: [
  40. true,
  41. [true, OPTION_ONLY_INLINE_LAMBDAS],
  42. ],
  43. options: {
  44. enum: [OPTION_ONLY_INLINE_LAMBDAS],
  45. type: "string",
  46. },
  47. optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n If `", "` is specified, only immediately-declared arrow functions are checked."], ["\n If \\`", "\\` is specified, only immediately-declared arrow functions are checked."])), OPTION_ONLY_INLINE_LAMBDAS),
  48. rationale: Lint.Utils.dedent(templateObject_3 || (templateObject_3 = tslib_1.__makeTemplateObject(["\n Marking never-modified variables as readonly helps enforce the code's intent of keeping them as never-modified.\n It can also help prevent accidental changes of members not meant to be changed."], ["\n Marking never-modified variables as readonly helps enforce the code's intent of keeping them as never-modified.\n It can also help prevent accidental changes of members not meant to be changed."]))),
  49. requiresTypeInfo: true,
  50. ruleName: "prefer-readonly",
  51. type: "maintainability",
  52. typescriptOnly: true,
  53. };
  54. return Rule;
  55. }(Lint.Rules.TypedRule));
  56. exports.Rule = Rule;
  57. function walk(context, typeChecker) {
  58. if (context.sourceFile.isDeclarationFile) {
  59. return;
  60. }
  61. var scope;
  62. ts.forEachChild(context.sourceFile, visitNode);
  63. function visitNode(node) {
  64. if (utils.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword)) {
  65. return;
  66. }
  67. switch (node.kind) {
  68. case ts.SyntaxKind.ClassDeclaration:
  69. case ts.SyntaxKind.ClassExpression:
  70. handleClassDeclarationOrExpression(node);
  71. break;
  72. case ts.SyntaxKind.Constructor:
  73. handleConstructor(node);
  74. break;
  75. case ts.SyntaxKind.PropertyDeclaration:
  76. handlePropertyDeclaration(node);
  77. break;
  78. case ts.SyntaxKind.PropertyAccessExpression:
  79. if (scope !== undefined) {
  80. handlePropertyAccessExpression(node, node.parent);
  81. }
  82. break;
  83. default:
  84. if (utils.isFunctionScopeBoundary(node)) {
  85. handleFunctionScopeBoundary(node);
  86. }
  87. else {
  88. ts.forEachChild(node, visitNode);
  89. }
  90. }
  91. }
  92. function handleFunctionScopeBoundary(node) {
  93. if (scope === undefined) {
  94. ts.forEachChild(node, visitNode);
  95. return;
  96. }
  97. scope.enterNonConstructorScope();
  98. ts.forEachChild(node, visitNode);
  99. scope.exitNonConstructorScope();
  100. }
  101. function handleClassDeclarationOrExpression(node) {
  102. var parentScope = scope;
  103. var childScope = scope = new ClassScope(node, typeChecker);
  104. ts.forEachChild(node, visitNode);
  105. finalizeScope(childScope);
  106. scope = parentScope;
  107. }
  108. function handleConstructor(node) {
  109. scope.enterConstructor();
  110. for (var _i = 0, _a = node.parameters; _i < _a.length; _i++) {
  111. var parameter = _a[_i];
  112. if (utils.isModifierFlagSet(parameter, ts.ModifierFlags.Private)) {
  113. scope.addDeclaredVariable(parameter);
  114. }
  115. }
  116. ts.forEachChild(node, visitNode);
  117. scope.exitConstructor();
  118. }
  119. function handlePropertyDeclaration(node) {
  120. if (!shouldPropertyDeclarationBeIgnored(node)) {
  121. scope.addDeclaredVariable(node);
  122. }
  123. ts.forEachChild(node, visitNode);
  124. }
  125. function handlePropertyAccessExpression(node, parent) {
  126. switch (parent.kind) {
  127. case ts.SyntaxKind.BinaryExpression:
  128. handleParentBinaryExpression(node, parent);
  129. break;
  130. case ts.SyntaxKind.DeleteExpression:
  131. handleDeleteExpression(node);
  132. break;
  133. case ts.SyntaxKind.PostfixUnaryExpression:
  134. case ts.SyntaxKind.PrefixUnaryExpression:
  135. handleParentPostfixOrPrefixUnaryExpression(parent);
  136. }
  137. ts.forEachChild(node, visitNode);
  138. }
  139. function handleParentBinaryExpression(node, parent) {
  140. if (parent.left === node && utils.isAssignmentKind(parent.operatorToken.kind)) {
  141. scope.addVariableModification(node);
  142. }
  143. }
  144. function handleParentPostfixOrPrefixUnaryExpression(node) {
  145. if (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) {
  146. scope.addVariableModification(node.operand);
  147. }
  148. }
  149. function handleDeleteExpression(node) {
  150. scope.addVariableModification(node);
  151. }
  152. function shouldPropertyDeclarationBeIgnored(node) {
  153. if (!context.options.onlyInlineLambdas) {
  154. return false;
  155. }
  156. return node.initializer === undefined || node.initializer.kind !== ts.SyntaxKind.ArrowFunction;
  157. }
  158. function finalizeScope(childScope) {
  159. for (var _i = 0, _a = childScope.finalizeUnmodifiedPrivateNonReadonlys(); _i < _a.length; _i++) {
  160. var violatingNode = _a[_i];
  161. complainOnNode(violatingNode);
  162. }
  163. }
  164. function complainOnNode(node) {
  165. var fix = Lint.Replacement.appendText(node.modifiers.end, " readonly");
  166. context.addFailureAtNode(node.name, createFailureString(node), fix);
  167. }
  168. }
  169. function createFailureString(node) {
  170. var accessibility = utils.isModifierFlagSet(node, ts.ModifierFlags.Static)
  171. ? "static"
  172. : "member";
  173. var text = node.name.getText();
  174. return "Private " + accessibility + " variable '" + text + "' is never reassigned; mark it as 'readonly'.";
  175. }
  176. var OUTSIDE_CONSTRUCTOR = -1;
  177. var DIRECTLY_INSIDE_CONSTRUCTOR = 0;
  178. var ClassScope = /** @class */ (function () {
  179. function ClassScope(classNode, typeChecker) {
  180. this.privateModifiableMembers = new Map();
  181. this.privateModifiableStatics = new Map();
  182. this.memberVariableModifications = new Set();
  183. this.staticVariableModifications = new Set();
  184. this.constructorScopeDepth = OUTSIDE_CONSTRUCTOR;
  185. this.classType = typeChecker.getTypeAtLocation(classNode);
  186. this.typeChecker = typeChecker;
  187. }
  188. ClassScope.prototype.addDeclaredVariable = function (node) {
  189. if (!utils.isModifierFlagSet(node, ts.ModifierFlags.Private)
  190. || utils.isModifierFlagSet(node, ts.ModifierFlags.Readonly)
  191. || node.name.kind === ts.SyntaxKind.ComputedPropertyName) {
  192. return;
  193. }
  194. if (utils.isModifierFlagSet(node, ts.ModifierFlags.Static)) {
  195. this.privateModifiableStatics.set(node.name.getText(), node);
  196. }
  197. else {
  198. this.privateModifiableMembers.set(node.name.getText(), node);
  199. }
  200. };
  201. ClassScope.prototype.addVariableModification = function (node) {
  202. var modifierType = this.typeChecker.getTypeAtLocation(node.expression);
  203. if (modifierType.symbol === undefined || !typeUtils_1.typeIsOrHasBaseType(modifierType, this.classType)) {
  204. return;
  205. }
  206. var toStatic = utils.isObjectType(modifierType) && utils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous);
  207. if (!toStatic && this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR) {
  208. return;
  209. }
  210. var variable = node.name.text;
  211. (toStatic ? this.staticVariableModifications : this.memberVariableModifications).add(variable);
  212. };
  213. ClassScope.prototype.enterConstructor = function () {
  214. this.constructorScopeDepth = DIRECTLY_INSIDE_CONSTRUCTOR;
  215. };
  216. ClassScope.prototype.exitConstructor = function () {
  217. this.constructorScopeDepth = OUTSIDE_CONSTRUCTOR;
  218. };
  219. ClassScope.prototype.enterNonConstructorScope = function () {
  220. if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) {
  221. this.constructorScopeDepth += 1;
  222. }
  223. };
  224. ClassScope.prototype.exitNonConstructorScope = function () {
  225. if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) {
  226. this.constructorScopeDepth -= 1;
  227. }
  228. };
  229. ClassScope.prototype.finalizeUnmodifiedPrivateNonReadonlys = function () {
  230. var _this = this;
  231. this.memberVariableModifications.forEach(function (variableName) {
  232. _this.privateModifiableMembers.delete(variableName);
  233. });
  234. this.staticVariableModifications.forEach(function (variableName) {
  235. _this.privateModifiableStatics.delete(variableName);
  236. });
  237. return Array.from(this.privateModifiableMembers.values()).concat(Array.from(this.privateModifiableStatics.values()));
  238. };
  239. return ClassScope;
  240. }());
  241. var templateObject_1, templateObject_2, templateObject_3;