1 | "use strict";
|
2 | var __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 | }));
|
9 | var __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 | });
|
14 | var __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 | };
|
21 | Object.defineProperty(exports, "__esModule", { value: true });
|
22 | const tsutils = __importStar(require("tsutils"));
|
23 | const ts = __importStar(require("typescript"));
|
24 | const util = __importStar(require("../util"));
|
25 | const util_1 = require("../util");
|
26 | const utils_1 = require("@typescript-eslint/utils");
|
27 | const 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(', ');
|
33 | exports.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 | });
|
176 | const OUTSIDE_CONSTRUCTOR = -1;
|
177 | const DIRECTLY_INSIDE_CONSTRUCTOR = 0;
|
178 | class 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 |
|
\ | No newline at end of file |