@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -14
- package/lib/index.js +20 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +68 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +119 -49
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-imports.js +42 -21
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +66 -15
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
- package/lib/rules/global-const-style.js +25 -4
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
- package/lib/rules/memo-nested-react-components.js +243 -103
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +150 -489
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +147 -65
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-margin-properties.js +7 -38
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
- package/lib/rules/no-unused-props.js +215 -37
- package/lib/rules/no-useless-fragment.js +10 -2
- package/lib/rules/parallelize-async-operations.js +117 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +218 -13
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/rules/require-migration-script-metadata.d.ts +9 -0
- package/lib/rules/require-migration-script-metadata.js +206 -0
- package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
- package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
- package/lib/utils/ASTHelpers.d.ts +49 -1
- package/lib/utils/ASTHelpers.js +394 -112
- package/package.json +7 -6
- package/release-manifest.json +166 -0
package/lib/utils/ASTHelpers.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ASTHelpers = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
5
|
class ASTHelpers {
|
|
6
|
+
/**
|
|
7
|
+
* AST node shapes vary across ESLint/typescript-eslint versions, with some
|
|
8
|
+
* node types (ParenthesizedExpression, TSSatisfiesExpression) not consistently
|
|
9
|
+
* available in type definitions. Type guards and runtime checks ensure correctness
|
|
10
|
+
* despite these discrepancies, trading compile-time safety for cross-version
|
|
11
|
+
* compatibility until type definitions stabilize.
|
|
12
|
+
*
|
|
13
|
+
* Semantics Contract:
|
|
14
|
+
* - Helpers like getScope and returnsJSX must be invoked from the active
|
|
15
|
+
* visitor traversal context (ESLint 8 compatible).
|
|
16
|
+
* - returnsJSX is a heuristic and does not perform a full control-flow proof.
|
|
17
|
+
* - Reliance on runtime type guards (isParenthesizedExpression,
|
|
18
|
+
* isLoopOrLabeledStatement) is intentional.
|
|
19
|
+
*/
|
|
5
20
|
/**
|
|
6
21
|
* Finds a variable by name in the scope chain starting from the given scope.
|
|
7
22
|
*/
|
|
@@ -17,9 +32,30 @@ class ASTHelpers {
|
|
|
17
32
|
}
|
|
18
33
|
return null;
|
|
19
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Compatibility wrapper for getting the scope of a node across ESLint versions.
|
|
37
|
+
* ESLint 9 moves getScope onto sourceCode; ESLint 8 exposes context.getScope().
|
|
38
|
+
*/
|
|
39
|
+
static getScope(context, node) {
|
|
40
|
+
const sourceCode = context.sourceCode;
|
|
41
|
+
const sourceGetScope = sourceCode?.getScope;
|
|
42
|
+
const contextGetScope = context.getScope;
|
|
43
|
+
if (typeof sourceGetScope === 'function') {
|
|
44
|
+
try {
|
|
45
|
+
return sourceGetScope.call(sourceCode, node);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to context.getScope
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (typeof contextGetScope === 'function') {
|
|
52
|
+
return contextGetScope.call(context);
|
|
53
|
+
}
|
|
54
|
+
throw new Error('getScope is not available in this ESLint version.');
|
|
55
|
+
}
|
|
20
56
|
static blockIncludesIdentifier(block) {
|
|
21
57
|
for (const statement of block.body) {
|
|
22
|
-
if (
|
|
58
|
+
if (this.declarationIncludesIdentifier(statement)) {
|
|
23
59
|
return true;
|
|
24
60
|
}
|
|
25
61
|
}
|
|
@@ -30,7 +66,7 @@ class ASTHelpers {
|
|
|
30
66
|
return false;
|
|
31
67
|
}
|
|
32
68
|
// Gracefully handle ParenthesizedExpression without widening AST node types
|
|
33
|
-
if (node
|
|
69
|
+
if (this.isParenthesizedExpression(node)) {
|
|
34
70
|
return this.declarationIncludesIdentifier(node.expression);
|
|
35
71
|
}
|
|
36
72
|
switch (node.type) {
|
|
@@ -39,11 +75,11 @@ class ASTHelpers {
|
|
|
39
75
|
case 'TSSatisfiesExpression':
|
|
40
76
|
return this.declarationIncludesIdentifier(node.expression);
|
|
41
77
|
case 'ArrayPattern':
|
|
42
|
-
return node.elements.some((element) =>
|
|
78
|
+
return node.elements.some((element) => this.declarationIncludesIdentifier(element));
|
|
43
79
|
case 'ObjectPattern':
|
|
44
|
-
return node.properties.some((property) => this.declarationIncludesIdentifier(property
|
|
80
|
+
return node.properties.some((property) => this.declarationIncludesIdentifier(property));
|
|
45
81
|
case 'AssignmentPattern':
|
|
46
|
-
return this.declarationIncludesIdentifier(node.
|
|
82
|
+
return this.declarationIncludesIdentifier(node.right);
|
|
47
83
|
case 'RestElement':
|
|
48
84
|
return this.declarationIncludesIdentifier(node.argument);
|
|
49
85
|
case 'AwaitExpression':
|
|
@@ -52,8 +88,28 @@ class ASTHelpers {
|
|
|
52
88
|
return (this.declarationIncludesIdentifier(node.left) ||
|
|
53
89
|
this.declarationIncludesIdentifier(node.right));
|
|
54
90
|
case 'BlockStatement':
|
|
55
|
-
return node.body.some((statement) => statement
|
|
56
|
-
|
|
91
|
+
return node.body.some((statement) => this.declarationIncludesIdentifier(statement));
|
|
92
|
+
case 'ExpressionStatement':
|
|
93
|
+
return this.declarationIncludesIdentifier(node.expression);
|
|
94
|
+
case 'TryStatement':
|
|
95
|
+
return (this.declarationIncludesIdentifier(node.block) ||
|
|
96
|
+
this.declarationIncludesIdentifier(node.handler) ||
|
|
97
|
+
this.declarationIncludesIdentifier(node.finalizer));
|
|
98
|
+
case 'CatchClause':
|
|
99
|
+
return (this.patternHasDependency(node.param) ||
|
|
100
|
+
this.declarationIncludesIdentifier(node.body));
|
|
101
|
+
case 'ReturnStatement':
|
|
102
|
+
case 'ThrowStatement':
|
|
103
|
+
return this.declarationIncludesIdentifier(node.argument);
|
|
104
|
+
case 'VariableDeclaration':
|
|
105
|
+
return node.declarations.some((decl) => this.declarationIncludesIdentifier(decl));
|
|
106
|
+
case 'VariableDeclarator':
|
|
107
|
+
return (this.patternHasDependency(node.id) ||
|
|
108
|
+
this.declarationIncludesIdentifier(node.init));
|
|
109
|
+
case 'FunctionDeclaration':
|
|
110
|
+
case 'FunctionExpression':
|
|
111
|
+
case 'ArrowFunctionExpression':
|
|
112
|
+
return (node.params.some((param) => this.patternHasDependency(param)) || this.declarationIncludesIdentifier(node.body));
|
|
57
113
|
case 'IfStatement':
|
|
58
114
|
return (this.declarationIncludesIdentifier(node.test) ||
|
|
59
115
|
this.declarationIncludesIdentifier(node.consequent) ||
|
|
@@ -63,26 +119,30 @@ class ASTHelpers {
|
|
|
63
119
|
case 'Identifier':
|
|
64
120
|
return true;
|
|
65
121
|
case 'SpreadElement':
|
|
66
|
-
return
|
|
122
|
+
return this.declarationIncludesIdentifier(node.argument);
|
|
67
123
|
case 'ChainExpression':
|
|
68
|
-
return
|
|
124
|
+
return this.declarationIncludesIdentifier(node.expression);
|
|
69
125
|
case 'ArrayExpression':
|
|
70
126
|
return node.elements.some((element) => element &&
|
|
71
127
|
(element.type === 'SpreadElement'
|
|
72
|
-
?
|
|
73
|
-
:
|
|
128
|
+
? this.declarationIncludesIdentifier(element.argument)
|
|
129
|
+
: this.declarationIncludesIdentifier(element)));
|
|
74
130
|
case 'ObjectExpression':
|
|
75
131
|
return node.properties.some((property) => {
|
|
76
132
|
if (property.type === 'Property') {
|
|
77
|
-
return
|
|
133
|
+
return ((property.computed &&
|
|
134
|
+
this.declarationIncludesIdentifier(property.key)) ||
|
|
135
|
+
this.declarationIncludesIdentifier(property.value));
|
|
78
136
|
}
|
|
79
137
|
else if (property.type === 'SpreadElement') {
|
|
80
|
-
return
|
|
138
|
+
return this.declarationIncludesIdentifier(property.argument);
|
|
81
139
|
}
|
|
82
140
|
return false;
|
|
83
141
|
});
|
|
84
142
|
case 'Property':
|
|
85
|
-
return
|
|
143
|
+
return ((node.computed &&
|
|
144
|
+
this.declarationIncludesIdentifier(node.key)) ||
|
|
145
|
+
this.declarationIncludesIdentifier(node.value));
|
|
86
146
|
case 'BinaryExpression':
|
|
87
147
|
case 'LogicalExpression':
|
|
88
148
|
return (this.declarationIncludesIdentifier(node.left) ||
|
|
@@ -130,160 +190,207 @@ class ASTHelpers {
|
|
|
130
190
|
return false;
|
|
131
191
|
}
|
|
132
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Checks if a pattern (in a declaration or parameter) contains any dependencies.
|
|
195
|
+
* Patterns themselves define new bindings (Identifiers), but they can contain
|
|
196
|
+
* dependencies in computed keys or default values (AssignmentPattern).
|
|
197
|
+
*/
|
|
198
|
+
static patternHasDependency(node) {
|
|
199
|
+
if (!node) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
switch (node.type) {
|
|
203
|
+
case 'Identifier':
|
|
204
|
+
return false; // Declaration site, not a reference
|
|
205
|
+
case 'AssignmentPattern':
|
|
206
|
+
return this.declarationIncludesIdentifier(node.right);
|
|
207
|
+
case 'ArrayPattern':
|
|
208
|
+
return node.elements.some((element) => this.patternHasDependency(element));
|
|
209
|
+
case 'ObjectPattern':
|
|
210
|
+
return node.properties.some((property) => this.patternHasDependency(property));
|
|
211
|
+
case 'Property':
|
|
212
|
+
return ((node.computed &&
|
|
213
|
+
this.declarationIncludesIdentifier(node.key)) ||
|
|
214
|
+
this.patternHasDependency(node.value));
|
|
215
|
+
case 'RestElement':
|
|
216
|
+
return this.patternHasDependency(node.argument);
|
|
217
|
+
default:
|
|
218
|
+
// For anything else (like nested expressions in computed keys),
|
|
219
|
+
// fall back to the general check.
|
|
220
|
+
return this.declarationIncludesIdentifier(node);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
133
223
|
static classMethodDependenciesOf(node, graph, className) {
|
|
134
224
|
const dependencies = [];
|
|
135
225
|
if (!node) {
|
|
136
226
|
return dependencies;
|
|
137
227
|
}
|
|
138
228
|
// Gracefully handle ParenthesizedExpression without widening AST node types
|
|
139
|
-
if (node
|
|
140
|
-
return
|
|
229
|
+
if (this.isParenthesizedExpression(node)) {
|
|
230
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
141
231
|
}
|
|
142
232
|
switch (node.type) {
|
|
143
|
-
case 'MethodDefinition':
|
|
233
|
+
case 'MethodDefinition': {
|
|
144
234
|
const functionBody = node.value.body;
|
|
145
235
|
return (functionBody?.body || [])
|
|
146
|
-
.map((statement) =>
|
|
236
|
+
.map((statement) => this.classMethodDependenciesOf(statement, graph, className))
|
|
147
237
|
.flat();
|
|
238
|
+
}
|
|
148
239
|
case 'Identifier':
|
|
149
240
|
dependencies.push(node.name);
|
|
150
241
|
break;
|
|
151
242
|
case 'ExpressionStatement':
|
|
152
|
-
return
|
|
153
|
-
case 'MemberExpression':
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
243
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
244
|
+
case 'MemberExpression': {
|
|
245
|
+
const memberExpr = node;
|
|
246
|
+
if ((memberExpr.object.type === 'ThisExpression' &&
|
|
247
|
+
memberExpr.property.type === 'Identifier') ||
|
|
248
|
+
(memberExpr.object.type === 'Identifier' &&
|
|
249
|
+
memberExpr.object.name === className &&
|
|
250
|
+
memberExpr.property.type === 'Identifier')) {
|
|
251
|
+
dependencies.push(memberExpr.property.name);
|
|
160
252
|
}
|
|
161
253
|
else {
|
|
162
254
|
return [
|
|
163
|
-
...
|
|
164
|
-
...
|
|
255
|
+
...this.classMethodDependenciesOf(memberExpr.object, graph, className),
|
|
256
|
+
...this.classMethodDependenciesOf(memberExpr.property, graph, className),
|
|
165
257
|
];
|
|
166
258
|
}
|
|
167
259
|
break;
|
|
260
|
+
}
|
|
168
261
|
case 'TSNonNullExpression':
|
|
169
|
-
return
|
|
262
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
170
263
|
case 'ArrayPattern':
|
|
171
264
|
return node.elements
|
|
172
|
-
.map((element) =>
|
|
265
|
+
.map((element) => this.classMethodDependenciesOf(element, graph, className))
|
|
173
266
|
.flat();
|
|
174
267
|
case 'ObjectPattern':
|
|
175
268
|
return node.properties
|
|
176
|
-
.map((property) =>
|
|
269
|
+
.map((property) => this.classMethodDependenciesOf(property, graph, className))
|
|
177
270
|
.flat();
|
|
178
271
|
case 'AssignmentPattern':
|
|
179
|
-
return
|
|
272
|
+
return this.classMethodDependenciesOf(node.left, graph, className);
|
|
180
273
|
case 'RestElement':
|
|
181
|
-
return
|
|
274
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
182
275
|
case 'AwaitExpression':
|
|
183
|
-
return
|
|
184
|
-
case 'AssignmentExpression':
|
|
276
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
277
|
+
case 'AssignmentExpression': {
|
|
278
|
+
const assignExpr = node;
|
|
185
279
|
return [
|
|
186
|
-
...
|
|
187
|
-
...
|
|
280
|
+
...this.classMethodDependenciesOf(assignExpr.left, graph, className),
|
|
281
|
+
...this.classMethodDependenciesOf(assignExpr.right, graph, className),
|
|
188
282
|
];
|
|
283
|
+
}
|
|
189
284
|
case 'BlockStatement':
|
|
190
285
|
return node.body
|
|
191
|
-
.map((statement) =>
|
|
286
|
+
.map((statement) => this.classMethodDependenciesOf(statement, graph, className))
|
|
192
287
|
.flat()
|
|
193
288
|
.filter(Boolean);
|
|
194
|
-
case 'IfStatement':
|
|
289
|
+
case 'IfStatement': {
|
|
290
|
+
const ifStmt = node;
|
|
195
291
|
return [
|
|
196
|
-
...
|
|
197
|
-
...
|
|
198
|
-
...
|
|
292
|
+
...this.classMethodDependenciesOf(ifStmt.test, graph, className),
|
|
293
|
+
...this.classMethodDependenciesOf(ifStmt.consequent, graph, className),
|
|
294
|
+
...this.classMethodDependenciesOf(ifStmt.alternate, graph, className),
|
|
199
295
|
];
|
|
296
|
+
}
|
|
200
297
|
case 'TSTypeAssertion':
|
|
201
|
-
return
|
|
202
|
-
case 'Identifier':
|
|
203
|
-
return dependencies;
|
|
298
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
204
299
|
case 'SpreadElement':
|
|
205
|
-
return
|
|
300
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
206
301
|
case 'ChainExpression':
|
|
207
|
-
return
|
|
302
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
208
303
|
case 'ArrayExpression':
|
|
209
304
|
return node.elements
|
|
210
305
|
.map((element) => element &&
|
|
211
306
|
(element.type === 'SpreadElement'
|
|
212
|
-
?
|
|
213
|
-
:
|
|
307
|
+
? this.classMethodDependenciesOf(element.argument, graph, className)
|
|
308
|
+
: this.classMethodDependenciesOf(element, graph, className)))
|
|
214
309
|
.flat()
|
|
215
310
|
.filter(Boolean);
|
|
216
311
|
case 'ObjectExpression':
|
|
217
312
|
return node.properties
|
|
218
313
|
.map((property) => {
|
|
219
314
|
if (property.type === 'Property') {
|
|
220
|
-
return
|
|
315
|
+
return this.classMethodDependenciesOf(property.value, graph, className);
|
|
221
316
|
}
|
|
222
317
|
else if (property.type === 'SpreadElement') {
|
|
223
|
-
return
|
|
318
|
+
return this.classMethodDependenciesOf(property.argument, graph, className);
|
|
224
319
|
}
|
|
225
320
|
return false;
|
|
226
321
|
})
|
|
227
322
|
.flat()
|
|
228
323
|
.filter(Boolean);
|
|
229
324
|
case 'Property':
|
|
230
|
-
return
|
|
325
|
+
return this.classMethodDependenciesOf(node.value, graph, className);
|
|
231
326
|
case 'BinaryExpression':
|
|
232
|
-
case 'LogicalExpression':
|
|
327
|
+
case 'LogicalExpression': {
|
|
328
|
+
const binLogExpr = node;
|
|
233
329
|
return [
|
|
234
|
-
...
|
|
235
|
-
...
|
|
330
|
+
...this.classMethodDependenciesOf(binLogExpr.left, graph, className),
|
|
331
|
+
...this.classMethodDependenciesOf(binLogExpr.right, graph, className),
|
|
236
332
|
];
|
|
333
|
+
}
|
|
237
334
|
case 'UnaryExpression':
|
|
238
335
|
case 'UpdateExpression':
|
|
239
|
-
return
|
|
336
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
240
337
|
case 'CallExpression':
|
|
241
|
-
case 'NewExpression':
|
|
338
|
+
case 'NewExpression': {
|
|
242
339
|
// For function and constructor calls, we care about both the callee and the arguments.
|
|
340
|
+
const callNewExpr = node;
|
|
243
341
|
return [
|
|
244
|
-
...
|
|
245
|
-
...
|
|
246
|
-
.map((arg) =>
|
|
342
|
+
...this.classMethodDependenciesOf(callNewExpr.callee, graph, className),
|
|
343
|
+
...callNewExpr.arguments
|
|
344
|
+
.map((arg) => this.classMethodDependenciesOf(arg, graph, className))
|
|
247
345
|
.flat(),
|
|
248
346
|
];
|
|
249
|
-
|
|
347
|
+
}
|
|
348
|
+
case 'ConditionalExpression': {
|
|
349
|
+
const condExpr = node;
|
|
250
350
|
return [
|
|
251
|
-
...
|
|
252
|
-
...
|
|
253
|
-
...
|
|
351
|
+
...this.classMethodDependenciesOf(condExpr.test, graph, className),
|
|
352
|
+
...this.classMethodDependenciesOf(condExpr.consequent, graph, className),
|
|
353
|
+
...this.classMethodDependenciesOf(condExpr.alternate, graph, className),
|
|
254
354
|
];
|
|
355
|
+
}
|
|
255
356
|
case 'TSAsExpression':
|
|
256
|
-
return
|
|
357
|
+
return this.classMethodDependenciesOf(node.expression, graph, className);
|
|
257
358
|
case 'VariableDeclaration':
|
|
258
359
|
return node.declarations
|
|
259
|
-
.map((declaration) =>
|
|
360
|
+
.map((declaration) => this.classMethodDependenciesOf(declaration, graph, className))
|
|
260
361
|
.flat()
|
|
261
362
|
.filter(Boolean);
|
|
262
363
|
case 'VariableDeclarator':
|
|
263
|
-
return
|
|
264
|
-
case 'ForOfStatement':
|
|
364
|
+
return this.classMethodDependenciesOf(node.init, graph, className);
|
|
365
|
+
case 'ForOfStatement': {
|
|
366
|
+
const forOfStmt = node;
|
|
265
367
|
return [
|
|
266
|
-
...
|
|
267
|
-
...
|
|
268
|
-
...
|
|
368
|
+
...this.classMethodDependenciesOf(forOfStmt.left, graph, className),
|
|
369
|
+
...this.classMethodDependenciesOf(forOfStmt.body, graph, className),
|
|
370
|
+
...this.classMethodDependenciesOf(forOfStmt.right, graph, className),
|
|
269
371
|
];
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
372
|
+
}
|
|
373
|
+
case 'ForStatement': {
|
|
374
|
+
const forStmt = node;
|
|
375
|
+
return [forStmt.body, forStmt.init, forStmt.test, forStmt.update]
|
|
376
|
+
.map((node) => this.classMethodDependenciesOf(node, graph, className))
|
|
273
377
|
.flat();
|
|
378
|
+
}
|
|
274
379
|
case 'ThrowStatement':
|
|
275
|
-
return
|
|
380
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
276
381
|
case 'TemplateLiteral':
|
|
277
382
|
return node.expressions
|
|
278
|
-
.map((expression) =>
|
|
383
|
+
.map((expression) => this.classMethodDependenciesOf(expression, graph, className))
|
|
279
384
|
.flat();
|
|
280
385
|
case 'ReturnStatement':
|
|
281
|
-
return
|
|
282
|
-
case 'ArrowFunctionExpression':
|
|
386
|
+
return this.classMethodDependenciesOf(node.argument, graph, className);
|
|
387
|
+
case 'ArrowFunctionExpression': {
|
|
388
|
+
const arrowFunc = node;
|
|
283
389
|
return [
|
|
284
|
-
...
|
|
285
|
-
...
|
|
390
|
+
...(arrowFunc.params || []).flatMap((param) => this.classMethodDependenciesOf(param, graph, className)),
|
|
391
|
+
...this.classMethodDependenciesOf(arrowFunc.body, graph, className),
|
|
286
392
|
];
|
|
393
|
+
}
|
|
287
394
|
default:
|
|
288
395
|
break;
|
|
289
396
|
}
|
|
@@ -300,17 +407,19 @@ class ASTHelpers {
|
|
|
300
407
|
return typeof value === 'object' && value !== null && 'type' in value;
|
|
301
408
|
}
|
|
302
409
|
static hasReturnStatement(node) {
|
|
303
|
-
if (node.type ===
|
|
410
|
+
if (node.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
304
411
|
return true;
|
|
305
412
|
}
|
|
306
|
-
if (node.type ===
|
|
307
|
-
const
|
|
308
|
-
const
|
|
413
|
+
if (node.type === utils_1.AST_NODE_TYPES.IfStatement) {
|
|
414
|
+
const ifStmt = node;
|
|
415
|
+
const consequentHasReturn = this.hasReturnStatement(ifStmt.consequent);
|
|
416
|
+
const alternateHasReturn = !!ifStmt.alternate && this.hasReturnStatement(ifStmt.alternate);
|
|
309
417
|
return consequentHasReturn && alternateHasReturn;
|
|
310
418
|
}
|
|
311
|
-
if (node.type ===
|
|
312
|
-
|
|
313
|
-
|
|
419
|
+
if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
420
|
+
const blockStmt = node;
|
|
421
|
+
for (const statement of blockStmt.body) {
|
|
422
|
+
if (this.hasReturnStatement(statement)) {
|
|
314
423
|
return true;
|
|
315
424
|
}
|
|
316
425
|
}
|
|
@@ -320,8 +429,8 @@ class ASTHelpers {
|
|
|
320
429
|
continue; // Ignore the parent property
|
|
321
430
|
}
|
|
322
431
|
const value = node[key];
|
|
323
|
-
if (
|
|
324
|
-
if (
|
|
432
|
+
if (this.isNode(value)) {
|
|
433
|
+
if (this.hasReturnStatement(value)) {
|
|
325
434
|
return true;
|
|
326
435
|
}
|
|
327
436
|
}
|
|
@@ -330,55 +439,228 @@ class ASTHelpers {
|
|
|
330
439
|
}
|
|
331
440
|
static isNodeExported(node) {
|
|
332
441
|
// Checking if the node is exported as a named export.
|
|
333
|
-
if (node.parent &&
|
|
442
|
+
if (node.parent &&
|
|
443
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
334
444
|
return true;
|
|
335
445
|
}
|
|
336
446
|
// Checking if the node is exported as default.
|
|
337
447
|
if (node.parent &&
|
|
338
448
|
node.parent.parent &&
|
|
339
|
-
node.parent.parent.type ===
|
|
449
|
+
node.parent.parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
340
450
|
return true;
|
|
341
451
|
}
|
|
342
452
|
// Checking if the node is exported in a list of exports.
|
|
343
453
|
if (node.parent &&
|
|
344
454
|
node.parent.parent &&
|
|
345
|
-
node.parent.parent.type ===
|
|
346
|
-
node.parent.parent.exported
|
|
455
|
+
node.parent.parent.type === utils_1.AST_NODE_TYPES.ExportSpecifier &&
|
|
456
|
+
node.parent.parent.exported?.name ===
|
|
457
|
+
node.name) {
|
|
347
458
|
return true;
|
|
348
459
|
}
|
|
349
460
|
return false;
|
|
350
461
|
}
|
|
351
|
-
static
|
|
352
|
-
|
|
462
|
+
static isLoopOrLabeledStatement(node) {
|
|
463
|
+
return (node.type === utils_1.AST_NODE_TYPES.WhileStatement ||
|
|
464
|
+
node.type === utils_1.AST_NODE_TYPES.DoWhileStatement ||
|
|
465
|
+
node.type === utils_1.AST_NODE_TYPES.ForStatement ||
|
|
466
|
+
node.type === utils_1.AST_NODE_TYPES.ForInStatement ||
|
|
467
|
+
node.type === utils_1.AST_NODE_TYPES.ForOfStatement ||
|
|
468
|
+
node.type === utils_1.AST_NODE_TYPES.LabeledStatement);
|
|
469
|
+
}
|
|
470
|
+
static isParenthesizedExpression(node) {
|
|
471
|
+
// ParenthesizedExpression is not in AST_NODE_TYPES across all ESLint versions
|
|
472
|
+
// so we check the string literal directly for cross-version compatibility.
|
|
473
|
+
return node?.type === 'ParenthesizedExpression';
|
|
474
|
+
}
|
|
475
|
+
static returnsJSXValue(node) {
|
|
476
|
+
if (!node) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
480
|
+
node.type === utils_1.AST_NODE_TYPES.JSXFragment) {
|
|
353
481
|
return true;
|
|
354
482
|
}
|
|
355
|
-
if (node.type ===
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
483
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
484
|
+
return (this.returnsJSXValue(node.left) ||
|
|
485
|
+
this.returnsJSXValue(node.right));
|
|
486
|
+
}
|
|
487
|
+
if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
488
|
+
return (this.returnsJSXValue(node.consequent) ||
|
|
489
|
+
this.returnsJSXValue(node.alternate));
|
|
490
|
+
}
|
|
491
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
492
|
+
node.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
493
|
+
node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
494
|
+
node.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
495
|
+
return this.returnsJSXValue(node.expression);
|
|
496
|
+
}
|
|
497
|
+
if (this.isParenthesizedExpression(node)) {
|
|
498
|
+
return this.returnsJSXValue(node.expression);
|
|
499
|
+
}
|
|
500
|
+
// Function/class values are not JSX values.
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
static returnsJSXFromStatement(node, context) {
|
|
504
|
+
if (!node) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
if (node.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
508
|
+
const arg = node.argument;
|
|
509
|
+
if (arg?.type === utils_1.AST_NODE_TYPES.Identifier && context) {
|
|
510
|
+
// Resolve variable to its initializer if possible
|
|
511
|
+
const scope = this.getScope(context, arg);
|
|
512
|
+
if (scope) {
|
|
513
|
+
const variable = this.findVariableInScope(scope, arg.name);
|
|
514
|
+
if (variable && variable.defs.length === 1) {
|
|
515
|
+
const def = variable.defs[0];
|
|
516
|
+
// Check if the variable is reassigned after initialization.
|
|
517
|
+
// We only follow variables that are defined once and never reassigned
|
|
518
|
+
// to ensure we're following a deterministic JSX-returning value.
|
|
519
|
+
// This is intentionally conservative to avoid ambiguous multi-write cases,
|
|
520
|
+
// which affects React component detection accuracy.
|
|
521
|
+
const isReassigned = variable.references.some((ref) => ref.isWrite() && !ref.init);
|
|
522
|
+
if (isReassigned) {
|
|
523
|
+
return this.returnsJSXValue(arg);
|
|
524
|
+
}
|
|
525
|
+
if (def.type === 'Variable' &&
|
|
526
|
+
def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
527
|
+
def.node.init) {
|
|
528
|
+
// ReturnStatement returns a value; treat function/class initializers as non-JSX values.
|
|
529
|
+
return this.returnsJSXValue(def.node.init);
|
|
530
|
+
}
|
|
369
531
|
}
|
|
370
532
|
}
|
|
533
|
+
else {
|
|
534
|
+
return this.returnsJSXValue(arg);
|
|
535
|
+
}
|
|
371
536
|
}
|
|
537
|
+
return this.returnsJSXValue(arg);
|
|
538
|
+
}
|
|
539
|
+
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
540
|
+
// Variable declarations don't return values from the enclosing function.
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
544
|
+
return node.body.some((stmt) => this.returnsJSXFromStatement(stmt, context));
|
|
545
|
+
}
|
|
546
|
+
if (node.type === utils_1.AST_NODE_TYPES.IfStatement) {
|
|
547
|
+
return (this.returnsJSXFromStatement(node.consequent, context) ||
|
|
548
|
+
this.returnsJSXFromStatement(node.alternate, context));
|
|
549
|
+
}
|
|
550
|
+
if (node.type === utils_1.AST_NODE_TYPES.SwitchStatement) {
|
|
551
|
+
return node.cases.some((c) => c.consequent.some((stmt) => this.returnsJSXFromStatement(stmt, context)));
|
|
372
552
|
}
|
|
373
|
-
if (node.type ===
|
|
374
|
-
return (
|
|
375
|
-
|
|
553
|
+
if (node.type === utils_1.AST_NODE_TYPES.TryStatement) {
|
|
554
|
+
return (this.returnsJSXFromStatement(node.block, context) ||
|
|
555
|
+
this.returnsJSXFromStatement(node.handler?.body, context) ||
|
|
556
|
+
this.returnsJSXFromStatement(node.finalizer, context));
|
|
557
|
+
}
|
|
558
|
+
if (this.isLoopOrLabeledStatement(node)) {
|
|
559
|
+
return this.returnsJSXFromStatement(node.body, context);
|
|
376
560
|
}
|
|
377
561
|
return false;
|
|
378
562
|
}
|
|
563
|
+
static returnsJSX(node, context) {
|
|
564
|
+
if (!node) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
if (node.type === utils_1.AST_NODE_TYPES.ExpressionStatement) {
|
|
568
|
+
// ExpressionStatement does not produce a return value for the surrounding function, so treat as non-returning.
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
572
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
573
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
574
|
+
const func = node;
|
|
575
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
576
|
+
return func.body.type === utils_1.AST_NODE_TYPES.BlockStatement
|
|
577
|
+
? this.returnsJSXFromStatement(func.body, context)
|
|
578
|
+
: this.returnsJSXValue(func.body);
|
|
579
|
+
}
|
|
580
|
+
return this.returnsJSXFromStatement(func.body, context);
|
|
581
|
+
}
|
|
582
|
+
if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
583
|
+
node.type === utils_1.AST_NODE_TYPES.JSXFragment) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
587
|
+
// Detects `const Component = () => <div />`-style declarations.
|
|
588
|
+
return node.declarations.some((decl) => this.returnsJSX(decl.init, context));
|
|
589
|
+
}
|
|
590
|
+
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
591
|
+
return this.returnsJSX(node.init, context);
|
|
592
|
+
}
|
|
593
|
+
// Treat remaining nodes as statement-path or value checks.
|
|
594
|
+
return (this.returnsJSXFromStatement(node, context) || this.returnsJSXValue(node));
|
|
595
|
+
}
|
|
379
596
|
static hasParameters(node) {
|
|
380
597
|
return node.params && node.params.length > 0;
|
|
381
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Compatibility wrapper for getting declared variables across ESLint versions.
|
|
601
|
+
*/
|
|
602
|
+
static getDeclaredVariables(context, node) {
|
|
603
|
+
const sourceCode = context.sourceCode;
|
|
604
|
+
const sourceCodeWithDeclaredVariables = sourceCode;
|
|
605
|
+
const fn = typeof sourceCodeWithDeclaredVariables?.getDeclaredVariables ===
|
|
606
|
+
'function'
|
|
607
|
+
? sourceCodeWithDeclaredVariables.getDeclaredVariables.bind(sourceCodeWithDeclaredVariables)
|
|
608
|
+
: typeof context.getDeclaredVariables === 'function'
|
|
609
|
+
? context.getDeclaredVariables.bind(context)
|
|
610
|
+
: null;
|
|
611
|
+
if (!fn) {
|
|
612
|
+
throw new Error('getDeclaredVariables is not available in this ESLint version.');
|
|
613
|
+
}
|
|
614
|
+
return fn(node);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Checks if a call expression or new expression is a call to HttpsError.
|
|
618
|
+
* Handles both 'HttpsError' and 'https.HttpsError'.
|
|
619
|
+
*/
|
|
620
|
+
static isHttpsErrorCall(callee) {
|
|
621
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
622
|
+
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
623
|
+
callee.object.name === 'https' &&
|
|
624
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
625
|
+
callee.property.name === 'HttpsError');
|
|
626
|
+
}
|
|
627
|
+
else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
628
|
+
return callee.name === 'HttpsError';
|
|
629
|
+
}
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Checks if a call expression is a call to toHttpsError.
|
|
634
|
+
* Handles both 'toHttpsError' and 'https.toHttpsError'.
|
|
635
|
+
*/
|
|
636
|
+
static isToHttpsErrorCall(callee) {
|
|
637
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
638
|
+
return (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
639
|
+
callee.object.name === 'https' &&
|
|
640
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
641
|
+
callee.property.name === 'toHttpsError');
|
|
642
|
+
}
|
|
643
|
+
else if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
644
|
+
return callee.name === 'toHttpsError';
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Unwraps TypeScript-specific nodes (assertions, non-null, satisfies) and
|
|
650
|
+
* parenthesized expressions to get to the underlying expression.
|
|
651
|
+
*/
|
|
652
|
+
static unwrapTSAssertions(node) {
|
|
653
|
+
let inner = node;
|
|
654
|
+
while (inner &&
|
|
655
|
+
(inner.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
656
|
+
inner.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
657
|
+
inner.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
658
|
+
inner.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
659
|
+
inner.type === 'ParenthesizedExpression')) {
|
|
660
|
+
inner = inner.expression;
|
|
661
|
+
}
|
|
662
|
+
return inner;
|
|
663
|
+
}
|
|
382
664
|
/**
|
|
383
665
|
* Helper to get ancestors of a node in a way that is compatible with both ESLint v8 and v9.
|
|
384
666
|
* In ESLint v9, context.getAncestors() is deprecated and moved to context.sourceCode.getAncestors(node).
|