UNPKG

11.1 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 tsutils = __importStar(require("tsutils"));
23const ts = __importStar(require("typescript"));
24const util = __importStar(require("../util"));
25const util_1 = require("../util");
26const utils_1 = require("@typescript-eslint/utils");
27const functionScopeBoundaries = [
28 utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
29 utils_1.AST_NODE_TYPES.FunctionDeclaration,
30 utils_1.AST_NODE_TYPES.FunctionExpression,
31 utils_1.AST_NODE_TYPES.MethodDefinition,
32].join(', ');
33exports.default = util.createRule({
34 name: 'prefer-readonly',
35 meta: {
36 docs: {
37 description: "Requires that private members are marked as `readonly` if they're never modified outside of the constructor",
38 recommended: false,
39 requiresTypeChecking: true,
40 },
41 fixable: 'code',
42 messages: {
43 preferReadonly: "Member '{{name}}' is never reassigned; mark it as `readonly`.",
44 },
45 schema: [
46 {
47 allowAdditionalProperties: false,
48 properties: {
49 onlyInlineLambdas: {
50 type: 'boolean',
51 },
52 },
53 type: 'object',
54 },
55 ],
56 type: 'suggestion',
57 },
58 defaultOptions: [{ onlyInlineLambdas: false }],
59 create(context, [{ onlyInlineLambdas }]) {
60 const parserServices = util.getParserServices(context);
61 const checker = parserServices.program.getTypeChecker();
62 const classScopeStack = [];
63 function handlePropertyAccessExpression(node, parent, classScope) {
64 if (ts.isBinaryExpression(parent)) {
65 handleParentBinaryExpression(node, parent, classScope);
66 return;
67 }
68 if (ts.isDeleteExpression(parent) || isDestructuringAssignment(node)) {
69 classScope.addVariableModification(node);
70 return;
71 }
72 if (ts.isPostfixUnaryExpression(parent) ||
73 ts.isPrefixUnaryExpression(parent)) {
74 handleParentPostfixOrPrefixUnaryExpression(parent, classScope);
75 }
76 }
77 function handleParentBinaryExpression(node, parent, classScope) {
78 if (parent.left === node &&
79 tsutils.isAssignmentKind(parent.operatorToken.kind)) {
80 classScope.addVariableModification(node);
81 }
82 }
83 function handleParentPostfixOrPrefixUnaryExpression(node, classScope) {
84 if (node.operator === ts.SyntaxKind.PlusPlusToken ||
85 node.operator === ts.SyntaxKind.MinusMinusToken) {
86 classScope.addVariableModification(node.operand);
87 }
88 }
89 function isDestructuringAssignment(node) {
90 let current = node.parent;
91 while (current) {
92 const parent = current.parent;
93 if (ts.isObjectLiteralExpression(parent) ||
94 ts.isArrayLiteralExpression(parent) ||
95 ts.isSpreadAssignment(parent) ||
96 (ts.isSpreadElement(parent) &&
97 ts.isArrayLiteralExpression(parent.parent))) {
98 current = parent;
99 }
100 else if (ts.isBinaryExpression(parent)) {
101 return (parent.left === current &&
102 parent.operatorToken.kind === ts.SyntaxKind.EqualsToken);
103 }
104 else {
105 break;
106 }
107 }
108 return false;
109 }
110 function isFunctionScopeBoundaryInStack(node) {
111 if (classScopeStack.length === 0) {
112 return false;
113 }
114 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
115 if (ts.isConstructorDeclaration(tsNode)) {
116 return false;
117 }
118 return tsutils.isFunctionScopeBoundary(tsNode);
119 }
120 function getEsNodesFromViolatingNode(violatingNode) {
121 if (ts.isParameterPropertyDeclaration(violatingNode, violatingNode.parent)) {
122 return {
123 esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name),
124 nameNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name),
125 };
126 }
127 return {
128 esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode),
129 nameNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name),
130 };
131 }
132 return {
133 'ClassDeclaration, ClassExpression'(node) {
134 classScopeStack.push(new ClassScope(checker, parserServices.esTreeNodeToTSNodeMap.get(node), onlyInlineLambdas));
135 },
136 'ClassDeclaration, ClassExpression:exit'() {
137 const finalizedClassScope = classScopeStack.pop();
138 const sourceCode = context.getSourceCode();
139 for (const violatingNode of finalizedClassScope.finalizeUnmodifiedPrivateNonReadonlys()) {
140 const { esNode, nameNode } = getEsNodesFromViolatingNode(violatingNode);
141 context.report({
142 data: {
143 name: sourceCode.getText(nameNode),
144 },
145 fix: fixer => fixer.insertTextBefore(nameNode, 'readonly '),
146 messageId: 'preferReadonly',
147 node: esNode,
148 });
149 }
150 },
151 MemberExpression(node) {
152 if (classScopeStack.length !== 0 && !node.computed) {
153 const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
154 handlePropertyAccessExpression(tsNode, tsNode.parent, classScopeStack[classScopeStack.length - 1]);
155 }
156 },
157 [functionScopeBoundaries](node) {
158 if (utils_1.ASTUtils.isConstructor(node)) {
159 classScopeStack[classScopeStack.length - 1].enterConstructor(parserServices.esTreeNodeToTSNodeMap.get(node));
160 }
161 else if (isFunctionScopeBoundaryInStack(node)) {
162 classScopeStack[classScopeStack.length - 1].enterNonConstructor();
163 }
164 },
165 [`${functionScopeBoundaries}:exit`](node) {
166 if (utils_1.ASTUtils.isConstructor(node)) {
167 classScopeStack[classScopeStack.length - 1].exitConstructor();
168 }
169 else if (isFunctionScopeBoundaryInStack(node)) {
170 classScopeStack[classScopeStack.length - 1].exitNonConstructor();
171 }
172 },
173 };
174 },
175});
176const OUTSIDE_CONSTRUCTOR = -1;
177const DIRECTLY_INSIDE_CONSTRUCTOR = 0;
178class ClassScope {
179 constructor(checker, classNode, onlyInlineLambdas) {
180 this.checker = checker;
181 this.onlyInlineLambdas = onlyInlineLambdas;
182 this.privateModifiableMembers = new Map();
183 this.privateModifiableStatics = new Map();
184 this.memberVariableModifications = new Set();
185 this.staticVariableModifications = new Set();
186 this.constructorScopeDepth = OUTSIDE_CONSTRUCTOR;
187 this.checker = checker;
188 this.classType = checker.getTypeAtLocation(classNode);
189 for (const member of classNode.members) {
190 if (ts.isPropertyDeclaration(member)) {
191 this.addDeclaredVariable(member);
192 }
193 }
194 }
195 addDeclaredVariable(node) {
196 if (!tsutils.isModifierFlagSet(node, ts.ModifierFlags.Private) ||
197 tsutils.isModifierFlagSet(node, ts.ModifierFlags.Readonly) ||
198 ts.isComputedPropertyName(node.name)) {
199 return;
200 }
201 if (this.onlyInlineLambdas &&
202 node.initializer !== undefined &&
203 !ts.isArrowFunction(node.initializer)) {
204 return;
205 }
206 (tsutils.isModifierFlagSet(node, ts.ModifierFlags.Static)
207 ? this.privateModifiableStatics
208 : this.privateModifiableMembers).set(node.name.getText(), node);
209 }
210 addVariableModification(node) {
211 const modifierType = this.checker.getTypeAtLocation(node.expression);
212 if (!modifierType.getSymbol() ||
213 !(0, util_1.typeIsOrHasBaseType)(modifierType, this.classType)) {
214 return;
215 }
216 const modifyingStatic = tsutils.isObjectType(modifierType) &&
217 tsutils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous);
218 if (!modifyingStatic &&
219 this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR) {
220 return;
221 }
222 (modifyingStatic
223 ? this.staticVariableModifications
224 : this.memberVariableModifications).add(node.name.text);
225 }
226 enterConstructor(node) {
227 this.constructorScopeDepth = DIRECTLY_INSIDE_CONSTRUCTOR;
228 for (const parameter of node.parameters) {
229 if (tsutils.isModifierFlagSet(parameter, ts.ModifierFlags.Private)) {
230 this.addDeclaredVariable(parameter);
231 }
232 }
233 }
234 exitConstructor() {
235 this.constructorScopeDepth = OUTSIDE_CONSTRUCTOR;
236 }
237 enterNonConstructor() {
238 if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) {
239 this.constructorScopeDepth += 1;
240 }
241 }
242 exitNonConstructor() {
243 if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) {
244 this.constructorScopeDepth -= 1;
245 }
246 }
247 finalizeUnmodifiedPrivateNonReadonlys() {
248 this.memberVariableModifications.forEach(variableName => {
249 this.privateModifiableMembers.delete(variableName);
250 });
251 this.staticVariableModifications.forEach(variableName => {
252 this.privateModifiableStatics.delete(variableName);
253 });
254 return [
255 ...Array.from(this.privateModifiableMembers.values()),
256 ...Array.from(this.privateModifiableStatics.values()),
257 ];
258 }
259}
260//# sourceMappingURL=prefer-readonly.js.map
\No newline at end of file