oxlint-plugin-react-doctor 0.7.7-dev.9c612c5 → 0.7.7-dev.9fc93f8
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/dist/index.js +556 -1362
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,13 +7,6 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
-
};
|
|
15
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
-
//#endregion
|
|
17
10
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
18
11
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
19
12
|
"solid-js",
|
|
@@ -28,33 +21,17 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
28
21
|
"vidode"
|
|
29
22
|
]);
|
|
30
23
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
-
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
-
"react",
|
|
33
|
-
"react-dom",
|
|
34
|
-
"preact"
|
|
35
|
-
];
|
|
36
24
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
37
|
-
const
|
|
38
|
-
let hasNonReactRuntime = false;
|
|
39
|
-
let hasReactRuntime = false;
|
|
25
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
40
26
|
for (const statement of program.body) {
|
|
41
27
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
42
|
-
const
|
|
43
|
-
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
-
const source = importDeclaration.source;
|
|
28
|
+
const source = statement.source;
|
|
45
29
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
46
30
|
if (!value) continue;
|
|
47
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)
|
|
48
|
-
if (startsWithAny(value,
|
|
31
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
|
|
32
|
+
if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
|
|
49
33
|
}
|
|
50
|
-
return
|
|
51
|
-
hasNonReactRuntime,
|
|
52
|
-
hasReactRuntime
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
-
const runtimeImports = collectJsxRuntimeImports$1(program);
|
|
57
|
-
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
34
|
+
return false;
|
|
58
35
|
};
|
|
59
36
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
60
37
|
for (const attribute of openingNode.attributes) {
|
|
@@ -267,7 +244,6 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
267
244
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
268
245
|
const innerVisitors = create(context);
|
|
269
246
|
let fileIsNonReactJsx = false;
|
|
270
|
-
let fileImportsReactRuntime = false;
|
|
271
247
|
const wrappedVisitors = {};
|
|
272
248
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
273
249
|
if (typeof visitor !== "function") {
|
|
@@ -280,16 +256,14 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
280
256
|
}
|
|
281
257
|
if (key === "Program") {
|
|
282
258
|
wrappedVisitors.Program = (node) => {
|
|
283
|
-
|
|
284
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
259
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
286
260
|
visitor(node);
|
|
287
261
|
};
|
|
288
262
|
continue;
|
|
289
263
|
}
|
|
290
264
|
if (key === "JSXOpeningElement") {
|
|
291
265
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
292
|
-
if (!
|
|
266
|
+
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
293
267
|
if (fileIsNonReactJsx) return;
|
|
294
268
|
visitor(node);
|
|
295
269
|
};
|
|
@@ -301,9 +275,7 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
301
275
|
};
|
|
302
276
|
}
|
|
303
277
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
304
|
-
|
|
305
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
278
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
307
279
|
};
|
|
308
280
|
return wrappedVisitors;
|
|
309
281
|
});
|
|
@@ -4342,397 +4314,6 @@ const findTransparentExpressionRoot = (node) => {
|
|
|
4342
4314
|
return current;
|
|
4343
4315
|
};
|
|
4344
4316
|
//#endregion
|
|
4345
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
4346
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
4347
|
-
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
4348
|
-
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
4349
|
-
if (node.computed) {
|
|
4350
|
-
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
4351
|
-
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
4352
|
-
return null;
|
|
4353
|
-
}
|
|
4354
|
-
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
4355
|
-
if (isNodeOfType(key, "Literal")) {
|
|
4356
|
-
if (typeof key.value === "string") return key.value;
|
|
4357
|
-
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
4358
|
-
}
|
|
4359
|
-
return null;
|
|
4360
|
-
};
|
|
4361
|
-
//#endregion
|
|
4362
|
-
//#region src/plugin/utils/get-destructured-binding-property-name.ts
|
|
4363
|
-
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
4364
|
-
let bindingNode = bindingIdentifier;
|
|
4365
|
-
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
4366
|
-
const property = bindingNode.parent;
|
|
4367
|
-
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
4368
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4369
|
-
};
|
|
4370
|
-
//#endregion
|
|
4371
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
4372
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
4373
|
-
const property = memberExpression.property;
|
|
4374
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
4375
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
4376
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
4377
|
-
return null;
|
|
4378
|
-
};
|
|
4379
|
-
//#endregion
|
|
4380
|
-
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
4381
|
-
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4382
|
-
const potentiallyAliasedSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4383
|
-
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
4384
|
-
"CatchClause",
|
|
4385
|
-
"ConditionalExpression",
|
|
4386
|
-
"DoWhileStatement",
|
|
4387
|
-
"ForInStatement",
|
|
4388
|
-
"ForOfStatement",
|
|
4389
|
-
"ForStatement",
|
|
4390
|
-
"IfStatement",
|
|
4391
|
-
"LogicalExpression",
|
|
4392
|
-
"SwitchCase",
|
|
4393
|
-
"SwitchStatement",
|
|
4394
|
-
"TryStatement",
|
|
4395
|
-
"WhileStatement"
|
|
4396
|
-
]);
|
|
4397
|
-
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
4398
|
-
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
4399
|
-
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
4400
|
-
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
4401
|
-
const property = stripParenExpression(memberExpression.property);
|
|
4402
|
-
if (!isNodeOfType(property, "Identifier")) return null;
|
|
4403
|
-
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
4404
|
-
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
4405
|
-
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
4406
|
-
};
|
|
4407
|
-
const collectScopeSymbols = (scope, symbols) => {
|
|
4408
|
-
symbols.push(...scope.symbols);
|
|
4409
|
-
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
4410
|
-
};
|
|
4411
|
-
const getEquivalentSymbols = (identifier, scopes) => {
|
|
4412
|
-
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4413
|
-
if (!rootSymbol) return [];
|
|
4414
|
-
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
4415
|
-
if (!symbolsByRootId) {
|
|
4416
|
-
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4417
|
-
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4418
|
-
}
|
|
4419
|
-
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4420
|
-
if (cachedSymbols) return cachedSymbols;
|
|
4421
|
-
const allSymbols = [];
|
|
4422
|
-
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4423
|
-
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
4424
|
-
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
4425
|
-
return equivalentSymbols;
|
|
4426
|
-
};
|
|
4427
|
-
const getDirectAliasSourceSymbol = (expression, scopes) => {
|
|
4428
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
4429
|
-
return isNodeOfType(unwrappedExpression, "Identifier") ? scopes.symbolFor(unwrappedExpression) : null;
|
|
4430
|
-
};
|
|
4431
|
-
const getDirectAssignmentSourceSymbol = (identifier, scopes) => {
|
|
4432
|
-
const assignmentTarget = findTransparentExpressionRoot(identifier);
|
|
4433
|
-
const parent = assignmentTarget.parent;
|
|
4434
|
-
if (!parent || !isNodeOfType(parent, "AssignmentExpression") || parent.operator !== "=" || parent.left !== assignmentTarget) return null;
|
|
4435
|
-
return getDirectAliasSourceSymbol(parent.right, scopes);
|
|
4436
|
-
};
|
|
4437
|
-
const isDirectAliasSourceReference = (identifier) => {
|
|
4438
|
-
const aliasSource = findTransparentExpressionRoot(identifier);
|
|
4439
|
-
const parent = aliasSource.parent;
|
|
4440
|
-
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === aliasSource && (isNodeOfType(parent.id, "Identifier") || isNodeOfType(parent.id, "ObjectPattern"))) return true;
|
|
4441
|
-
return Boolean(parent && isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.right === aliasSource && isNodeOfType(stripParenExpression(parent.left), "Identifier"));
|
|
4442
|
-
};
|
|
4443
|
-
const isDirectAliasOfKnownSymbol = (symbol, knownSymbolIds, scopes) => {
|
|
4444
|
-
if (symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier) {
|
|
4445
|
-
const initializerSymbol = getDirectAliasSourceSymbol(symbol.initializer, scopes);
|
|
4446
|
-
if (initializerSymbol && knownSymbolIds.has(initializerSymbol.id)) return true;
|
|
4447
|
-
}
|
|
4448
|
-
return symbol.references.some((reference) => {
|
|
4449
|
-
const assignmentSourceSymbol = getDirectAssignmentSourceSymbol(reference.identifier, scopes);
|
|
4450
|
-
return Boolean(assignmentSourceSymbol && knownSymbolIds.has(assignmentSourceSymbol.id));
|
|
4451
|
-
});
|
|
4452
|
-
};
|
|
4453
|
-
const getPotentiallyAliasedSymbols = (identifier, scopes) => {
|
|
4454
|
-
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4455
|
-
if (!rootSymbol) return [];
|
|
4456
|
-
let symbolsByRootId = potentiallyAliasedSymbolsByAnalysis.get(scopes);
|
|
4457
|
-
if (!symbolsByRootId) {
|
|
4458
|
-
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4459
|
-
potentiallyAliasedSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4460
|
-
}
|
|
4461
|
-
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4462
|
-
if (cachedSymbols) return cachedSymbols;
|
|
4463
|
-
const allSymbols = [];
|
|
4464
|
-
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4465
|
-
const aliasedSymbolIds = new Set([rootSymbol.id]);
|
|
4466
|
-
let didAddAlias = true;
|
|
4467
|
-
while (didAddAlias) {
|
|
4468
|
-
didAddAlias = false;
|
|
4469
|
-
for (const symbol of allSymbols) {
|
|
4470
|
-
if (aliasedSymbolIds.has(symbol.id)) continue;
|
|
4471
|
-
if (!isDirectAliasOfKnownSymbol(symbol, aliasedSymbolIds, scopes)) continue;
|
|
4472
|
-
aliasedSymbolIds.add(symbol.id);
|
|
4473
|
-
didAddAlias = true;
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
|
-
const aliasedSymbols = allSymbols.filter((symbol) => aliasedSymbolIds.has(symbol.id));
|
|
4477
|
-
symbolsByRootId.set(rootSymbol.id, aliasedSymbols);
|
|
4478
|
-
return aliasedSymbols;
|
|
4479
|
-
};
|
|
4480
|
-
const findExecutionBoundary = (node) => {
|
|
4481
|
-
let current = node;
|
|
4482
|
-
while (current) {
|
|
4483
|
-
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
4484
|
-
current = current.parent ?? null;
|
|
4485
|
-
}
|
|
4486
|
-
return null;
|
|
4487
|
-
};
|
|
4488
|
-
const isOnUnconditionalPath = (node, boundary) => {
|
|
4489
|
-
let current = node.parent ?? null;
|
|
4490
|
-
while (current && current !== boundary) {
|
|
4491
|
-
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
4492
|
-
current = current.parent ?? null;
|
|
4493
|
-
}
|
|
4494
|
-
return current === boundary;
|
|
4495
|
-
};
|
|
4496
|
-
const findFunctionBindingIdentifier = (functionNode) => {
|
|
4497
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
4498
|
-
const parent = functionNode.parent;
|
|
4499
|
-
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
4500
|
-
return null;
|
|
4501
|
-
};
|
|
4502
|
-
const findDirectCall = (identifier) => {
|
|
4503
|
-
let callee = identifier;
|
|
4504
|
-
let parent = callee.parent;
|
|
4505
|
-
while (parent && stripParenExpression(parent) === identifier) {
|
|
4506
|
-
callee = parent;
|
|
4507
|
-
parent = callee.parent;
|
|
4508
|
-
}
|
|
4509
|
-
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
4510
|
-
};
|
|
4511
|
-
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
4512
|
-
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
4513
|
-
visitedFunctionNodes.add(functionNode);
|
|
4514
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4515
|
-
if (!referenceBoundary) return false;
|
|
4516
|
-
const invocationCalls = [];
|
|
4517
|
-
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
4518
|
-
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
4519
|
-
const call = findDirectCall(reference.identifier);
|
|
4520
|
-
if (call) invocationCalls.push(call);
|
|
4521
|
-
}
|
|
4522
|
-
else {
|
|
4523
|
-
const call = findDirectCall(functionNode);
|
|
4524
|
-
if (call) invocationCalls.push(call);
|
|
4525
|
-
}
|
|
4526
|
-
return invocationCalls.some((call) => {
|
|
4527
|
-
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
4528
|
-
const callBoundary = findExecutionBoundary(call);
|
|
4529
|
-
if (!callBoundary) return false;
|
|
4530
|
-
if (callBoundary === referenceBoundary) return true;
|
|
4531
|
-
if (!isFunctionLike$2(callBoundary)) return false;
|
|
4532
|
-
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
4533
|
-
});
|
|
4534
|
-
};
|
|
4535
|
-
const isMemberWriteTarget = (memberExpression) => {
|
|
4536
|
-
const parent = memberExpression.parent;
|
|
4537
|
-
if (!parent) return false;
|
|
4538
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
4539
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
4540
|
-
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
4541
|
-
};
|
|
4542
|
-
const getMemberWriteTarget = (identifier) => {
|
|
4543
|
-
let parent = identifier.parent;
|
|
4544
|
-
while (parent && stripParenExpression(parent) === identifier) parent = parent.parent;
|
|
4545
|
-
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== identifier || !isMemberWriteTarget(parent)) return null;
|
|
4546
|
-
return parent;
|
|
4547
|
-
};
|
|
4548
|
-
const getStaticPropertyWriteTarget = (identifier, propertyName, scopes) => {
|
|
4549
|
-
const writeTarget = getMemberWriteTarget(identifier);
|
|
4550
|
-
return writeTarget && getResolvedStaticPropertyName(writeTarget, scopes) === propertyName ? writeTarget : null;
|
|
4551
|
-
};
|
|
4552
|
-
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4553
|
-
const writeTarget = getStaticPropertyWriteTarget(reference.identifier, propertyName, scopes);
|
|
4554
|
-
if (!writeTarget) return false;
|
|
4555
|
-
const writeBoundary = findExecutionBoundary(writeTarget);
|
|
4556
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4557
|
-
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(writeTarget, writeBoundary)) return false;
|
|
4558
|
-
if (writeBoundary === referenceBoundary) return writeTarget.range[0] < referenceNode.range[0];
|
|
4559
|
-
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
4560
|
-
});
|
|
4561
|
-
const isStableStaticPropertyReference = (identifier) => {
|
|
4562
|
-
if (isDirectAliasSourceReference(identifier)) return true;
|
|
4563
|
-
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
4564
|
-
const memberExpression = identifierRoot.parent;
|
|
4565
|
-
return Boolean(memberExpression && isNodeOfType(memberExpression, "MemberExpression") && stripParenExpression(memberExpression.object) === identifierRoot);
|
|
4566
|
-
};
|
|
4567
|
-
const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
|
|
4568
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4569
|
-
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
4570
|
-
const writeTarget = getMemberWriteTarget(reference.identifier);
|
|
4571
|
-
if (!writeTarget) return false;
|
|
4572
|
-
const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
|
|
4573
|
-
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4574
|
-
}));
|
|
4575
|
-
};
|
|
4576
|
-
const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
|
|
4577
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4578
|
-
if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
|
|
4579
|
-
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => !isStableStaticPropertyReference(reference.identifier)));
|
|
4580
|
-
};
|
|
4581
|
-
const hasPossibleStaticMemberCallWrite = (callExpression, scopes) => {
|
|
4582
|
-
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
4583
|
-
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
4584
|
-
const callee = stripParenExpression(unwrappedCallExpression.callee);
|
|
4585
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4586
|
-
const propertyName = getStaticPropertyName(callee);
|
|
4587
|
-
if (propertyName === null) return false;
|
|
4588
|
-
const receiver = stripParenExpression(callee.object);
|
|
4589
|
-
return isNodeOfType(receiver, "Identifier") && hasPossibleStaticPropertyWrite(receiver, propertyName, scopes);
|
|
4590
|
-
};
|
|
4591
|
-
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
4592
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4593
|
-
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
4594
|
-
};
|
|
4595
|
-
//#endregion
|
|
4596
|
-
//#region src/plugin/utils/find-enclosing-function.ts
|
|
4597
|
-
const findEnclosingFunction$1 = (node) => {
|
|
4598
|
-
let cursor = node.parent;
|
|
4599
|
-
while (cursor) {
|
|
4600
|
-
if (isFunctionLike$2(cursor)) return cursor;
|
|
4601
|
-
cursor = cursor.parent ?? null;
|
|
4602
|
-
}
|
|
4603
|
-
return null;
|
|
4604
|
-
};
|
|
4605
|
-
//#endregion
|
|
4606
|
-
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
4607
|
-
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4608
|
-
if (reference.flag === "read") return false;
|
|
4609
|
-
const writeFunction = findEnclosingFunction$1(reference.identifier);
|
|
4610
|
-
if (writeFunction === findEnclosingFunction$1(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
4611
|
-
if (!writeFunction) return true;
|
|
4612
|
-
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
4613
|
-
});
|
|
4614
|
-
//#endregion
|
|
4615
|
-
//#region src/plugin/utils/get-order-independent-local-function.ts
|
|
4616
|
-
const COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS = new Set([
|
|
4617
|
-
"+=",
|
|
4618
|
-
"-=",
|
|
4619
|
-
"*=",
|
|
4620
|
-
"/=",
|
|
4621
|
-
"%=",
|
|
4622
|
-
"&=",
|
|
4623
|
-
"^=",
|
|
4624
|
-
"|="
|
|
4625
|
-
]);
|
|
4626
|
-
const isPureParameterExpression = (expression, parameterNames) => {
|
|
4627
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
4628
|
-
if (isNodeOfType(unwrappedExpression, "Literal")) return true;
|
|
4629
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) return parameterNames.has(unwrappedExpression.name);
|
|
4630
|
-
if (isNodeOfType(unwrappedExpression, "BinaryExpression") || isNodeOfType(unwrappedExpression, "LogicalExpression")) return isPureParameterExpression(unwrappedExpression.left, parameterNames) && isPureParameterExpression(unwrappedExpression.right, parameterNames);
|
|
4631
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) return unwrappedExpression.operator !== "delete" && isPureParameterExpression(unwrappedExpression.argument, parameterNames);
|
|
4632
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isPureParameterExpression(unwrappedExpression.test, parameterNames) && isPureParameterExpression(unwrappedExpression.consequent, parameterNames) && isPureParameterExpression(unwrappedExpression.alternate, parameterNames);
|
|
4633
|
-
if (isNodeOfType(unwrappedExpression, "TemplateLiteral")) return unwrappedExpression.expressions.every((nestedExpression) => isPureParameterExpression(nestedExpression, parameterNames));
|
|
4634
|
-
return false;
|
|
4635
|
-
};
|
|
4636
|
-
const isOrderIndependentPromiseResolveCall = (expression, parameterNames, scopes) => {
|
|
4637
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
4638
|
-
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
4639
|
-
if (!unwrappedExpression.arguments.every((argument) => !isNodeOfType(argument, "SpreadElement") && isPureParameterExpression(argument, parameterNames))) return false;
|
|
4640
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
4641
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4642
|
-
if (getStaticPropertyName(callee) !== "resolve") return false;
|
|
4643
|
-
const receiver = stripParenExpression(callee.object);
|
|
4644
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && scopes.isGlobalReference(receiver);
|
|
4645
|
-
};
|
|
4646
|
-
const isHarmlessPromiseResolveAwait = (statement, parameterNames, scopes) => {
|
|
4647
|
-
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AwaitExpression")) return false;
|
|
4648
|
-
return isOrderIndependentPromiseResolveCall(statement.expression.argument, parameterNames, scopes);
|
|
4649
|
-
};
|
|
4650
|
-
const isCommutativeParameterMutation = (statement, parameterNames) => {
|
|
4651
|
-
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AssignmentExpression") || !COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS.has(statement.expression.operator)) return false;
|
|
4652
|
-
const mutationTarget = stripParenExpression(statement.expression.left);
|
|
4653
|
-
if (!isNodeOfType(mutationTarget, "MemberExpression")) return false;
|
|
4654
|
-
if (getStaticPropertyName(mutationTarget) === null) return false;
|
|
4655
|
-
const receiver = stripParenExpression(mutationTarget.object);
|
|
4656
|
-
return isNodeOfType(receiver, "Identifier") && parameterNames.has(receiver.name) && isNodeOfType(stripParenExpression(statement.expression.right), "Literal");
|
|
4657
|
-
};
|
|
4658
|
-
const isOrderIndependentFunction = (functionNode, scopes) => {
|
|
4659
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
4660
|
-
const parameterNames = /* @__PURE__ */ new Set();
|
|
4661
|
-
for (const parameter of functionNode.params) {
|
|
4662
|
-
if (!isNodeOfType(parameter, "Identifier")) return false;
|
|
4663
|
-
parameterNames.add(parameter.name);
|
|
4664
|
-
}
|
|
4665
|
-
if (!functionNode.async) {
|
|
4666
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isOrderIndependentPromiseResolveCall(functionNode.body, parameterNames, scopes);
|
|
4667
|
-
if (functionNode.body.body.length !== 1) return false;
|
|
4668
|
-
const [returnStatement] = functionNode.body.body;
|
|
4669
|
-
return Boolean(isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument && isOrderIndependentPromiseResolveCall(returnStatement.argument, parameterNames, scopes));
|
|
4670
|
-
}
|
|
4671
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isPureParameterExpression(functionNode.body, parameterNames);
|
|
4672
|
-
const statements = functionNode.body.body;
|
|
4673
|
-
for (let statementIndex = 0; statementIndex < statements.length; statementIndex++) {
|
|
4674
|
-
const statement = statements[statementIndex];
|
|
4675
|
-
const isTerminalStatement = statementIndex === statements.length - 1;
|
|
4676
|
-
if (isHarmlessPromiseResolveAwait(statement, parameterNames, scopes)) continue;
|
|
4677
|
-
if (isNodeOfType(statement, "ExpressionStatement") && isPureParameterExpression(statement.expression, parameterNames)) continue;
|
|
4678
|
-
if (isCommutativeParameterMutation(statement, parameterNames)) return isTerminalStatement;
|
|
4679
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isTerminalStatement) return false;
|
|
4680
|
-
return !statement.argument || isPureParameterExpression(statement.argument, parameterNames) || isOrderIndependentPromiseResolveCall(statement.argument, parameterNames, scopes);
|
|
4681
|
-
}
|
|
4682
|
-
return true;
|
|
4683
|
-
};
|
|
4684
|
-
const getObjectPropertyName = (property) => {
|
|
4685
|
-
if (!isNodeOfType(property, "Property")) return null;
|
|
4686
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4687
|
-
};
|
|
4688
|
-
const resolveOrderIndependentObjectPropertyFunction = (objectExpression, propertyName, callExpression, scopes, visitedSymbolIds) => {
|
|
4689
|
-
const unwrappedObject = stripParenExpression(objectExpression);
|
|
4690
|
-
if (isNodeOfType(unwrappedObject, "Identifier")) {
|
|
4691
|
-
const symbol = scopes.symbolFor(unwrappedObject);
|
|
4692
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes) || hasPossibleStaticPropertyMutationOrEscape(unwrappedObject, propertyName, scopes)) return null;
|
|
4693
|
-
visitedSymbolIds.add(symbol.id);
|
|
4694
|
-
return resolveOrderIndependentObjectPropertyFunction(symbol.initializer, propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4695
|
-
}
|
|
4696
|
-
if (!isNodeOfType(unwrappedObject, "ObjectExpression")) return null;
|
|
4697
|
-
let matchingProperty = null;
|
|
4698
|
-
for (const property of unwrappedObject.properties) {
|
|
4699
|
-
if (!isNodeOfType(property, "Property")) return null;
|
|
4700
|
-
if (property.kind !== "init") return null;
|
|
4701
|
-
const candidatePropertyName = getObjectPropertyName(property);
|
|
4702
|
-
if (candidatePropertyName === null) return null;
|
|
4703
|
-
if (candidatePropertyName === propertyName) matchingProperty = property;
|
|
4704
|
-
}
|
|
4705
|
-
if (!matchingProperty || !isNodeOfType(matchingProperty, "Property")) return null;
|
|
4706
|
-
const propertyValue = stripParenExpression(matchingProperty.value);
|
|
4707
|
-
if (isFunctionLike$2(propertyValue)) return isOrderIndependentFunction(propertyValue, scopes) ? propertyValue : null;
|
|
4708
|
-
return resolveOrderIndependentLocalFunction(propertyValue, callExpression, scopes, visitedSymbolIds);
|
|
4709
|
-
};
|
|
4710
|
-
const resolveOrderIndependentLocalFunction = (callee, callExpression, scopes, visitedSymbolIds) => {
|
|
4711
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
4712
|
-
if (isNodeOfType(unwrappedCallee, "MemberExpression")) {
|
|
4713
|
-
const propertyName = getStaticPropertyName(unwrappedCallee);
|
|
4714
|
-
if (propertyName === null) return null;
|
|
4715
|
-
return resolveOrderIndependentObjectPropertyFunction(stripParenExpression(unwrappedCallee.object), propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4716
|
-
}
|
|
4717
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
4718
|
-
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
4719
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes)) return null;
|
|
4720
|
-
visitedSymbolIds.add(symbol.id);
|
|
4721
|
-
if (!symbol.initializer) return null;
|
|
4722
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
4723
|
-
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
4724
|
-
if (destructuredPropertyName !== null) return resolveOrderIndependentObjectPropertyFunction(initializer, destructuredPropertyName, callExpression, scopes, visitedSymbolIds);
|
|
4725
|
-
if (isFunctionLike$2(initializer)) return isOrderIndependentFunction(initializer, scopes) ? initializer : null;
|
|
4726
|
-
if (symbol.kind !== "const") return null;
|
|
4727
|
-
return resolveOrderIndependentLocalFunction(initializer, callExpression, scopes, visitedSymbolIds);
|
|
4728
|
-
};
|
|
4729
|
-
const getOrderIndependentLocalFunction = (callExpression, scopes) => {
|
|
4730
|
-
const unwrappedCall = stripParenExpression(callExpression);
|
|
4731
|
-
if (!isNodeOfType(unwrappedCall, "CallExpression")) return null;
|
|
4732
|
-
if (hasPossibleStaticMemberCallWrite(unwrappedCall, scopes)) return null;
|
|
4733
|
-
return resolveOrderIndependentLocalFunction(unwrappedCall.callee, unwrappedCall, scopes, /* @__PURE__ */ new Set());
|
|
4734
|
-
};
|
|
4735
|
-
//#endregion
|
|
4736
4317
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
4737
4318
|
/**
|
|
4738
4319
|
* Type-guard for the two "inline function expression" ESTree forms:
|
|
@@ -4766,15 +4347,13 @@ const isIntentionalSequencingCallee = (callee) => {
|
|
|
4766
4347
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
|
|
4767
4348
|
return false;
|
|
4768
4349
|
};
|
|
4769
|
-
const isAwaitingSleepLikeCall = (awaitNode
|
|
4350
|
+
const isAwaitingSleepLikeCall = (awaitNode) => {
|
|
4770
4351
|
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
4771
4352
|
const argument = awaitNode.argument;
|
|
4772
4353
|
if (!argument) return false;
|
|
4773
4354
|
if (!isNodeOfType(argument, "CallExpression")) return false;
|
|
4774
|
-
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
|
|
4775
4355
|
return isIntentionalSequencingCallee(argument.callee);
|
|
4776
4356
|
};
|
|
4777
|
-
const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
|
|
4778
4357
|
const PROMISE_CONCURRENCY_METHODS = new Set([
|
|
4779
4358
|
"all",
|
|
4780
4359
|
"allSettled",
|
|
@@ -4817,7 +4396,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
4817
4396
|
});
|
|
4818
4397
|
return isWaitLike;
|
|
4819
4398
|
};
|
|
4820
|
-
const isIntentionallySequentialAwait = (awaitNode
|
|
4399
|
+
const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4821
4400
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
4822
4401
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
4823
4402
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -5008,12 +4587,12 @@ const getLoopLabelName = (loopNode) => {
|
|
|
5008
4587
|
if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
|
|
5009
4588
|
return null;
|
|
5010
4589
|
};
|
|
5011
|
-
const loopBodyHasIntentionallySequentialAwait = (block
|
|
4590
|
+
const loopBodyHasIntentionallySequentialAwait = (block) => {
|
|
5012
4591
|
let foundIntentional = false;
|
|
5013
4592
|
walkAst(block, (child) => {
|
|
5014
4593
|
if (foundIntentional) return false;
|
|
5015
4594
|
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
5016
|
-
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child
|
|
4595
|
+
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
|
|
5017
4596
|
foundIntentional = true;
|
|
5018
4597
|
return false;
|
|
5019
4598
|
}
|
|
@@ -5118,7 +4697,7 @@ const asyncAwaitInLoop = defineRule({
|
|
|
5118
4697
|
const inspectLoop = (loopNode, label) => {
|
|
5119
4698
|
const loopBody = loopNode.body;
|
|
5120
4699
|
if (!loopBody) return;
|
|
5121
|
-
if (loopBodyHasIntentionallySequentialAwait(loopBody
|
|
4700
|
+
if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
|
|
5122
4701
|
if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
|
|
5123
4702
|
if (hasLoopCarriedDependency(loopBody)) return;
|
|
5124
4703
|
if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
|
|
@@ -5378,7 +4957,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
|
|
|
5378
4957
|
});
|
|
5379
4958
|
return performsSideEffects;
|
|
5380
4959
|
};
|
|
5381
|
-
const findEnclosingFunction = (node) => {
|
|
4960
|
+
const findEnclosingFunction$1 = (node) => {
|
|
5382
4961
|
let ancestor = node.parent;
|
|
5383
4962
|
while (ancestor) {
|
|
5384
4963
|
if (isFunctionLike$2(ancestor)) return ancestor;
|
|
@@ -5391,7 +4970,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
|
|
|
5391
4970
|
const testIdentifierNames = /* @__PURE__ */ new Set();
|
|
5392
4971
|
collectReferenceIdentifierNames(test, testIdentifierNames);
|
|
5393
4972
|
if (testIdentifierNames.size === 0) return false;
|
|
5394
|
-
const enclosingFunction = findEnclosingFunction(guardStatement);
|
|
4973
|
+
const enclosingFunction = findEnclosingFunction$1(guardStatement);
|
|
5395
4974
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
|
|
5396
4975
|
let readsReassignedLocal = false;
|
|
5397
4976
|
walkAst(enclosingFunction.body, (child) => {
|
|
@@ -5562,36 +5141,19 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
|
|
|
5562
5141
|
return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
|
|
5563
5142
|
};
|
|
5564
5143
|
const isBareExpressionAwait = (statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression");
|
|
5565
|
-
const hasOrderIndependentBareAwaitArguments = (callExpression) => {
|
|
5566
|
-
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
5567
|
-
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
5568
|
-
return unwrappedCallExpression.arguments.every((argument) => {
|
|
5569
|
-
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
5570
|
-
const unwrappedArgument = stripParenExpression(argument);
|
|
5571
|
-
return isNodeOfType(unwrappedArgument, "Identifier") || isNodeOfType(unwrappedArgument, "Literal");
|
|
5572
|
-
});
|
|
5573
|
-
};
|
|
5574
5144
|
const isNonCallAwait = (statement) => {
|
|
5575
5145
|
const awaitedExpression = getAwaitedCall(statement);
|
|
5576
5146
|
if (!awaitedExpression) return false;
|
|
5577
5147
|
const stripped = stripParenExpression(awaitedExpression);
|
|
5578
5148
|
return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
|
|
5579
5149
|
};
|
|
5580
|
-
const sequenceContainsSerializationSignal = (statements
|
|
5581
|
-
let bareAwaitFunction = null;
|
|
5150
|
+
const sequenceContainsSerializationSignal = (statements) => {
|
|
5582
5151
|
for (const statement of statements) {
|
|
5152
|
+
if (isBareExpressionAwait(statement)) return true;
|
|
5583
5153
|
if (isNonCallAwait(statement)) return true;
|
|
5584
5154
|
const awaitedCall = getAwaitedCall(statement);
|
|
5585
|
-
if (awaitedCall && hasPossibleStaticMemberCallWrite(awaitedCall, context.scopes)) return true;
|
|
5586
|
-
const orderIndependentFunction = awaitedCall ? getOrderIndependentLocalFunction(awaitedCall, context.scopes) : null;
|
|
5587
|
-
if (isBareExpressionAwait(statement)) {
|
|
5588
|
-
if (orderIndependentFunction === null) return true;
|
|
5589
|
-
if (!awaitedCall || !hasOrderIndependentBareAwaitArguments(awaitedCall)) return true;
|
|
5590
|
-
if (bareAwaitFunction !== null && bareAwaitFunction !== orderIndependentFunction) return true;
|
|
5591
|
-
bareAwaitFunction = orderIndependentFunction;
|
|
5592
|
-
}
|
|
5593
5155
|
if (isOrderedUiFlowAwait(awaitedCall)) return true;
|
|
5594
|
-
if (isIntentionalSequencingAwait(awaitedCall)
|
|
5156
|
+
if (isIntentionalSequencingAwait(awaitedCall)) return true;
|
|
5595
5157
|
}
|
|
5596
5158
|
return false;
|
|
5597
5159
|
};
|
|
@@ -5649,7 +5211,7 @@ const asyncParallel = defineRule({
|
|
|
5649
5211
|
const consecutiveAwaitStatements = [];
|
|
5650
5212
|
const flushConsecutiveAwaits = () => {
|
|
5651
5213
|
if (consecutiveAwaitStatements.length >= 3) {
|
|
5652
|
-
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements
|
|
5214
|
+
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5653
5215
|
}
|
|
5654
5216
|
consecutiveAwaitStatements.length = 0;
|
|
5655
5217
|
};
|
|
@@ -6427,6 +5989,23 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
6427
5989
|
}
|
|
6428
5990
|
});
|
|
6429
5991
|
//#endregion
|
|
5992
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5993
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5994
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
5995
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
5996
|
+
if (node.computed) {
|
|
5997
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
5998
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
5999
|
+
return null;
|
|
6000
|
+
}
|
|
6001
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
6002
|
+
if (isNodeOfType(key, "Literal")) {
|
|
6003
|
+
if (typeof key.value === "string") return key.value;
|
|
6004
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
6005
|
+
}
|
|
6006
|
+
return null;
|
|
6007
|
+
};
|
|
6008
|
+
//#endregion
|
|
6430
6009
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
6431
6010
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
6432
6011
|
if (!a || !b) return a === b;
|
|
@@ -7110,6 +6689,15 @@ const getDirectConstInitializer = (symbol) => {
|
|
|
7110
6689
|
return symbol.initializer;
|
|
7111
6690
|
};
|
|
7112
6691
|
//#endregion
|
|
6692
|
+
//#region src/plugin/utils/get-static-property-name.ts
|
|
6693
|
+
const getStaticPropertyName = (memberExpression) => {
|
|
6694
|
+
const property = memberExpression.property;
|
|
6695
|
+
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
6696
|
+
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
6697
|
+
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
6698
|
+
return null;
|
|
6699
|
+
};
|
|
6700
|
+
//#endregion
|
|
7113
6701
|
//#region src/plugin/utils/get-range-start.ts
|
|
7114
6702
|
const getRangeStart = (node) => {
|
|
7115
6703
|
const rangeStart = node.range?.[0];
|
|
@@ -7505,6 +7093,16 @@ const collectFunctionReturnStatements = (functionNode) => {
|
|
|
7505
7093
|
return returnStatements;
|
|
7506
7094
|
};
|
|
7507
7095
|
//#endregion
|
|
7096
|
+
//#region src/plugin/utils/find-enclosing-function.ts
|
|
7097
|
+
const findEnclosingFunction = (node) => {
|
|
7098
|
+
let cursor = node.parent;
|
|
7099
|
+
while (cursor) {
|
|
7100
|
+
if (isFunctionLike$2(cursor)) return cursor;
|
|
7101
|
+
cursor = cursor.parent ?? null;
|
|
7102
|
+
}
|
|
7103
|
+
return null;
|
|
7104
|
+
};
|
|
7105
|
+
//#endregion
|
|
7508
7106
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
7509
7107
|
const statementAlwaysExits = (statement) => {
|
|
7510
7108
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -7550,8 +7148,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
|
|
|
7550
7148
|
const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
|
|
7551
7149
|
if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
|
|
7552
7150
|
if (!controlFlow) return [];
|
|
7553
|
-
const referenceFunction = findEnclosingFunction
|
|
7554
|
-
if (findEnclosingFunction
|
|
7151
|
+
const referenceFunction = findEnclosingFunction(referenceNode);
|
|
7152
|
+
if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7555
7153
|
if (!referenceFunction) return [];
|
|
7556
7154
|
const functionControlFlow = controlFlow.cfgFor(referenceFunction);
|
|
7557
7155
|
if (!functionControlFlow) return [];
|
|
@@ -7575,7 +7173,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
|
|
|
7575
7173
|
if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
|
|
7576
7174
|
for (const reference of symbol.references) {
|
|
7577
7175
|
const writePosition = getRangeStart(reference.identifier);
|
|
7578
|
-
if (reference.flag === "read" || findEnclosingFunction
|
|
7176
|
+
if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7579
7177
|
const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
|
|
7580
7178
|
if (!assignedExpression) continue;
|
|
7581
7179
|
addDefinition(assignedExpression, reference.identifier, writePosition);
|
|
@@ -9247,7 +8845,6 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
9247
8845
|
const LABEL_ELEMENT = "label";
|
|
9248
8846
|
const LABEL_COMPONENT_NAME = "Label";
|
|
9249
8847
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
9250
|
-
const TITLE_ATTRIBUTE = "title";
|
|
9251
8848
|
const WRAPPER_LABEL_PROP = "label";
|
|
9252
8849
|
const SELECT_ELEMENT = "select";
|
|
9253
8850
|
const DEFAULT_DEPTH = 5;
|
|
@@ -9293,96 +8890,6 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
9293
8890
|
}
|
|
9294
8891
|
return true;
|
|
9295
8892
|
};
|
|
9296
|
-
const getLastJsxPropIgnoreCase = (attributes, targetProp) => {
|
|
9297
|
-
const targetPropLower = targetProp.toLowerCase();
|
|
9298
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
9299
|
-
const attribute = attributes[attributeIndex];
|
|
9300
|
-
if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
9301
|
-
if (getJsxAttributeName(attribute.name)?.toLowerCase() === targetPropLower) return attribute;
|
|
9302
|
-
}
|
|
9303
|
-
};
|
|
9304
|
-
const getStaticNativeTitleArrayValue = (expression, scopes) => {
|
|
9305
|
-
const elementValues = [];
|
|
9306
|
-
for (const rawElement of expression.elements) {
|
|
9307
|
-
if (rawElement === null) {
|
|
9308
|
-
elementValues.push("");
|
|
9309
|
-
continue;
|
|
9310
|
-
}
|
|
9311
|
-
if (isNodeOfType(rawElement, "SpreadElement")) return null;
|
|
9312
|
-
const element = stripParenExpression(rawElement);
|
|
9313
|
-
if (isNodeOfType(element, "Literal")) {
|
|
9314
|
-
elementValues.push(element.value === null ? "" : String(element.value));
|
|
9315
|
-
continue;
|
|
9316
|
-
}
|
|
9317
|
-
if (isNodeOfType(element, "TemplateLiteral")) {
|
|
9318
|
-
const staticValue = getStaticTemplateLiteralValue(element);
|
|
9319
|
-
if (staticValue === null) return null;
|
|
9320
|
-
elementValues.push(staticValue);
|
|
9321
|
-
continue;
|
|
9322
|
-
}
|
|
9323
|
-
if (isNodeOfType(element, "Identifier") && element.name === "undefined" && scopes.isGlobalReference(element) || isNodeOfType(element, "UnaryExpression") && element.operator === "void") {
|
|
9324
|
-
elementValues.push("");
|
|
9325
|
-
continue;
|
|
9326
|
-
}
|
|
9327
|
-
if (isNodeOfType(element, "ArrayExpression")) {
|
|
9328
|
-
const nestedValue = getStaticNativeTitleArrayValue(element, scopes);
|
|
9329
|
-
if (nestedValue === null) return null;
|
|
9330
|
-
elementValues.push(nestedValue);
|
|
9331
|
-
continue;
|
|
9332
|
-
}
|
|
9333
|
-
return null;
|
|
9334
|
-
}
|
|
9335
|
-
return elementValues.join(",");
|
|
9336
|
-
};
|
|
9337
|
-
const isGlobalSymbolExpression = (expression, scopes) => {
|
|
9338
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name === "Symbol" && scopes.isGlobalReference(expression);
|
|
9339
|
-
if (!isNodeOfType(expression, "MemberExpression")) return false;
|
|
9340
|
-
const object = stripParenExpression(expression.object);
|
|
9341
|
-
return isNodeOfType(object, "Identifier") && object.name === "Symbol" && scopes.isGlobalReference(object);
|
|
9342
|
-
};
|
|
9343
|
-
const hasNonEmptyNativeTitleExpression = (rawExpression, scopes) => {
|
|
9344
|
-
const expression = stripParenExpression(rawExpression);
|
|
9345
|
-
if (isNodeOfType(expression, "Literal")) {
|
|
9346
|
-
if (typeof expression.value === "string") return expression.value.trim().length > 0;
|
|
9347
|
-
return expression.value !== null && typeof expression.value !== "boolean";
|
|
9348
|
-
}
|
|
9349
|
-
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
9350
|
-
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
9351
|
-
return staticValue === null || staticValue.trim().length > 0;
|
|
9352
|
-
}
|
|
9353
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name !== "undefined" || !scopes.isGlobalReference(expression);
|
|
9354
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return false;
|
|
9355
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return false;
|
|
9356
|
-
if (isNodeOfType(expression, "ArrowFunctionExpression")) return false;
|
|
9357
|
-
if (isNodeOfType(expression, "FunctionExpression")) return false;
|
|
9358
|
-
if (isNodeOfType(expression, "ClassExpression")) return false;
|
|
9359
|
-
if (isNodeOfType(expression, "ArrayExpression")) {
|
|
9360
|
-
const staticValue = getStaticNativeTitleArrayValue(expression, scopes);
|
|
9361
|
-
return staticValue === null || staticValue.trim().length > 0;
|
|
9362
|
-
}
|
|
9363
|
-
if (isGlobalSymbolExpression(expression, scopes)) return false;
|
|
9364
|
-
if (isNodeOfType(expression, "CallExpression") && isGlobalSymbolExpression(expression.callee, scopes)) return false;
|
|
9365
|
-
if (isNodeOfType(expression, "ConditionalExpression")) return hasNonEmptyNativeTitleExpression(expression.consequent, scopes) && hasNonEmptyNativeTitleExpression(expression.alternate, scopes);
|
|
9366
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
9367
|
-
const finalExpression = expression.expressions.at(-1);
|
|
9368
|
-
return finalExpression ? hasNonEmptyNativeTitleExpression(finalExpression, scopes) : false;
|
|
9369
|
-
}
|
|
9370
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
9371
|
-
const leftExpression = stripParenExpression(expression.left);
|
|
9372
|
-
if (!isNodeOfType(leftExpression, "Literal")) return expression.operator !== "&&";
|
|
9373
|
-
if (expression.operator === "??") return hasNonEmptyNativeTitleExpression(leftExpression.value === null ? expression.right : leftExpression, scopes);
|
|
9374
|
-
const leftValueIsTruthy = Boolean(leftExpression.value);
|
|
9375
|
-
if (expression.operator === "&&") return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? expression.right : leftExpression, scopes);
|
|
9376
|
-
return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? leftExpression : expression.right, scopes);
|
|
9377
|
-
}
|
|
9378
|
-
return true;
|
|
9379
|
-
};
|
|
9380
|
-
const hasNonEmptyNativeTitle = (attribute, scopes) => {
|
|
9381
|
-
if (!attribute?.value) return false;
|
|
9382
|
-
if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
|
|
9383
|
-
if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return hasNonEmptyNativeTitleExpression(attribute.value.expression, scopes);
|
|
9384
|
-
return true;
|
|
9385
|
-
};
|
|
9386
8893
|
const toAttributeMatchKey = (kind, value) => {
|
|
9387
8894
|
const trimmedValue = value.trim();
|
|
9388
8895
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9639,7 +9146,6 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9639
9146
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9640
9147
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9641
9148
|
}
|
|
9642
|
-
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9643
9149
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9644
9150
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9645
9151
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -11589,7 +11095,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11589
11095
|
//#endregion
|
|
11590
11096
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11591
11097
|
const enclosingComponentOrHookName = (node) => {
|
|
11592
|
-
const functionNode = findEnclosingFunction
|
|
11098
|
+
const functionNode = findEnclosingFunction(node);
|
|
11593
11099
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11594
11100
|
};
|
|
11595
11101
|
//#endregion
|
|
@@ -11646,11 +11152,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11646
11152
|
//#endregion
|
|
11647
11153
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11648
11154
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11649
|
-
let functionNode = findEnclosingFunction
|
|
11155
|
+
let functionNode = findEnclosingFunction(node);
|
|
11650
11156
|
while (functionNode) {
|
|
11651
11157
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11652
11158
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11653
|
-
functionNode = findEnclosingFunction
|
|
11159
|
+
functionNode = findEnclosingFunction(functionNode);
|
|
11654
11160
|
}
|
|
11655
11161
|
return null;
|
|
11656
11162
|
};
|
|
@@ -11899,13 +11405,13 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11899
11405
|
};
|
|
11900
11406
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11901
11407
|
let pathAnchor = usageNode;
|
|
11902
|
-
let pathOwner = findEnclosingFunction
|
|
11408
|
+
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11903
11409
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11904
11410
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11905
11411
|
const iteratorCall = pathOwner.parent;
|
|
11906
11412
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11907
11413
|
pathAnchor = iteratorCall;
|
|
11908
|
-
pathOwner = findEnclosingFunction
|
|
11414
|
+
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11909
11415
|
}
|
|
11910
11416
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11911
11417
|
if (!owner) return false;
|
|
@@ -12134,7 +11640,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
12134
11640
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
12135
11641
|
};
|
|
12136
11642
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
12137
|
-
const callbackNode = findEnclosingFunction
|
|
11643
|
+
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12138
11644
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
12139
11645
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
12140
11646
|
if (!mappingCall) return null;
|
|
@@ -12245,10 +11751,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
12245
11751
|
});
|
|
12246
11752
|
if (invocationCalls.length !== 1) return null;
|
|
12247
11753
|
const invocationCall = invocationCalls[0];
|
|
12248
|
-
return findEnclosingFunction
|
|
11754
|
+
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12249
11755
|
};
|
|
12250
11756
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
12251
|
-
const usageFunction = findEnclosingFunction
|
|
11757
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
12252
11758
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
12253
11759
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
12254
11760
|
};
|
|
@@ -12263,7 +11769,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
12263
11769
|
const assignmentReference = assignmentReferences[0];
|
|
12264
11770
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
12265
11771
|
const assignmentNode = assignmentTarget.parent;
|
|
12266
|
-
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction
|
|
11772
|
+
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
12267
11773
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
12268
11774
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
12269
11775
|
};
|
|
@@ -12352,12 +11858,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
12352
11858
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
12353
11859
|
};
|
|
12354
11860
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
12355
|
-
const componentFunction = findEnclosingFunction
|
|
11861
|
+
const componentFunction = findEnclosingFunction(callback);
|
|
12356
11862
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
12357
11863
|
let didFindUnmountCleanup = false;
|
|
12358
11864
|
walkAst(componentFunction.body, (child) => {
|
|
12359
11865
|
if (didFindUnmountCleanup) return false;
|
|
12360
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction
|
|
11866
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12361
11867
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
12362
11868
|
const dependencyList = child.arguments?.[1];
|
|
12363
11869
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -12453,7 +11959,7 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
12453
11959
|
return didWriteGuard;
|
|
12454
11960
|
};
|
|
12455
11961
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
12456
|
-
const usageFunction = findEnclosingFunction
|
|
11962
|
+
const usageFunction = findEnclosingFunction(usage.node);
|
|
12457
11963
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
12458
11964
|
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12459
11965
|
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
@@ -12461,7 +11967,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
12461
11967
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
12462
11968
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
12463
11969
|
if (callback.async) return false;
|
|
12464
|
-
if (usage.kind === "subscribe" && findEnclosingFunction
|
|
11970
|
+
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
12465
11971
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
12466
11972
|
const matchingCleanupReturns = [];
|
|
12467
11973
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -12630,7 +12136,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12630
12136
|
parentNode = currentNode.parent;
|
|
12631
12137
|
}
|
|
12632
12138
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12633
|
-
const effectCallback = findEnclosingFunction
|
|
12139
|
+
const effectCallback = findEnclosingFunction(parentNode);
|
|
12634
12140
|
const effectCall = effectCallback?.parent;
|
|
12635
12141
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12636
12142
|
};
|
|
@@ -12640,13 +12146,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12640
12146
|
if (!bindingIdentifier) return false;
|
|
12641
12147
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12642
12148
|
if (!symbol) return false;
|
|
12643
|
-
return symbol.references.some((reference) => findEnclosingFunction
|
|
12149
|
+
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12644
12150
|
};
|
|
12645
12151
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12646
12152
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12647
|
-
const releaseFunction = findEnclosingFunction
|
|
12153
|
+
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12648
12154
|
if (!releaseFunction) return true;
|
|
12649
|
-
if (releaseFunction === findEnclosingFunction
|
|
12155
|
+
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12650
12156
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12651
12157
|
};
|
|
12652
12158
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12829,10 +12335,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12829
12335
|
}
|
|
12830
12336
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12831
12337
|
const assignedValue = stripParenExpression(child.right);
|
|
12832
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction
|
|
12338
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12833
12339
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12834
12340
|
const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
|
|
12835
|
-
if (findEnclosingFunction
|
|
12341
|
+
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12836
12342
|
hasUnsafeRefWrite = true;
|
|
12837
12343
|
return false;
|
|
12838
12344
|
}
|
|
@@ -12853,12 +12359,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12853
12359
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12854
12360
|
};
|
|
12855
12361
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12856
|
-
const componentFunction = findEnclosingFunction
|
|
12362
|
+
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12857
12363
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12858
12364
|
let didFindCleanupEffect = false;
|
|
12859
12365
|
walkAst(componentFunction.body, (child) => {
|
|
12860
12366
|
if (didFindCleanupEffect) return false;
|
|
12861
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction
|
|
12367
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12862
12368
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12863
12369
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12864
12370
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13712,202 +13218,6 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13712
13218
|
};
|
|
13713
13219
|
};
|
|
13714
13220
|
//#endregion
|
|
13715
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13716
|
-
/**
|
|
13717
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13718
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13719
|
-
* match (`true`) or the chain's root (`false`).
|
|
13720
|
-
*
|
|
13721
|
-
* Was duplicated byte-identical in:
|
|
13722
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
13723
|
-
* - semantic/closure-captures.ts
|
|
13724
|
-
*/
|
|
13725
|
-
const isAstDescendant = (inner, outer) => {
|
|
13726
|
-
let current = inner;
|
|
13727
|
-
while (current) {
|
|
13728
|
-
if (current === outer) return true;
|
|
13729
|
-
current = current.parent ?? null;
|
|
13730
|
-
}
|
|
13731
|
-
return false;
|
|
13732
|
-
};
|
|
13733
|
-
//#endregion
|
|
13734
|
-
//#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
|
|
13735
|
-
const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
|
|
13736
|
-
const getUseStateSetterSymbol = (stateSymbol, scopes) => {
|
|
13737
|
-
const declaration = stateSymbol.declarationNode;
|
|
13738
|
-
if (!isNodeOfType(declaration, "VariableDeclarator")) return null;
|
|
13739
|
-
if (!isNodeOfType(declaration.id, "ArrayPattern")) return null;
|
|
13740
|
-
const stateBinding = declaration.id.elements[0];
|
|
13741
|
-
const setterBinding = declaration.id.elements[1];
|
|
13742
|
-
if (stateBinding !== stateSymbol.bindingIdentifier || !isNodeOfType(setterBinding, "Identifier")) return null;
|
|
13743
|
-
const initializer = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
13744
|
-
if (!initializer || !isReactApiCall(initializer, "useState", scopes, {
|
|
13745
|
-
allowGlobalReactNamespace: true,
|
|
13746
|
-
allowUnboundBareCalls: true,
|
|
13747
|
-
resolveNamedAliases: true
|
|
13748
|
-
})) return null;
|
|
13749
|
-
return scopes.symbolFor(setterBinding);
|
|
13750
|
-
};
|
|
13751
|
-
const getDirectSetterCall = (setterSymbol, callback) => {
|
|
13752
|
-
if (setterSymbol.references.length !== 1) return null;
|
|
13753
|
-
const setterReference = setterSymbol.references[0];
|
|
13754
|
-
if (setterReference.flag !== "read") return null;
|
|
13755
|
-
const referenceRoot = findTransparentExpressionRoot(setterReference.identifier);
|
|
13756
|
-
const callExpression = referenceRoot.parent;
|
|
13757
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot || callExpression.arguments.length !== 1 || findEnclosingFunction$1(callExpression) !== callback) return null;
|
|
13758
|
-
const writtenValue = stripParenExpression(callExpression.arguments[0]);
|
|
13759
|
-
if (isNodeOfType(writtenValue, "ArrowFunctionExpression") || isNodeOfType(writtenValue, "FunctionExpression") || isNodeOfType(writtenValue, "SpreadElement")) return null;
|
|
13760
|
-
return {
|
|
13761
|
-
callExpression,
|
|
13762
|
-
writtenValue
|
|
13763
|
-
};
|
|
13764
|
-
};
|
|
13765
|
-
const isGlobalObjectIsCall = (callExpression, scopes) => {
|
|
13766
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
13767
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
13768
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
13769
|
-
const propertyName = getStaticPropertyName(callee);
|
|
13770
|
-
const receiver = stripParenExpression(callee.object);
|
|
13771
|
-
return Boolean(propertyName === "is" && isNodeOfType(receiver, "Identifier") && receiver.name === "Object" && scopes.isGlobalReference(receiver));
|
|
13772
|
-
};
|
|
13773
|
-
const isGlobalNaNReference = (expression, scopes) => {
|
|
13774
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
13775
|
-
return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression);
|
|
13776
|
-
};
|
|
13777
|
-
const getEqualityComparison = (stateReference, candidate, scopes) => {
|
|
13778
|
-
const unwrappedStateReference = stripParenExpression(stateReference);
|
|
13779
|
-
if (isNodeOfType(candidate, "BinaryExpression") && EQUALITY_BINARY_OPERATORS.has(candidate.operator)) {
|
|
13780
|
-
if (stripParenExpression(candidate.left) === unwrappedStateReference) return isGlobalNaNReference(candidate.right, scopes) ? null : {
|
|
13781
|
-
comparison: candidate,
|
|
13782
|
-
counterpart: candidate.right,
|
|
13783
|
-
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13784
|
-
};
|
|
13785
|
-
if (stripParenExpression(candidate.right) === unwrappedStateReference) return isGlobalNaNReference(candidate.left, scopes) ? null : {
|
|
13786
|
-
comparison: candidate,
|
|
13787
|
-
counterpart: candidate.left,
|
|
13788
|
-
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13789
|
-
};
|
|
13790
|
-
return null;
|
|
13791
|
-
}
|
|
13792
|
-
if (!isNodeOfType(candidate, "CallExpression") || !isGlobalObjectIsCall(candidate, scopes) || candidate.arguments.length !== 2) return null;
|
|
13793
|
-
const firstArgument = candidate.arguments[0];
|
|
13794
|
-
const secondArgument = candidate.arguments[1];
|
|
13795
|
-
if (isNodeOfType(firstArgument, "SpreadElement") || isNodeOfType(secondArgument, "SpreadElement")) return null;
|
|
13796
|
-
if (stripParenExpression(firstArgument) === unwrappedStateReference) return {
|
|
13797
|
-
comparison: candidate,
|
|
13798
|
-
counterpart: secondArgument,
|
|
13799
|
-
areValuesEqualWhenTruthy: true
|
|
13800
|
-
};
|
|
13801
|
-
if (stripParenExpression(secondArgument) === unwrappedStateReference) return {
|
|
13802
|
-
comparison: candidate,
|
|
13803
|
-
counterpart: firstArgument,
|
|
13804
|
-
areValuesEqualWhenTruthy: true
|
|
13805
|
-
};
|
|
13806
|
-
return null;
|
|
13807
|
-
};
|
|
13808
|
-
const findEqualityComparison = (stateReference, test, scopes) => {
|
|
13809
|
-
let current = stateReference.parent;
|
|
13810
|
-
while (current && isAstDescendant(current, test)) {
|
|
13811
|
-
const comparison = getEqualityComparison(stateReference, current, scopes);
|
|
13812
|
-
if (comparison) return comparison;
|
|
13813
|
-
if (current === test) break;
|
|
13814
|
-
current = current.parent;
|
|
13815
|
-
}
|
|
13816
|
-
return null;
|
|
13817
|
-
};
|
|
13818
|
-
const doesTestOutcomeRequireComparisonOutcome = (comparison, test, testOutcome, comparisonOutcome) => {
|
|
13819
|
-
let requiredChildOutcome = testOutcome;
|
|
13820
|
-
let current = comparison;
|
|
13821
|
-
while (current !== test) {
|
|
13822
|
-
const parent = current.parent;
|
|
13823
|
-
if (!parent || !isAstDescendant(parent, test)) return false;
|
|
13824
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") requiredChildOutcome = !requiredChildOutcome;
|
|
13825
|
-
else if (isNodeOfType(parent, "LogicalExpression")) {
|
|
13826
|
-
if (parent.operator === "&&" && !requiredChildOutcome) return false;
|
|
13827
|
-
if (parent.operator === "||" && requiredChildOutcome) return false;
|
|
13828
|
-
if (parent.operator !== "&&" && parent.operator !== "||") return false;
|
|
13829
|
-
} else if (!TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type)) return false;
|
|
13830
|
-
current = parent;
|
|
13831
|
-
}
|
|
13832
|
-
return requiredChildOutcome === comparisonOutcome;
|
|
13833
|
-
};
|
|
13834
|
-
const resolveImmutableAliasExpression = (expression, scopes) => {
|
|
13835
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
13836
|
-
let current = stripParenExpression(expression);
|
|
13837
|
-
while (isNodeOfType(current, "Identifier")) {
|
|
13838
|
-
const symbol = scopes.symbolFor(current);
|
|
13839
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) break;
|
|
13840
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
13841
|
-
if (!initializer || !isNodeOfType(stripParenExpression(initializer), "Identifier")) break;
|
|
13842
|
-
visitedSymbolIds.add(symbol.id);
|
|
13843
|
-
current = stripParenExpression(initializer);
|
|
13844
|
-
}
|
|
13845
|
-
return current;
|
|
13846
|
-
};
|
|
13847
|
-
const referencesSameValue = (leftExpression, rightExpression, scopes) => {
|
|
13848
|
-
const left = resolveImmutableAliasExpression(leftExpression, scopes);
|
|
13849
|
-
const right = resolveImmutableAliasExpression(rightExpression, scopes);
|
|
13850
|
-
if (left === right) return true;
|
|
13851
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
13852
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
13853
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
13854
|
-
if (leftSymbol || rightSymbol) return leftSymbol?.id === rightSymbol?.id;
|
|
13855
|
-
return left.name === right.name;
|
|
13856
|
-
}
|
|
13857
|
-
if (isNodeOfType(left, "Literal") && isNodeOfType(right, "Literal")) return Object.is(left.value, right.value);
|
|
13858
|
-
return false;
|
|
13859
|
-
};
|
|
13860
|
-
const hasDeclaredTriggerDependency = (callback) => {
|
|
13861
|
-
const hookCall = findTransparentExpressionRoot(callback).parent;
|
|
13862
|
-
if (!isNodeOfType(hookCall, "CallExpression")) return false;
|
|
13863
|
-
const dependencyArray = hookCall.arguments[1];
|
|
13864
|
-
return Boolean(isNodeOfType(dependencyArray, "ArrayExpression") && dependencyArray.elements.length);
|
|
13865
|
-
};
|
|
13866
|
-
const doesBranchExitEffect = (branch) => {
|
|
13867
|
-
if (isNodeOfType(branch, "ReturnStatement") || isNodeOfType(branch, "ThrowStatement")) return true;
|
|
13868
|
-
if (!isNodeOfType(branch, "BlockStatement")) return false;
|
|
13869
|
-
const terminalStatement = branch.body.at(-1);
|
|
13870
|
-
return Boolean(terminalStatement && doesBranchExitEffect(terminalStatement));
|
|
13871
|
-
};
|
|
13872
|
-
const doesGuardDominateLaterSetter = (guard, setterCall) => {
|
|
13873
|
-
if (!isNodeOfType(guard, "IfStatement") || guard.alternate) return false;
|
|
13874
|
-
if (!doesBranchExitEffect(guard.consequent)) return false;
|
|
13875
|
-
const block = guard.parent;
|
|
13876
|
-
if (!isNodeOfType(block, "BlockStatement")) return false;
|
|
13877
|
-
const guardIndex = block.body.findIndex((statement) => statement === guard);
|
|
13878
|
-
return block.body.some((statement, statementIndex) => statementIndex > guardIndex && isAstDescendant(setterCall, statement));
|
|
13879
|
-
};
|
|
13880
|
-
const findDominatingGuardCounterpart = (stateReference, setterCall, callback, scopes) => {
|
|
13881
|
-
let current = stateReference.parent;
|
|
13882
|
-
while (current && current !== callback) {
|
|
13883
|
-
if (isNodeOfType(current, "IfStatement") && isAstDescendant(stateReference, current.test) && (isAstDescendant(setterCall, current.consequent) || Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) {
|
|
13884
|
-
const setterRunsWhenTestTruthy = isAstDescendant(setterCall, current.consequent);
|
|
13885
|
-
if (setterRunsWhenTestTruthy === (Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) return null;
|
|
13886
|
-
const equalityComparison = findEqualityComparison(stateReference, current.test, scopes);
|
|
13887
|
-
if (!equalityComparison) return null;
|
|
13888
|
-
const comparisonOutcomeForDifferentValues = !equalityComparison.areValuesEqualWhenTruthy;
|
|
13889
|
-
if (!doesTestOutcomeRequireComparisonOutcome(equalityComparison.comparison, current.test, setterRunsWhenTestTruthy, comparisonOutcomeForDifferentValues)) return null;
|
|
13890
|
-
return equalityComparison.counterpart;
|
|
13891
|
-
}
|
|
13892
|
-
current = current.parent;
|
|
13893
|
-
}
|
|
13894
|
-
return null;
|
|
13895
|
-
};
|
|
13896
|
-
const isSoleWriterEffectGuardCapture = (stateSymbol, callback, scopes) => {
|
|
13897
|
-
if (!hasDeclaredTriggerDependency(callback)) return false;
|
|
13898
|
-
if (stateSymbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
13899
|
-
const setterSymbol = getUseStateSetterSymbol(stateSymbol, scopes);
|
|
13900
|
-
if (!setterSymbol) return false;
|
|
13901
|
-
const setterWrite = getDirectSetterCall(setterSymbol, callback);
|
|
13902
|
-
if (!setterWrite) return false;
|
|
13903
|
-
const callbackStateReferences = stateSymbol.references.filter((reference) => isAstDescendant(reference.identifier, callback));
|
|
13904
|
-
if (callbackStateReferences.length !== 1) return false;
|
|
13905
|
-
const stateReferenceRoot = findTransparentExpressionRoot(callbackStateReferences[0].identifier);
|
|
13906
|
-
if (isNodeOfType(stateReferenceRoot.parent, "MemberExpression") && stateReferenceRoot.parent.object === stateReferenceRoot) return false;
|
|
13907
|
-
const counterpart = findDominatingGuardCounterpart(callbackStateReferences[0].identifier, setterWrite.callExpression, callback, scopes);
|
|
13908
|
-
return Boolean(counterpart && referencesSameValue(counterpart, setterWrite.writtenValue, scopes));
|
|
13909
|
-
};
|
|
13910
|
-
//#endregion
|
|
13911
13221
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13912
13222
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13913
13223
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13979,6 +13289,25 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13979
13289
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13980
13290
|
};
|
|
13981
13291
|
//#endregion
|
|
13292
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13293
|
+
/**
|
|
13294
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13295
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13296
|
+
* match (`true`) or the chain's root (`false`).
|
|
13297
|
+
*
|
|
13298
|
+
* Was duplicated byte-identical in:
|
|
13299
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
13300
|
+
* - semantic/closure-captures.ts
|
|
13301
|
+
*/
|
|
13302
|
+
const isAstDescendant = (inner, outer) => {
|
|
13303
|
+
let current = inner;
|
|
13304
|
+
while (current) {
|
|
13305
|
+
if (current === outer) return true;
|
|
13306
|
+
current = current.parent ?? null;
|
|
13307
|
+
}
|
|
13308
|
+
return false;
|
|
13309
|
+
};
|
|
13310
|
+
//#endregion
|
|
13982
13311
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13983
13312
|
/**
|
|
13984
13313
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -14159,7 +13488,6 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
14159
13488
|
"useLayoutEffect",
|
|
14160
13489
|
"useInsertionEffect"
|
|
14161
13490
|
]);
|
|
14162
|
-
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
14163
13491
|
const buildAdditionalHooksRegex = (additional) => {
|
|
14164
13492
|
if (!additional) return null;
|
|
14165
13493
|
try {
|
|
@@ -14296,7 +13624,7 @@ const stringifyMemberChain = (node) => {
|
|
|
14296
13624
|
}
|
|
14297
13625
|
return null;
|
|
14298
13626
|
};
|
|
14299
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys
|
|
13627
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14300
13628
|
const keys = /* @__PURE__ */ new Set();
|
|
14301
13629
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
14302
13630
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -14307,10 +13635,6 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
14307
13635
|
const symbol = reference.resolvedSymbol;
|
|
14308
13636
|
if (!symbol) continue;
|
|
14309
13637
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
-
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
-
stableCapturedNames.add(symbol.name);
|
|
14312
|
-
continue;
|
|
14313
|
-
}
|
|
14314
13638
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
14315
13639
|
stableCapturedNames.add(symbol.name);
|
|
14316
13640
|
continue;
|
|
@@ -14983,7 +14307,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14983
14307
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14984
14308
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14985
14309
|
}
|
|
14986
|
-
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys
|
|
14310
|
+
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
|
|
14987
14311
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14988
14312
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14989
14313
|
const missingCaptureKeys = [];
|
|
@@ -16797,14 +16121,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16797
16121
|
if (didFindRenderPathInvocation) return false;
|
|
16798
16122
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16799
16123
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16800
|
-
let enclosingFunction = findEnclosingFunction
|
|
16124
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
16801
16125
|
while (enclosingFunction) {
|
|
16802
16126
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16803
16127
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16804
16128
|
didFindRenderPathInvocation = true;
|
|
16805
16129
|
return;
|
|
16806
16130
|
}
|
|
16807
|
-
enclosingFunction = findEnclosingFunction
|
|
16131
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16808
16132
|
}
|
|
16809
16133
|
});
|
|
16810
16134
|
return didFindRenderPathInvocation;
|
|
@@ -16839,7 +16163,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16839
16163
|
};
|
|
16840
16164
|
return { CallExpression(node) {
|
|
16841
16165
|
if (!isImportedSelectAtom(node)) return;
|
|
16842
|
-
const nearestFunctionLike = findEnclosingFunction
|
|
16166
|
+
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16843
16167
|
if (!nearestFunctionLike) return;
|
|
16844
16168
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16845
16169
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17836,7 +17160,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17836
17160
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17837
17161
|
};
|
|
17838
17162
|
const isInsideCacheMemo = (node) => {
|
|
17839
|
-
const enclosingFunction = findEnclosingFunction
|
|
17163
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
17840
17164
|
let child = node;
|
|
17841
17165
|
let cursor = node.parent ?? null;
|
|
17842
17166
|
while (cursor) {
|
|
@@ -17874,7 +17198,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17874
17198
|
return null;
|
|
17875
17199
|
};
|
|
17876
17200
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17877
|
-
const enclosingFunction = findEnclosingFunction
|
|
17201
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
17878
17202
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17879
17203
|
const functionName = getFunctionName(enclosingFunction);
|
|
17880
17204
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -18065,6 +17389,13 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
18065
17389
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
18066
17390
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
18067
17391
|
};
|
|
17392
|
+
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
17393
|
+
let bindingNode = bindingIdentifier;
|
|
17394
|
+
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
17395
|
+
const property = bindingNode.parent;
|
|
17396
|
+
if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
17397
|
+
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
17398
|
+
};
|
|
18068
17399
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
18069
17400
|
if (basePath === "global") {
|
|
18070
17401
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -18354,89 +17685,7 @@ const collectEarlierAndGuardOperands = (node) => {
|
|
|
18354
17685
|
return earlierOperands;
|
|
18355
17686
|
};
|
|
18356
17687
|
//#endregion
|
|
18357
|
-
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
18358
|
-
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
18359
|
-
"freeze",
|
|
18360
|
-
"seal",
|
|
18361
|
-
"preventExtensions"
|
|
18362
|
-
]);
|
|
18363
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
18364
|
-
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18365
|
-
const expression = stripParenExpression(node);
|
|
18366
|
-
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
18367
|
-
const callee = stripParenExpression(expression.callee);
|
|
18368
|
-
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
18369
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
18370
|
-
return callee.property.name;
|
|
18371
|
-
};
|
|
18372
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18373
|
-
let expression = stripParenExpression(node);
|
|
18374
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
18375
|
-
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
18376
|
-
const wrappedExpression = expression.arguments[0];
|
|
18377
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
18378
|
-
expression = stripParenExpression(wrappedExpression);
|
|
18379
|
-
}
|
|
18380
|
-
return expression;
|
|
18381
|
-
};
|
|
18382
|
-
//#endregion
|
|
18383
17688
|
//#region src/plugin/rules/js-performance/js-length-check-first.ts
|
|
18384
|
-
const OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES = new Set(["keys", "values"]);
|
|
18385
|
-
const getGlobalObjectProjection = (expression, context) => {
|
|
18386
|
-
const callExpression = stripParenExpression(expression);
|
|
18387
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18388
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
18389
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
18390
|
-
const receiver = stripParenExpression(callee.object);
|
|
18391
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
18392
|
-
const methodName = getStaticPropertyName(callee);
|
|
18393
|
-
if (!methodName || !OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES.has(methodName)) return null;
|
|
18394
|
-
const callArguments = callExpression.arguments ?? [];
|
|
18395
|
-
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18396
|
-
return {
|
|
18397
|
-
methodName,
|
|
18398
|
-
source: callArguments[0]
|
|
18399
|
-
};
|
|
18400
|
-
};
|
|
18401
|
-
const isPlainDataObjectLiteral = (expression) => {
|
|
18402
|
-
const objectExpression = stripParenExpression(expression);
|
|
18403
|
-
if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
|
|
18404
|
-
return (objectExpression.properties ?? []).every((property) => {
|
|
18405
|
-
if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
|
|
18406
|
-
const propertyValue = stripParenExpression(property.value);
|
|
18407
|
-
return isNodeOfType(propertyValue, "Literal") || isNodeOfType(propertyValue, "TemplateLiteral") && propertyValue.expressions.length === 0;
|
|
18408
|
-
});
|
|
18409
|
-
};
|
|
18410
|
-
const getGlobalObjectFreezeArgument = (expression, context) => {
|
|
18411
|
-
const callExpression = stripParenExpression(expression);
|
|
18412
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18413
|
-
if (getObjectIntegrityMethodName(callExpression, context.scopes) !== "freeze") return null;
|
|
18414
|
-
const callArguments = callExpression.arguments ?? [];
|
|
18415
|
-
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18416
|
-
return callArguments[0];
|
|
18417
|
-
};
|
|
18418
|
-
const resolveStablePlainObjectRootId = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
18419
|
-
const source = stripParenExpression(expression);
|
|
18420
|
-
if (!isNodeOfType(source, "Identifier")) return null;
|
|
18421
|
-
const symbol = context.scopes.symbolFor(source);
|
|
18422
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
18423
|
-
const directConstInitializer = getDirectConstInitializer(symbol);
|
|
18424
|
-
if (!directConstInitializer) return null;
|
|
18425
|
-
visitedSymbolIds.add(symbol.id);
|
|
18426
|
-
const initializer = stripParenExpression(directConstInitializer);
|
|
18427
|
-
const frozenValue = getGlobalObjectFreezeArgument(initializer, context);
|
|
18428
|
-
if (frozenValue) return isPlainDataObjectLiteral(frozenValue) ? symbol.id : null;
|
|
18429
|
-
return resolveStablePlainObjectRootId(initializer, context, visitedSymbolIds);
|
|
18430
|
-
};
|
|
18431
|
-
const areEqualCardinalityObjectProjections = (receiverArray, indexedArray, context) => {
|
|
18432
|
-
const receiverProjection = getGlobalObjectProjection(receiverArray, context);
|
|
18433
|
-
const indexedProjection = getGlobalObjectProjection(indexedArray, context);
|
|
18434
|
-
if (!receiverProjection || !indexedProjection) return false;
|
|
18435
|
-
if (receiverProjection.methodName === indexedProjection.methodName) return false;
|
|
18436
|
-
const receiverRootId = resolveStablePlainObjectRootId(receiverProjection.source, context);
|
|
18437
|
-
const indexedRootId = resolveStablePlainObjectRootId(indexedProjection.source, context);
|
|
18438
|
-
return receiverRootId !== null && receiverRootId === indexedRootId;
|
|
18439
|
-
};
|
|
18440
17689
|
const findIndexedArrayObject = (callbackBody, indexParameterName) => {
|
|
18441
17690
|
let indexedArrayObject = null;
|
|
18442
17691
|
walkAst(callbackBody, (child) => {
|
|
@@ -18651,7 +17900,6 @@ const jsLengthCheckFirst = defineRule({
|
|
|
18651
17900
|
const resolvedReceiverSource = resolveComparedArraySource(receiverArrayObject, node);
|
|
18652
17901
|
const resolvedIndexedSource = resolveComparedArraySource(indexedArrayObject, node);
|
|
18653
17902
|
if (areExpressionsStructurallyEqual(resolvedReceiverSource, resolvedIndexedSource)) return;
|
|
18654
|
-
if (areEqualCardinalityObjectProjections(receiverArrayObject, indexedArrayObject, context) || areEqualCardinalityObjectProjections(resolvedReceiverSource, resolvedIndexedSource, context)) return;
|
|
18655
17903
|
const comparedArrayPairs = [[receiverArrayObject, indexedArrayObject], [resolvedReceiverSource, resolvedIndexedSource]];
|
|
18656
17904
|
if (collectEarlierAndGuardOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_ANY_COMPARISON_OPERATORS)))) return;
|
|
18657
17905
|
if (collectEarlierOrOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_MISMATCH_OPERATORS)))) return;
|
|
@@ -24529,6 +23777,32 @@ const hasDirective = (programNode, directive) => {
|
|
|
24529
23777
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
24530
23778
|
};
|
|
24531
23779
|
//#endregion
|
|
23780
|
+
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
23781
|
+
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
23782
|
+
"freeze",
|
|
23783
|
+
"seal",
|
|
23784
|
+
"preventExtensions"
|
|
23785
|
+
]);
|
|
23786
|
+
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
23787
|
+
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23788
|
+
const expression = stripParenExpression(node);
|
|
23789
|
+
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
23790
|
+
const callee = stripParenExpression(expression.callee);
|
|
23791
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
23792
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
23793
|
+
return callee.property.name;
|
|
23794
|
+
};
|
|
23795
|
+
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23796
|
+
let expression = stripParenExpression(node);
|
|
23797
|
+
while (isNodeOfType(expression, "CallExpression")) {
|
|
23798
|
+
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
23799
|
+
const wrappedExpression = expression.arguments[0];
|
|
23800
|
+
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
23801
|
+
expression = stripParenExpression(wrappedExpression);
|
|
23802
|
+
}
|
|
23803
|
+
return expression;
|
|
23804
|
+
};
|
|
23805
|
+
//#endregion
|
|
24532
23806
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
24533
23807
|
const nextjsAsyncClientComponent = defineRule({
|
|
24534
23808
|
id: "nextjs-async-client-component",
|
|
@@ -26868,7 +26142,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26868
26142
|
};
|
|
26869
26143
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26870
26144
|
if (!setterRef.resolved) return false;
|
|
26871
|
-
const componentFunction = findEnclosingFunction
|
|
26145
|
+
const componentFunction = findEnclosingFunction(effectNode);
|
|
26872
26146
|
if (!componentFunction) return false;
|
|
26873
26147
|
for (const reference of setterRef.resolved.references) {
|
|
26874
26148
|
if (reference.init) continue;
|
|
@@ -29641,6 +28915,138 @@ const noBarrelImport = defineRule({
|
|
|
29641
28915
|
}
|
|
29642
28916
|
});
|
|
29643
28917
|
//#endregion
|
|
28918
|
+
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
28919
|
+
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28920
|
+
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
28921
|
+
"CatchClause",
|
|
28922
|
+
"ConditionalExpression",
|
|
28923
|
+
"DoWhileStatement",
|
|
28924
|
+
"ForInStatement",
|
|
28925
|
+
"ForOfStatement",
|
|
28926
|
+
"ForStatement",
|
|
28927
|
+
"IfStatement",
|
|
28928
|
+
"LogicalExpression",
|
|
28929
|
+
"SwitchCase",
|
|
28930
|
+
"SwitchStatement",
|
|
28931
|
+
"TryStatement",
|
|
28932
|
+
"WhileStatement"
|
|
28933
|
+
]);
|
|
28934
|
+
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
28935
|
+
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
28936
|
+
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
28937
|
+
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
28938
|
+
const property = stripParenExpression(memberExpression.property);
|
|
28939
|
+
if (!isNodeOfType(property, "Identifier")) return null;
|
|
28940
|
+
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
28941
|
+
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
28942
|
+
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
28943
|
+
};
|
|
28944
|
+
const collectScopeSymbols = (scope, symbols) => {
|
|
28945
|
+
symbols.push(...scope.symbols);
|
|
28946
|
+
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
28947
|
+
};
|
|
28948
|
+
const getEquivalentSymbols = (identifier, scopes) => {
|
|
28949
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
28950
|
+
if (!rootSymbol) return [];
|
|
28951
|
+
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
28952
|
+
if (!symbolsByRootId) {
|
|
28953
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
28954
|
+
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
28955
|
+
}
|
|
28956
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
28957
|
+
if (cachedSymbols) return cachedSymbols;
|
|
28958
|
+
const allSymbols = [];
|
|
28959
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
28960
|
+
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
28961
|
+
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
28962
|
+
return equivalentSymbols;
|
|
28963
|
+
};
|
|
28964
|
+
const findExecutionBoundary = (node) => {
|
|
28965
|
+
let current = node;
|
|
28966
|
+
while (current) {
|
|
28967
|
+
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
28968
|
+
current = current.parent ?? null;
|
|
28969
|
+
}
|
|
28970
|
+
return null;
|
|
28971
|
+
};
|
|
28972
|
+
const isOnUnconditionalPath = (node, boundary) => {
|
|
28973
|
+
let current = node.parent ?? null;
|
|
28974
|
+
while (current && current !== boundary) {
|
|
28975
|
+
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
28976
|
+
current = current.parent ?? null;
|
|
28977
|
+
}
|
|
28978
|
+
return current === boundary;
|
|
28979
|
+
};
|
|
28980
|
+
const findFunctionBindingIdentifier = (functionNode) => {
|
|
28981
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
28982
|
+
const parent = functionNode.parent;
|
|
28983
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
28984
|
+
return null;
|
|
28985
|
+
};
|
|
28986
|
+
const findDirectCall = (identifier) => {
|
|
28987
|
+
let callee = identifier;
|
|
28988
|
+
let parent = callee.parent;
|
|
28989
|
+
while (parent && stripParenExpression(parent) === identifier) {
|
|
28990
|
+
callee = parent;
|
|
28991
|
+
parent = callee.parent;
|
|
28992
|
+
}
|
|
28993
|
+
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
28994
|
+
};
|
|
28995
|
+
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
28996
|
+
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
28997
|
+
visitedFunctionNodes.add(functionNode);
|
|
28998
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28999
|
+
if (!referenceBoundary) return false;
|
|
29000
|
+
const invocationCalls = [];
|
|
29001
|
+
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
29002
|
+
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
29003
|
+
const call = findDirectCall(reference.identifier);
|
|
29004
|
+
if (call) invocationCalls.push(call);
|
|
29005
|
+
}
|
|
29006
|
+
else {
|
|
29007
|
+
const call = findDirectCall(functionNode);
|
|
29008
|
+
if (call) invocationCalls.push(call);
|
|
29009
|
+
}
|
|
29010
|
+
return invocationCalls.some((call) => {
|
|
29011
|
+
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
29012
|
+
const callBoundary = findExecutionBoundary(call);
|
|
29013
|
+
if (!callBoundary) return false;
|
|
29014
|
+
if (callBoundary === referenceBoundary) return true;
|
|
29015
|
+
if (!isFunctionLike$2(callBoundary)) return false;
|
|
29016
|
+
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
29017
|
+
});
|
|
29018
|
+
};
|
|
29019
|
+
const isMemberWriteTarget = (memberExpression) => {
|
|
29020
|
+
const parent = memberExpression.parent;
|
|
29021
|
+
if (!parent) return false;
|
|
29022
|
+
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
29023
|
+
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
29024
|
+
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
29025
|
+
};
|
|
29026
|
+
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29027
|
+
let parent = reference.identifier.parent;
|
|
29028
|
+
while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
|
|
29029
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
|
|
29030
|
+
const writeBoundary = findExecutionBoundary(parent);
|
|
29031
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
29032
|
+
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
|
|
29033
|
+
if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
|
|
29034
|
+
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
29035
|
+
});
|
|
29036
|
+
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
29037
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
29038
|
+
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
29039
|
+
};
|
|
29040
|
+
//#endregion
|
|
29041
|
+
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
29042
|
+
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29043
|
+
if (reference.flag === "read") return false;
|
|
29044
|
+
const writeFunction = findEnclosingFunction(reference.identifier);
|
|
29045
|
+
if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
29046
|
+
if (!writeFunction) return true;
|
|
29047
|
+
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
29048
|
+
});
|
|
29049
|
+
//#endregion
|
|
29644
29050
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29645
29051
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29646
29052
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -30109,8 +29515,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
30109
29515
|
return isReactFunctionCall(parent, "useMemo");
|
|
30110
29516
|
};
|
|
30111
29517
|
const findEnclosingRenderFunction = (node) => {
|
|
30112
|
-
let enclosingFunction = findEnclosingFunction
|
|
30113
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
29518
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
29519
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30114
29520
|
return enclosingFunction;
|
|
30115
29521
|
};
|
|
30116
29522
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -31161,7 +30567,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
31161
30567
|
const isEffectDrivenResync = (useStateCall) => {
|
|
31162
30568
|
const setterName = getStateSetterName(useStateCall);
|
|
31163
30569
|
if (!setterName) return false;
|
|
31164
|
-
const componentFunction = findEnclosingFunction
|
|
30570
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31165
30571
|
if (!componentFunction) return false;
|
|
31166
30572
|
let isExempt = false;
|
|
31167
30573
|
walkAst(componentFunction, (child) => {
|
|
@@ -31265,7 +30671,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
31265
30671
|
const setterName = getStateSetterName(useStateCall);
|
|
31266
30672
|
if (!setterName) return false;
|
|
31267
30673
|
const stateValueName = getStateValueName(useStateCall);
|
|
31268
|
-
const componentFunction = findEnclosingFunction
|
|
30674
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31269
30675
|
if (!componentFunction) return false;
|
|
31270
30676
|
let isExempt = false;
|
|
31271
30677
|
walkAst(componentFunction, (child) => {
|
|
@@ -31311,7 +30717,7 @@ const noDerivedUseState = defineRule({
|
|
|
31311
30717
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
31312
30718
|
if (isEffectDrivenResync(node)) return;
|
|
31313
30719
|
if (isNextjsDataFetchingPage(node)) return;
|
|
31314
|
-
const componentFunction = findEnclosingFunction
|
|
30720
|
+
const componentFunction = findEnclosingFunction(node);
|
|
31315
30721
|
if (componentFunction) {
|
|
31316
30722
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
31317
30723
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -32786,29 +32192,6 @@ const isReturnOnlyStatement = (node) => {
|
|
|
32786
32192
|
return isNodeOfType(node, "BlockStatement") && (node.body?.length ?? 0) === 1 && isNodeOfType(node.body?.[0], "ReturnStatement");
|
|
32787
32193
|
};
|
|
32788
32194
|
const hasEventLikeRemainingStatements = (statements) => statements.some((statement) => !isNodeOfType(statement, "ReturnStatement") && hasEventLikeNode(statement));
|
|
32789
|
-
const collectLeadingEarlyReturnGuards = (statements) => {
|
|
32790
|
-
const guardExpressions = [];
|
|
32791
|
-
for (const statement of statements) {
|
|
32792
|
-
if (isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
32793
|
-
if (!isNodeOfType(statement, "IfStatement") || statement.alternate || !isReturnOnlyStatement(statement.consequent)) break;
|
|
32794
|
-
collectGuardExpressions(statement.test, guardExpressions);
|
|
32795
|
-
}
|
|
32796
|
-
return guardExpressions;
|
|
32797
|
-
};
|
|
32798
|
-
const collectImmutableExpressionOrigins = (expression, scopes) => {
|
|
32799
|
-
const origins = [];
|
|
32800
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
32801
|
-
let currentExpression = stripParenExpression(expression);
|
|
32802
|
-
while (currentExpression) {
|
|
32803
|
-
origins.push(currentExpression);
|
|
32804
|
-
if (!isNodeOfType(currentExpression, "Identifier")) break;
|
|
32805
|
-
const bindingSymbol = scopes.symbolFor(currentExpression);
|
|
32806
|
-
if (!bindingSymbol || bindingSymbol.kind !== "const" || visitedSymbolIds.has(bindingSymbol.id) || !bindingSymbol.initializer) break;
|
|
32807
|
-
visitedSymbolIds.add(bindingSymbol.id);
|
|
32808
|
-
currentExpression = stripParenExpression(bindingSymbol.initializer);
|
|
32809
|
-
}
|
|
32810
|
-
return origins;
|
|
32811
|
-
};
|
|
32812
32195
|
const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
32813
32196
|
const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
|
|
32814
32197
|
if (!unwrappedDependencyExpression) return false;
|
|
@@ -32816,69 +32199,6 @@ const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
|
32816
32199
|
return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
|
|
32817
32200
|
};
|
|
32818
32201
|
const hasDependencyMatch = (guardExpression, dependencyExpressions) => dependencyExpressions.some((dependencyExpression) => doesGuardMatchDependency(guardExpression, dependencyExpression));
|
|
32819
|
-
const hasAliasedDependencyMatch = (guardExpression, dependencyExpressions, scopes) => {
|
|
32820
|
-
const guardOrigins = collectImmutableExpressionOrigins(guardExpression.expression, scopes);
|
|
32821
|
-
return dependencyExpressions.some((dependencyExpression) => {
|
|
32822
|
-
if (!dependencyExpression) return false;
|
|
32823
|
-
const dependencyOrigins = collectImmutableExpressionOrigins(dependencyExpression, scopes);
|
|
32824
|
-
return guardOrigins.some((guardOrigin) => dependencyOrigins.some((dependencyOrigin) => areExpressionsStructurallyEqual(guardOrigin, dependencyOrigin)));
|
|
32825
|
-
});
|
|
32826
|
-
};
|
|
32827
|
-
const isStaticallyTrue = (expression, scopes) => collectImmutableExpressionOrigins(expression, scopes).some((origin) => isNodeOfType(origin, "Literal") && origin.value === true);
|
|
32828
|
-
const isReactRouterReplacementNavigation = (node, rootIdentifierNames, scopes) => {
|
|
32829
|
-
let didFindNavigation = false;
|
|
32830
|
-
let didFindOtherTriggeredSideEffect = false;
|
|
32831
|
-
walkAst(node, (child) => {
|
|
32832
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32833
|
-
if (!(findTriggeredSideEffectCalleeName(child) !== null || hasDocumentClassListMutation(child))) return;
|
|
32834
|
-
const destinationExpression = child.arguments?.[0];
|
|
32835
|
-
const doesDestinationReferenceReconciliationValue = Boolean(destinationExpression && collectImmutableExpressionOrigins(destinationExpression, scopes).some((origin) => doesNodeReferenceAnyRoot(origin, rootIdentifierNames)));
|
|
32836
|
-
if (!isNodeOfType(child.callee, "Identifier") || !doesDestinationReferenceReconciliationValue) {
|
|
32837
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32838
|
-
return;
|
|
32839
|
-
}
|
|
32840
|
-
const navigationSymbol = scopes.symbolFor(child.callee);
|
|
32841
|
-
const navigationInitializer = navigationSymbol?.initializer ? stripParenExpression(navigationSymbol.initializer) : null;
|
|
32842
|
-
if (navigationSymbol?.kind !== "const" || !isNodeOfType(navigationInitializer, "CallExpression") || !isNodeOfType(navigationInitializer.callee, "Identifier")) {
|
|
32843
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32844
|
-
return;
|
|
32845
|
-
}
|
|
32846
|
-
if (scopes.symbolFor(navigationInitializer.callee)?.kind !== "import") {
|
|
32847
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32848
|
-
return;
|
|
32849
|
-
}
|
|
32850
|
-
if ((getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router-dom") ?? getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router")) !== "useNavigate") {
|
|
32851
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32852
|
-
return;
|
|
32853
|
-
}
|
|
32854
|
-
const navigationOptionsArgument = child.arguments?.[1];
|
|
32855
|
-
const navigationOptions = navigationOptionsArgument ? stripParenExpression(navigationOptionsArgument) : null;
|
|
32856
|
-
if (!isNodeOfType(navigationOptions, "ObjectExpression")) {
|
|
32857
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32858
|
-
return;
|
|
32859
|
-
}
|
|
32860
|
-
let isReplacementGuaranteed = false;
|
|
32861
|
-
for (const property of navigationOptions.properties) {
|
|
32862
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
32863
|
-
isReplacementGuaranteed = false;
|
|
32864
|
-
continue;
|
|
32865
|
-
}
|
|
32866
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
32867
|
-
if (propertyName === null) {
|
|
32868
|
-
isReplacementGuaranteed = false;
|
|
32869
|
-
continue;
|
|
32870
|
-
}
|
|
32871
|
-
if (propertyName !== "replace") continue;
|
|
32872
|
-
isReplacementGuaranteed = isStaticallyTrue(property.value, scopes);
|
|
32873
|
-
}
|
|
32874
|
-
if (isReplacementGuaranteed) {
|
|
32875
|
-
didFindNavigation = true;
|
|
32876
|
-
return;
|
|
32877
|
-
}
|
|
32878
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32879
|
-
});
|
|
32880
|
-
return didFindNavigation && !didFindOtherTriggeredSideEffect;
|
|
32881
|
-
};
|
|
32882
32202
|
const isEqualityToLiteralGuard = (guardExpression) => {
|
|
32883
32203
|
const parent = guardExpression.expression.parent;
|
|
32884
32204
|
if (!isNodeOfType(parent, "BinaryExpression")) return false;
|
|
@@ -32943,35 +32263,18 @@ const noEffectEventHandler = defineRule({
|
|
|
32943
32263
|
if (statements.length === 0) return;
|
|
32944
32264
|
const soleStatement = statements[0];
|
|
32945
32265
|
if (!isNodeOfType(soleStatement, "IfStatement")) return;
|
|
32946
|
-
const
|
|
32947
|
-
collectGuardExpressions(soleStatement.test,
|
|
32948
|
-
const
|
|
32949
|
-
if (guardExpressions.length === 0) guardExpressions.push(...initialGuardExpressions);
|
|
32950
|
-
const matchingPropGuardExpressions = initialGuardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
|
|
32266
|
+
const guardExpressions = [];
|
|
32267
|
+
collectGuardExpressions(soleStatement.test, guardExpressions);
|
|
32268
|
+
const matchingPropGuardExpressions = guardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
|
|
32951
32269
|
if (matchingPropGuardExpressions.length === 0) return;
|
|
32952
32270
|
const isSingleGuardedEventLikeStatement = statements.length === 1 && hasEventLikeNode(soleStatement.consequent);
|
|
32953
32271
|
const isEarlyReturnGuardedEventLikeBody = statements.length > 1 && !soleStatement.alternate && isReturnOnlyStatement(soleStatement.consequent) && hasEventLikeRemainingStatements(statements.slice(1));
|
|
32954
32272
|
if (!isSingleGuardedEventLikeStatement && !isEarlyReturnGuardedEventLikeBody) return;
|
|
32955
32273
|
if (isEarlyReturnGuardedEventLikeBody && !isSingleGuardedEventLikeStatement && matchingPropGuardExpressions.every(isEqualityToLiteralGuard)) return;
|
|
32956
|
-
if (
|
|
32274
|
+
if (guardExpressions.some((guardExpression) => !matchingPropGuardExpressions.some((matchingGuardExpression) => matchingGuardExpression.expression === guardExpression.expression))) {
|
|
32957
32275
|
const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32958
32276
|
if (!(isSingleGuardedEventLikeStatement ? doesEventLikeCallReferenceAnyRoot(soleStatement.consequent, matchingPropRootNames) : doesAnyEventLikeCallReferenceAnyRoot(statements.slice(1), matchingPropRootNames))) return;
|
|
32959
32277
|
}
|
|
32960
|
-
if (isEarlyReturnGuardedEventLikeBody) {
|
|
32961
|
-
const reconciliationGuardRootNames = new Set(guardExpressions.filter((guardExpression) => !propStackTracker.isPropName(guardExpression.rootIdentifierName, node) && hasAliasedDependencyMatch(guardExpression, dependencyExpressions, context.scopes) && guardExpressions.some((comparisonGuardExpression) => {
|
|
32962
|
-
if (comparisonGuardExpression.rootIdentifierName !== guardExpression.rootIdentifierName) return false;
|
|
32963
|
-
const comparisonExpression = comparisonGuardExpression.expression.parent;
|
|
32964
|
-
if (!isNodeOfType(comparisonExpression, "BinaryExpression") || comparisonExpression.operator !== "===" && comparisonExpression.operator !== "==") return false;
|
|
32965
|
-
const comparedRootIdentifierName = getRootIdentifierName(comparisonExpression.left === comparisonGuardExpression.expression ? comparisonExpression.right : comparisonExpression.left);
|
|
32966
|
-
return Boolean(comparedRootIdentifierName && propStackTracker.isPropName(comparedRootIdentifierName, node));
|
|
32967
|
-
})).map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32968
|
-
if (reconciliationGuardRootNames.size > 0) {
|
|
32969
|
-
const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32970
|
-
const eventLikeStatements = statements.slice(1);
|
|
32971
|
-
const triggeredStatements = eventLikeStatements.filter(hasEventLikeNode);
|
|
32972
|
-
if (triggeredStatements.length > 0 && triggeredStatements.every((statement) => isReactRouterReplacementNavigation(statement, reconciliationGuardRootNames, context.scopes)) && !doesAnyEventLikeCallReferenceAnyRoot(eventLikeStatements, matchingPropRootNames)) return;
|
|
32973
|
-
}
|
|
32974
|
-
}
|
|
32975
32278
|
context.report({
|
|
32976
32279
|
node,
|
|
32977
32280
|
message: "This useEffect is simulating an event handler, which costs an extra render & runs late."
|
|
@@ -33252,7 +32555,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
33252
32555
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
33253
32556
|
const parent = identifier.parent;
|
|
33254
32557
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
33255
|
-
if (findEnclosingFunction
|
|
32558
|
+
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33256
32559
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
33257
32560
|
}
|
|
33258
32561
|
return true;
|
|
@@ -33298,7 +32601,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
33298
32601
|
if (!setterVariable) return false;
|
|
33299
32602
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
33300
32603
|
if (!stateDeclarator) return false;
|
|
33301
|
-
const componentFunction = findEnclosingFunction
|
|
32604
|
+
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33302
32605
|
if (!componentFunction) return false;
|
|
33303
32606
|
let hasWriter = false;
|
|
33304
32607
|
for (const reference of setterVariable.references) {
|
|
@@ -34360,8 +33663,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
34360
33663
|
return false;
|
|
34361
33664
|
};
|
|
34362
33665
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
34363
|
-
const requestFunction = findEnclosingFunction
|
|
34364
|
-
const completionSinkFunction = findEnclosingFunction
|
|
33666
|
+
const requestFunction = findEnclosingFunction(request);
|
|
33667
|
+
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34365
33668
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
34366
33669
|
if (requestFunction === completionSinkFunction) {
|
|
34367
33670
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -34648,6 +33951,13 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
34648
33951
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
34649
33952
|
};
|
|
34650
33953
|
//#endregion
|
|
33954
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
33955
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33956
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
33957
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33958
|
+
};
|
|
33959
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33960
|
+
//#endregion
|
|
34651
33961
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
34652
33962
|
const isLibraryDevPage = (filename) => {
|
|
34653
33963
|
if (!filename) return false;
|
|
@@ -35211,7 +34521,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
35211
34521
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
35212
34522
|
}
|
|
35213
34523
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
35214
|
-
if (findEnclosingFunction
|
|
34524
|
+
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35215
34525
|
}
|
|
35216
34526
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
35217
34527
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -35334,7 +34644,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
35334
34644
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
35335
34645
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
35336
34646
|
if (!componentOrHookNode) return;
|
|
35337
|
-
const enclosingFunction = findEnclosingFunction
|
|
34647
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
35338
34648
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
35339
34649
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
35340
34650
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -35782,10 +35092,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35782
35092
|
return false;
|
|
35783
35093
|
};
|
|
35784
35094
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35785
|
-
let functionNode = findEnclosingFunction
|
|
35095
|
+
let functionNode = findEnclosingFunction(node);
|
|
35786
35096
|
while (functionNode) {
|
|
35787
35097
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35788
|
-
functionNode = findEnclosingFunction
|
|
35098
|
+
functionNode = findEnclosingFunction(functionNode);
|
|
35789
35099
|
}
|
|
35790
35100
|
return !isInsideInstanceField(node);
|
|
35791
35101
|
};
|
|
@@ -36303,221 +35613,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
36303
35613
|
}
|
|
36304
35614
|
});
|
|
36305
35615
|
//#endregion
|
|
36306
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
36307
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
36308
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
36309
|
-
const expression = stripParenExpression(node);
|
|
36310
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36311
|
-
const propertyName = getStaticPropertyName(expression);
|
|
36312
|
-
const receiver = stripParenExpression(expression.object);
|
|
36313
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
36314
|
-
}
|
|
36315
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36316
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
36317
|
-
const symbol = scopes.symbolFor(expression);
|
|
36318
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
36319
|
-
visitedSymbolIds.add(symbol.id);
|
|
36320
|
-
if (isImportedFromReact(symbol)) {
|
|
36321
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
36322
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
36323
|
-
}
|
|
36324
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36325
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
36326
|
-
};
|
|
36327
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36328
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
36329
|
-
visitedClassNodes.add(classNode);
|
|
36330
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36331
|
-
};
|
|
36332
|
-
//#endregion
|
|
36333
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
36334
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
36335
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36336
|
-
let containsReactHookCall = false;
|
|
36337
|
-
walkAst(functionNode.body, (node) => {
|
|
36338
|
-
if (containsReactHookCall) return false;
|
|
36339
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
36340
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36341
|
-
containsReactHookCall = true;
|
|
36342
|
-
return false;
|
|
36343
|
-
}
|
|
36344
|
-
});
|
|
36345
|
-
return containsReactHookCall;
|
|
36346
|
-
};
|
|
36347
|
-
//#endregion
|
|
36348
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
36349
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
36350
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
36351
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
36352
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
36353
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
36354
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
36355
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
36356
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
36357
|
-
const propertyValue = stripParenExpression(property.value);
|
|
36358
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
36359
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
36360
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
36361
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
36362
|
-
}
|
|
36363
|
-
}
|
|
36364
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
36365
|
-
const candidate = stripParenExpression(expression);
|
|
36366
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36367
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36368
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
36369
|
-
}
|
|
36370
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
36371
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
36372
|
-
const receiver = stripParenExpression(candidate.object);
|
|
36373
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
36374
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
36375
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
36376
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
36377
|
-
}, controlFlow);
|
|
36378
|
-
};
|
|
36379
|
-
//#endregion
|
|
36380
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
36381
|
-
const isNullExpression = (expression) => {
|
|
36382
|
-
const candidate = stripParenExpression(expression);
|
|
36383
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
36384
|
-
};
|
|
36385
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
36386
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36387
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
36388
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
36389
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
36390
|
-
};
|
|
36391
|
-
//#endregion
|
|
36392
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
36393
|
-
const findFactoryRoot = (node) => {
|
|
36394
|
-
const candidate = stripParenExpression(node);
|
|
36395
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
36396
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
36397
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
36398
|
-
return null;
|
|
36399
|
-
};
|
|
36400
|
-
const findFactoryPropertyName = (node) => {
|
|
36401
|
-
const candidate = stripParenExpression(node);
|
|
36402
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
36403
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
36404
|
-
return null;
|
|
36405
|
-
};
|
|
36406
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
36407
|
-
const candidate = stripParenExpression(expression);
|
|
36408
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
36409
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
36410
|
-
if (!factoryRoot) return false;
|
|
36411
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
36412
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
36413
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
36414
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
36415
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
36416
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
36417
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
36418
|
-
};
|
|
36419
|
-
//#endregion
|
|
36420
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
36421
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
36422
|
-
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
36423
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
36424
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36425
|
-
const candidate = stripParenExpression(expression);
|
|
36426
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
36427
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
36428
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
36429
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36430
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36431
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
36432
|
-
visitedSymbolIds.add(symbol.id);
|
|
36433
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
36434
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
36435
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
36436
|
-
}
|
|
36437
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
36438
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
36439
|
-
const factoryCallee = stripParenExpression(candidate.callee);
|
|
36440
|
-
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
36441
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36442
|
-
const wrappedComponent = candidate.arguments[0];
|
|
36443
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
36444
|
-
}
|
|
36445
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
36446
|
-
const factory = candidate.arguments[0];
|
|
36447
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
36448
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
36449
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
36450
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
36451
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
36452
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
36453
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
36454
|
-
};
|
|
36455
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
36456
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
36457
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
36458
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
36459
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
36460
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
36461
|
-
continue;
|
|
36462
|
-
}
|
|
36463
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
36464
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
36465
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
36466
|
-
continue;
|
|
36467
|
-
}
|
|
36468
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
36469
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
36470
|
-
continue;
|
|
36471
|
-
}
|
|
36472
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
36473
|
-
}
|
|
36474
|
-
return false;
|
|
36475
|
-
};
|
|
36476
|
-
//#endregion
|
|
36477
|
-
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
36478
|
-
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
36479
|
-
"ComponentClass",
|
|
36480
|
-
"ComponentType",
|
|
36481
|
-
"FC",
|
|
36482
|
-
"FunctionComponent"
|
|
36483
|
-
]);
|
|
36484
|
-
const findVisibleSymbol = (identifier, scopes) => {
|
|
36485
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
36486
|
-
let scope = scopes.scopeFor(identifier);
|
|
36487
|
-
while (true) {
|
|
36488
|
-
const symbol = scope.symbolsByName.get(identifier.name);
|
|
36489
|
-
if (symbol) return symbol;
|
|
36490
|
-
if (!scope.parent) return null;
|
|
36491
|
-
scope = scope.parent;
|
|
36492
|
-
}
|
|
36493
|
-
};
|
|
36494
|
-
const isReactNamespaceType = (identifier, scopes) => {
|
|
36495
|
-
const symbol = findVisibleSymbol(identifier, scopes);
|
|
36496
|
-
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
36497
|
-
};
|
|
36498
|
-
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
36499
|
-
if (!typeNode) return false;
|
|
36500
|
-
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36501
|
-
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
36502
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
36503
|
-
const typeName = typeNode.typeName;
|
|
36504
|
-
if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
|
|
36505
|
-
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
36506
|
-
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
36507
|
-
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
36508
|
-
if (isImportedFromReact(typeSymbol)) {
|
|
36509
|
-
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
36510
|
-
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
36511
|
-
}
|
|
36512
|
-
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
36513
|
-
visitedSymbolIds.add(typeSymbol.id);
|
|
36514
|
-
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36515
|
-
};
|
|
36516
|
-
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
36517
|
-
const bindingIdentifier = symbol.bindingIdentifier;
|
|
36518
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
36519
|
-
};
|
|
36520
|
-
//#endregion
|
|
36521
35616
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
36522
35617
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
36523
35618
|
"childContextTypes",
|
|
@@ -36528,6 +35623,15 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
36528
35623
|
if (memberName === "childContextTypes" || memberName === "getChildContext") return `${memberName} uses the old context API that React 19 removes, so your provider stops passing data. Switch to \`createContext\` with \`<MyContext.Provider value={...}>\` & read it with \`useContext()\`, moving every consumer together.`;
|
|
36529
35624
|
return "contextTypes uses the old context API that React 19 removes, so your component stops receiving context. Use `static contextType = MyContext` or `useContext()` in a function component, & update the provider too.";
|
|
36530
35625
|
};
|
|
35626
|
+
const isInsideClassBody = (node) => {
|
|
35627
|
+
let current = node.parent;
|
|
35628
|
+
while (current) {
|
|
35629
|
+
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35630
|
+
if (isFunctionLike$2(current)) return false;
|
|
35631
|
+
current = current.parent;
|
|
35632
|
+
}
|
|
35633
|
+
return false;
|
|
35634
|
+
};
|
|
36531
35635
|
const noLegacyContextApi = defineRule({
|
|
36532
35636
|
id: "no-legacy-context-api",
|
|
36533
35637
|
title: "Legacy context API",
|
|
@@ -36542,7 +35646,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36542
35646
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
36543
35647
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
36544
35648
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36545
|
-
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
36546
35649
|
context.report({
|
|
36547
35650
|
node: memberNode.key,
|
|
36548
35651
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -36550,8 +35653,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36550
35653
|
};
|
|
36551
35654
|
return {
|
|
36552
35655
|
ClassBody(node) {
|
|
36553
|
-
const classNode = node.parent;
|
|
36554
|
-
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
36555
35656
|
for (const member of node.body ?? []) checkMember(member);
|
|
36556
35657
|
},
|
|
36557
35658
|
AssignmentExpression(node) {
|
|
@@ -36561,11 +35662,9 @@ const noLegacyContextApi = defineRule({
|
|
|
36561
35662
|
if (left.computed) return;
|
|
36562
35663
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
36563
35664
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
36564
|
-
if (left.
|
|
36565
|
-
|
|
36566
|
-
if (
|
|
36567
|
-
const symbol = context.scopes.symbolFor(component);
|
|
36568
|
-
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35665
|
+
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
35666
|
+
if (!isUppercaseName(left.object.name)) return;
|
|
35667
|
+
if (isInsideClassBody(node)) return;
|
|
36569
35668
|
context.report({
|
|
36570
35669
|
node: left,
|
|
36571
35670
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36769,10 +35868,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
36769
35868
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
36770
35869
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
36771
35870
|
visitedFunctionNodes.add(functionNode);
|
|
36772
|
-
const usageFunction = findEnclosingFunction
|
|
35871
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36773
35872
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
36774
35873
|
if (immediateCall) {
|
|
36775
|
-
const immediateCallFunction = findEnclosingFunction
|
|
35874
|
+
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36776
35875
|
if (immediateCallFunction === usageFunction) {
|
|
36777
35876
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
36778
35877
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -36781,7 +35880,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36781
35880
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
36782
35881
|
}
|
|
36783
35882
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
36784
|
-
const methodCallFunction = findEnclosingFunction
|
|
35883
|
+
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36785
35884
|
if (methodCallFunction === usageFunction) {
|
|
36786
35885
|
const methodCallStart = getRangeStart(methodCall);
|
|
36787
35886
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -36804,7 +35903,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36804
35903
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
36805
35904
|
const call = getDirectCallForExpression(child);
|
|
36806
35905
|
if (!call) return false;
|
|
36807
|
-
const callFunction = findEnclosingFunction
|
|
35906
|
+
const callFunction = findEnclosingFunction(call);
|
|
36808
35907
|
if (callFunction === usageFunction) {
|
|
36809
35908
|
const callStart = getRangeStart(call);
|
|
36810
35909
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -36835,14 +35934,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
36835
35934
|
visitedMutationSymbolIds.add(symbol.id);
|
|
36836
35935
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
36837
35936
|
if (typeof usageBoundary !== "number") return true;
|
|
36838
|
-
const usageFunction = findEnclosingFunction
|
|
35937
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36839
35938
|
return symbol.references.some((reference) => {
|
|
36840
35939
|
const referenceStart = getRangeStart(reference.identifier);
|
|
36841
35940
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
36842
35941
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
36843
35942
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
36844
35943
|
if (referenceStart === null) return true;
|
|
36845
|
-
const mutationFunction = findEnclosingFunction
|
|
35944
|
+
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36846
35945
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
36847
35946
|
if (!mutationFunction) return usageFunction !== null;
|
|
36848
35947
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -37424,6 +36523,13 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
37424
36523
|
return isReactNamespaceImport(node, scopes);
|
|
37425
36524
|
};
|
|
37426
36525
|
const isReactHocMemberReference = (node, scopes) => Boolean(isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && REACT_HOC_NAMES.has(node.property.name) && isReactNamespaceExpression(node.object, scopes));
|
|
36526
|
+
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36527
|
+
const property = symbol.bindingIdentifier.parent;
|
|
36528
|
+
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36529
|
+
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36530
|
+
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36531
|
+
return null;
|
|
36532
|
+
};
|
|
37427
36533
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
37428
36534
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
37429
36535
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -37433,7 +36539,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
37433
36539
|
}
|
|
37434
36540
|
const init = symbol.initializer;
|
|
37435
36541
|
if (!init) return false;
|
|
37436
|
-
const destructuredPropertyName =
|
|
36542
|
+
const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
|
|
37437
36543
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
37438
36544
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
37439
36545
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -39187,6 +38293,12 @@ const getDeclarationKind = (declarator) => {
|
|
|
39187
38293
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
39188
38294
|
};
|
|
39189
38295
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
38296
|
+
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
38297
|
+
const property = bindingIdentifier.parent;
|
|
38298
|
+
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
38299
|
+
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
38300
|
+
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
38301
|
+
};
|
|
39190
38302
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
39191
38303
|
const unwrappedExpression = stripParenExpression(expression);
|
|
39192
38304
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -39196,7 +38308,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39196
38308
|
if (isProp(analysis, callbackReference)) {
|
|
39197
38309
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
39198
38310
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
39199
|
-
return (bindingIdentifier &&
|
|
38311
|
+
return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
39200
38312
|
}
|
|
39201
38313
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
39202
38314
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -39206,7 +38318,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39206
38318
|
visitedVariables.add(callbackVariable);
|
|
39207
38319
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
39208
38320
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
39209
|
-
const propertyName = bindingIdentifier ?
|
|
38321
|
+
const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
|
|
39210
38322
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
39211
38323
|
return propertyName && propsReference ? propertyName : null;
|
|
39212
38324
|
}
|
|
@@ -39309,7 +38421,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
39309
38421
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
39310
38422
|
if (!bindingProvenance) return null;
|
|
39311
38423
|
const { refCall, variables } = bindingProvenance;
|
|
39312
|
-
const componentFunction = findEnclosingFunction
|
|
38424
|
+
const componentFunction = findEnclosingFunction(effectCall);
|
|
39313
38425
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
39314
38426
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
39315
38427
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39765,25 +38877,14 @@ const isPropsChildrenLength = (node, scopes) => {
|
|
|
39765
38877
|
const unwrappedNode = stripParenExpression(node);
|
|
39766
38878
|
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
39767
38879
|
};
|
|
39768
|
-
const resolveStaticNumericValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39769
|
-
const unwrappedNode = stripParenExpression(node);
|
|
39770
|
-
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "number") return Number.isFinite(unwrappedNode.value) ? unwrappedNode.value : null;
|
|
39771
|
-
if (!isNodeOfType(unwrappedNode, "Identifier")) return null;
|
|
39772
|
-
const symbol = scopes.symbolFor(unwrappedNode);
|
|
39773
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
39774
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
39775
|
-
if (!initializer) return null;
|
|
39776
|
-
return resolveStaticNumericValue(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id));
|
|
39777
|
-
};
|
|
39778
38880
|
const isLargeTextLengthComparison = (node, scopes) => {
|
|
39779
38881
|
const unwrappedNode = stripParenExpression(node);
|
|
39780
38882
|
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
39781
38883
|
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
39782
38884
|
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
39783
38885
|
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
39784
|
-
if (!leftIsLength && !rightIsLength) return false;
|
|
39785
|
-
|
|
39786
|
-
if (thresholdValue === null || thresholdValue < 1e3) return false;
|
|
38886
|
+
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
38887
|
+
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
39787
38888
|
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
39788
38889
|
};
|
|
39789
38890
|
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
@@ -40156,6 +39257,174 @@ const noPropCallbackInRender = defineRule({
|
|
|
40156
39257
|
} })
|
|
40157
39258
|
});
|
|
40158
39259
|
//#endregion
|
|
39260
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39261
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39262
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39263
|
+
const expression = stripParenExpression(node);
|
|
39264
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39265
|
+
const propertyName = getStaticPropertyName(expression);
|
|
39266
|
+
const receiver = stripParenExpression(expression.object);
|
|
39267
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39268
|
+
}
|
|
39269
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39270
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39271
|
+
const symbol = scopes.symbolFor(expression);
|
|
39272
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39273
|
+
visitedSymbolIds.add(symbol.id);
|
|
39274
|
+
if (isImportedFromReact(symbol)) {
|
|
39275
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
39276
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39277
|
+
}
|
|
39278
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39279
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39280
|
+
};
|
|
39281
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39282
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39283
|
+
visitedClassNodes.add(classNode);
|
|
39284
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39285
|
+
};
|
|
39286
|
+
//#endregion
|
|
39287
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39288
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39289
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39290
|
+
let containsReactHookCall = false;
|
|
39291
|
+
walkAst(functionNode.body, (node) => {
|
|
39292
|
+
if (containsReactHookCall) return false;
|
|
39293
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39294
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39295
|
+
containsReactHookCall = true;
|
|
39296
|
+
return false;
|
|
39297
|
+
}
|
|
39298
|
+
});
|
|
39299
|
+
return containsReactHookCall;
|
|
39300
|
+
};
|
|
39301
|
+
//#endregion
|
|
39302
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39303
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39304
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39305
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39306
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39307
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39308
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39309
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39310
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39311
|
+
const propertyValue = stripParenExpression(property.value);
|
|
39312
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39313
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39314
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39315
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39316
|
+
}
|
|
39317
|
+
}
|
|
39318
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39319
|
+
const candidate = stripParenExpression(expression);
|
|
39320
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39321
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39322
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39323
|
+
}
|
|
39324
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39325
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39326
|
+
const receiver = stripParenExpression(candidate.object);
|
|
39327
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39328
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39329
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39330
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39331
|
+
}, controlFlow);
|
|
39332
|
+
};
|
|
39333
|
+
//#endregion
|
|
39334
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39335
|
+
const isNullExpression = (expression) => {
|
|
39336
|
+
const candidate = stripParenExpression(expression);
|
|
39337
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39338
|
+
};
|
|
39339
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
39340
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39341
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39342
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39343
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39344
|
+
};
|
|
39345
|
+
//#endregion
|
|
39346
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39347
|
+
const findFactoryRoot = (node) => {
|
|
39348
|
+
const candidate = stripParenExpression(node);
|
|
39349
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39350
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39351
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39352
|
+
return null;
|
|
39353
|
+
};
|
|
39354
|
+
const findFactoryPropertyName = (node) => {
|
|
39355
|
+
const candidate = stripParenExpression(node);
|
|
39356
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39357
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39358
|
+
return null;
|
|
39359
|
+
};
|
|
39360
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39361
|
+
const candidate = stripParenExpression(expression);
|
|
39362
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39363
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39364
|
+
if (!factoryRoot) return false;
|
|
39365
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39366
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39367
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39368
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
39369
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39370
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
39371
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39372
|
+
};
|
|
39373
|
+
//#endregion
|
|
39374
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39375
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39376
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39377
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39378
|
+
const candidate = stripParenExpression(expression);
|
|
39379
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39380
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39381
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39382
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39383
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39384
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39385
|
+
visitedSymbolIds.add(symbol.id);
|
|
39386
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39387
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39388
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39389
|
+
}
|
|
39390
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39391
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39392
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39393
|
+
const wrappedComponent = candidate.arguments[0];
|
|
39394
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39395
|
+
}
|
|
39396
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39397
|
+
const factory = candidate.arguments[0];
|
|
39398
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39399
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
39400
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39401
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39402
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39403
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
39404
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39405
|
+
};
|
|
39406
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39407
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39408
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
39409
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39410
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39411
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39412
|
+
continue;
|
|
39413
|
+
}
|
|
39414
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39415
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39416
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39417
|
+
continue;
|
|
39418
|
+
}
|
|
39419
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39420
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39421
|
+
continue;
|
|
39422
|
+
}
|
|
39423
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39424
|
+
}
|
|
39425
|
+
return false;
|
|
39426
|
+
};
|
|
39427
|
+
//#endregion
|
|
40159
39428
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
40160
39429
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
40161
39430
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -40308,7 +39577,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
40308
39577
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
40309
39578
|
if (!binding) return false;
|
|
40310
39579
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
40311
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction
|
|
39580
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40312
39581
|
return false;
|
|
40313
39582
|
};
|
|
40314
39583
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -42225,9 +41494,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
42225
41494
|
return isReturnedFromEffect;
|
|
42226
41495
|
};
|
|
42227
41496
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
42228
|
-
let functionNode = findEnclosingFunction
|
|
41497
|
+
let functionNode = findEnclosingFunction(node);
|
|
42229
41498
|
while (functionNode) {
|
|
42230
|
-
const outerFunction = findEnclosingFunction
|
|
41499
|
+
const outerFunction = findEnclosingFunction(functionNode);
|
|
42231
41500
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
42232
41501
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
42233
41502
|
functionNode = outerFunction;
|
|
@@ -42235,7 +41504,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
42235
41504
|
return false;
|
|
42236
41505
|
};
|
|
42237
41506
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
42238
|
-
const enclosingFunction = findEnclosingFunction
|
|
41507
|
+
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42239
41508
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
42240
41509
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42241
41510
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -43065,7 +42334,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
43065
42334
|
return false;
|
|
43066
42335
|
};
|
|
43067
42336
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
43068
|
-
const enclosingFunction = findEnclosingFunction
|
|
42337
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
43069
42338
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
43070
42339
|
let currentNode = node;
|
|
43071
42340
|
while (currentNode !== enclosingFunction) {
|
|
@@ -44332,11 +43601,7 @@ const resolveSettings$8 = (settings) => {
|
|
|
44332
43601
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
44333
43602
|
};
|
|
44334
43603
|
};
|
|
44335
|
-
const
|
|
44336
|
-
allowGlobalReactNamespace: true,
|
|
44337
|
-
resolveNamedAliases: true
|
|
44338
|
-
});
|
|
44339
|
-
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43604
|
+
const expressionContainsJsxOrCreateElement = (root) => {
|
|
44340
43605
|
let found = false;
|
|
44341
43606
|
walkAst(root, (node) => {
|
|
44342
43607
|
if (found) return false;
|
|
@@ -44345,17 +43610,17 @@ const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
|
44345
43610
|
found = true;
|
|
44346
43611
|
return false;
|
|
44347
43612
|
}
|
|
44348
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
43613
|
+
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
44349
43614
|
found = true;
|
|
44350
43615
|
return false;
|
|
44351
43616
|
}
|
|
44352
43617
|
});
|
|
44353
43618
|
return found;
|
|
44354
43619
|
};
|
|
44355
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode
|
|
44356
|
-
const isReactClassComponent = (classNode
|
|
43620
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43621
|
+
const isReactClassComponent = (classNode) => {
|
|
44357
43622
|
if (isEs6Component(classNode)) return true;
|
|
44358
|
-
return expressionContainsJsxOrCreateElement(classNode
|
|
43623
|
+
return expressionContainsJsxOrCreateElement(classNode);
|
|
44359
43624
|
};
|
|
44360
43625
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
44361
43626
|
let walker = node.parent;
|
|
@@ -44372,7 +43637,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
44372
43637
|
};
|
|
44373
43638
|
}
|
|
44374
43639
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
44375
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker
|
|
43640
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44376
43641
|
component: walker,
|
|
44377
43642
|
name: walker.id.name
|
|
44378
43643
|
};
|
|
@@ -44461,12 +43726,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
44461
43726
|
}
|
|
44462
43727
|
return false;
|
|
44463
43728
|
};
|
|
44464
|
-
const hocCallContainsComponent = (call
|
|
43729
|
+
const hocCallContainsComponent = (call) => {
|
|
44465
43730
|
const firstArgument = call.arguments[0];
|
|
44466
43731
|
if (!firstArgument) return false;
|
|
44467
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument
|
|
44468
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument
|
|
44469
|
-
return expressionContainsJsxOrCreateElement(firstArgument
|
|
43732
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43733
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43734
|
+
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
44470
43735
|
};
|
|
44471
43736
|
const isFirstArgumentOfHocCall = (node) => {
|
|
44472
43737
|
const parent = node.parent;
|
|
@@ -44499,35 +43764,19 @@ const isReturnOfMapCallback = (node) => {
|
|
|
44499
43764
|
}
|
|
44500
43765
|
return false;
|
|
44501
43766
|
};
|
|
43767
|
+
const isCloneElementCall = (node) => {
|
|
43768
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43769
|
+
const callee = node.callee;
|
|
43770
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43771
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43772
|
+
};
|
|
44502
43773
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
44503
43774
|
"TSAsExpression",
|
|
44504
43775
|
"TSNonNullExpression",
|
|
44505
43776
|
"TSSatisfiesExpression",
|
|
44506
43777
|
"TSTypeAssertion"
|
|
44507
43778
|
]);
|
|
44508
|
-
const
|
|
44509
|
-
"as",
|
|
44510
|
-
"body",
|
|
44511
|
-
"calendarcontainer",
|
|
44512
|
-
"component",
|
|
44513
|
-
"fallback",
|
|
44514
|
-
"tooltip"
|
|
44515
|
-
]);
|
|
44516
|
-
const isElementTypeJsxAttribute = (node) => {
|
|
44517
|
-
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44518
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44519
|
-
const attributeName = node.name.name;
|
|
44520
|
-
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44521
|
-
};
|
|
44522
|
-
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44523
|
-
allowGlobalReactNamespace: true,
|
|
44524
|
-
resolveNamedAliases: true
|
|
44525
|
-
});
|
|
44526
|
-
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44527
|
-
allowGlobalReactNamespace: true,
|
|
44528
|
-
resolveNamedAliases: true
|
|
44529
|
-
});
|
|
44530
|
-
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43779
|
+
const isRenderFlowingReadReference = (identifier) => {
|
|
44531
43780
|
let valueNode = identifier;
|
|
44532
43781
|
let parent = valueNode.parent;
|
|
44533
43782
|
while (parent) {
|
|
@@ -44537,26 +43786,20 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
44537
43786
|
continue;
|
|
44538
43787
|
}
|
|
44539
43788
|
switch (parent.type) {
|
|
44540
|
-
case "
|
|
44541
|
-
case "
|
|
44542
|
-
case "
|
|
44543
|
-
case "ArrowFunctionExpression": return false;
|
|
43789
|
+
case "JSXExpressionContainer":
|
|
43790
|
+
case "ReturnStatement": return true;
|
|
43791
|
+
case "ArrowFunctionExpression": return parent.body === valueNode;
|
|
44544
43792
|
case "CallExpression":
|
|
44545
43793
|
if (parent.callee === valueNode) return false;
|
|
44546
|
-
if (
|
|
44547
|
-
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43794
|
+
if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
|
|
44548
43795
|
valueNode = parent;
|
|
44549
43796
|
parent = parent.parent;
|
|
44550
43797
|
continue;
|
|
44551
|
-
case "VariableDeclarator":
|
|
44552
|
-
|
|
44553
|
-
const
|
|
44554
|
-
|
|
44555
|
-
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44556
|
-
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44557
|
-
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43798
|
+
case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
|
|
43799
|
+
case "AssignmentExpression": {
|
|
43800
|
+
const assignmentTarget = parent.left;
|
|
43801
|
+
return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
|
|
44558
43802
|
}
|
|
44559
|
-
case "AssignmentExpression": return false;
|
|
44560
43803
|
case "Property":
|
|
44561
43804
|
if (parent.value !== valueNode) return false;
|
|
44562
43805
|
valueNode = parent;
|
|
@@ -44594,7 +43837,6 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44594
43837
|
severity: "warn",
|
|
44595
43838
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
44596
43839
|
category: "Performance",
|
|
44597
|
-
tags: ["react-jsx-only"],
|
|
44598
43840
|
create: (context) => {
|
|
44599
43841
|
const settings = resolveSettings$8(context.settings);
|
|
44600
43842
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44657,7 +43899,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44657
43899
|
},
|
|
44658
43900
|
Identifier(node) {
|
|
44659
43901
|
if (!isReactComponentName(node.name)) return;
|
|
44660
|
-
if (!isRenderFlowingReadReference(node
|
|
43902
|
+
if (!isRenderFlowingReadReference(node)) return;
|
|
44661
43903
|
recordInstantiation(node, node.name);
|
|
44662
43904
|
},
|
|
44663
43905
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -44666,33 +43908,28 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44666
43908
|
ClassDeclaration(node) {
|
|
44667
43909
|
if (!node.id) return;
|
|
44668
43910
|
if (!isReactComponentName(node.id.name)) return;
|
|
44669
|
-
if (!isReactClassComponent(node
|
|
43911
|
+
if (!isReactClassComponent(node)) return;
|
|
44670
43912
|
enqueueCandidate(node, null);
|
|
44671
43913
|
},
|
|
44672
43914
|
ClassExpression(node) {
|
|
44673
43915
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
44674
43916
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
44675
|
-
if (!isReactClassComponent(node
|
|
43917
|
+
if (!isReactClassComponent(node)) return;
|
|
44676
43918
|
enqueueCandidate(node, null);
|
|
44677
43919
|
},
|
|
44678
43920
|
CallExpression(node) {
|
|
44679
|
-
if (
|
|
43921
|
+
if (isCreateElementCall(node)) {
|
|
44680
43922
|
const firstArgument = node.arguments[0];
|
|
44681
43923
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
44682
43924
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
44683
43925
|
}
|
|
44684
|
-
|
|
44685
|
-
if (!
|
|
44686
|
-
|
|
44687
|
-
const inferredName = inferFunctionLikeName(node);
|
|
44688
|
-
const propInfo = isComponentDeclaredInProp(node);
|
|
44689
|
-
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44690
|
-
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43926
|
+
if (!isHocCallee$1(node)) return;
|
|
43927
|
+
if (!hocCallContainsComponent(node)) return;
|
|
43928
|
+
enqueueCandidate(node, null);
|
|
44691
43929
|
},
|
|
44692
43930
|
"Program:exit"() {
|
|
44693
43931
|
for (const report of queuedReports) {
|
|
44694
43932
|
if (report.requiredInstantiationName !== null) {
|
|
44695
|
-
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
44696
43933
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
44697
43934
|
}
|
|
44698
43935
|
context.report({
|
|
@@ -45725,7 +44962,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
45725
44962
|
const symbol = scopes.symbolFor(identifier);
|
|
45726
44963
|
if (symbol) {
|
|
45727
44964
|
if (symbol.kind !== "parameter") return null;
|
|
45728
|
-
const declaringFunction = findEnclosingFunction
|
|
44965
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45729
44966
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
45730
44967
|
return null;
|
|
45731
44968
|
}
|
|
@@ -45771,7 +45008,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45771
45008
|
const symbol = scopes.symbolFor(propsObject);
|
|
45772
45009
|
if (symbol) {
|
|
45773
45010
|
if (symbol.kind !== "parameter") return false;
|
|
45774
|
-
const declaringFunction = findEnclosingFunction
|
|
45011
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45775
45012
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45776
45013
|
}
|
|
45777
45014
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -46423,7 +45660,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
46423
45660
|
//#endregion
|
|
46424
45661
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
46425
45662
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
46426
|
-
const functionNode = findEnclosingFunction
|
|
45663
|
+
const functionNode = findEnclosingFunction(startNode);
|
|
46427
45664
|
if (!functionNode) return null;
|
|
46428
45665
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
46429
45666
|
if (!displayName) return null;
|
|
@@ -47485,7 +46722,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
47485
46722
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
47486
46723
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
47487
46724
|
const objectParent = objectExpression.parent;
|
|
47488
|
-
const enclosingFunction = findEnclosingFunction
|
|
46725
|
+
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47489
46726
|
if (!enclosingFunction) return false;
|
|
47490
46727
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
47491
46728
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47863,10 +47100,10 @@ const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
|
47863
47100
|
return hasSuspension;
|
|
47864
47101
|
};
|
|
47865
47102
|
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47866
|
-
let enclosingFunction = findEnclosingFunction
|
|
47103
|
+
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47867
47104
|
while (enclosingFunction) {
|
|
47868
47105
|
if (enclosingFunction === ancestor) return true;
|
|
47869
|
-
enclosingFunction = findEnclosingFunction
|
|
47106
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47870
47107
|
}
|
|
47871
47108
|
return false;
|
|
47872
47109
|
};
|
|
@@ -47888,7 +47125,7 @@ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedF
|
|
|
47888
47125
|
return invokesTarget;
|
|
47889
47126
|
};
|
|
47890
47127
|
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47891
|
-
const boundaryFunction = findEnclosingFunction
|
|
47128
|
+
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47892
47129
|
const boundaryStart = getRangeStart(boundary);
|
|
47893
47130
|
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47894
47131
|
let isInvokedBefore = false;
|
|
@@ -47925,11 +47162,11 @@ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, conte
|
|
|
47925
47162
|
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47926
47163
|
const writeStart = getRangeStart(writeNode);
|
|
47927
47164
|
const boundaryStart = getRangeStart(boundary);
|
|
47928
|
-
const writeFunction = findEnclosingFunction
|
|
47165
|
+
const writeFunction = findEnclosingFunction(writeNode);
|
|
47929
47166
|
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47930
47167
|
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47931
|
-
const boundaryFunction = findEnclosingFunction
|
|
47932
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction
|
|
47168
|
+
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47169
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47933
47170
|
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47934
47171
|
if (!writeFunction) return writeStart < boundaryStart;
|
|
47935
47172
|
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
@@ -48201,8 +47438,8 @@ const queryStableQueryClient = defineRule({
|
|
|
48201
47438
|
create: (context) => ({ NewExpression(node) {
|
|
48202
47439
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
48203
47440
|
if (isStableHookWrapperArgument(node)) return;
|
|
48204
|
-
let enclosingFunction = findEnclosingFunction
|
|
48205
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
47441
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
47442
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48206
47443
|
if (!enclosingFunction) return;
|
|
48207
47444
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
48208
47445
|
context.report({
|
|
@@ -48297,7 +47534,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
48297
47534
|
const comparatorArgument = node.arguments?.[1];
|
|
48298
47535
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
48299
47536
|
} else {
|
|
48300
|
-
const enclosingFunction = findEnclosingFunction
|
|
47537
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
48301
47538
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
48302
47539
|
}
|
|
48303
47540
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -51402,7 +50639,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
51402
50639
|
};
|
|
51403
50640
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
51404
50641
|
const isInsideStyleFactoryCallback = (node) => {
|
|
51405
|
-
const enclosingFunction = findEnclosingFunction
|
|
50642
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
51406
50643
|
if (!enclosingFunction) return false;
|
|
51407
50644
|
const callExpression = enclosingFunction.parent;
|
|
51408
50645
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -58258,14 +57495,12 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
58258
57495
|
}
|
|
58259
57496
|
return false;
|
|
58260
57497
|
};
|
|
58261
|
-
const declarationAwaitsGate = (declaration
|
|
57498
|
+
const declarationAwaitsGate = (declaration) => {
|
|
58262
57499
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
58263
57500
|
for (const declarator of declaration.declarations ?? []) {
|
|
58264
57501
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
58265
57502
|
const argument = declarator.init.argument;
|
|
58266
57503
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58267
|
-
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58268
|
-
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
58269
57504
|
const calleeName = getCalleeName$2(argument);
|
|
58270
57505
|
if (!calleeName) continue;
|
|
58271
57506
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -58293,7 +57528,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
58293
57528
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
58294
57529
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
58295
57530
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
58296
|
-
if (declarationAwaitsGate(currentStatement
|
|
57531
|
+
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58297
57532
|
context.report({
|
|
58298
57533
|
node: nextStatement,
|
|
58299
57534
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -58383,26 +57618,6 @@ const stateInConstructor = defineRule({
|
|
|
58383
57618
|
}
|
|
58384
57619
|
});
|
|
58385
57620
|
//#endregion
|
|
58386
|
-
//#region src/plugin/utils/collect-jsx-runtime-imports.ts
|
|
58387
|
-
const REACT_RUNTIME_PACKAGE_PREFIXES = ["react", "react-dom"];
|
|
58388
|
-
const matchesPackage = (source, packageName) => source === packageName || source.startsWith(`${packageName}/`);
|
|
58389
|
-
const collectJsxRuntimeImports = (program) => {
|
|
58390
|
-
let hasReactRuntime = false;
|
|
58391
|
-
let hasSolidRuntime = false;
|
|
58392
|
-
for (const statement of program.body) {
|
|
58393
|
-
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
58394
|
-
if (isTypeOnlyImport(statement)) continue;
|
|
58395
|
-
const source = statement.source.value;
|
|
58396
|
-
if (typeof source !== "string") continue;
|
|
58397
|
-
if (matchesPackage(source, "solid-js")) hasSolidRuntime = true;
|
|
58398
|
-
if (REACT_RUNTIME_PACKAGE_PREFIXES.some((packageName) => matchesPackage(source, packageName))) hasReactRuntime = true;
|
|
58399
|
-
}
|
|
58400
|
-
return {
|
|
58401
|
-
hasReactRuntime,
|
|
58402
|
-
hasSolidRuntime
|
|
58403
|
-
};
|
|
58404
|
-
};
|
|
58405
|
-
//#endregion
|
|
58406
57621
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
58407
57622
|
const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
58408
57623
|
const resolveSettings = (settings) => {
|
|
@@ -58464,13 +57679,6 @@ const isInvalidStyleExpression = (expression) => {
|
|
|
58464
57679
|
}
|
|
58465
57680
|
return false;
|
|
58466
57681
|
};
|
|
58467
|
-
const hasObjectValuedClassList = (openingElement) => openingElement.attributes.some((attribute) => {
|
|
58468
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return false;
|
|
58469
|
-
if (!isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
58470
|
-
if (attribute.name.name !== "classList") return false;
|
|
58471
|
-
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
58472
|
-
return isNodeOfType(attribute.value.expression, "ObjectExpression");
|
|
58473
|
-
});
|
|
58474
57682
|
const stylePropObject = defineRule({
|
|
58475
57683
|
id: "style-prop-object",
|
|
58476
57684
|
title: "Style prop is not an object",
|
|
@@ -58480,21 +57688,8 @@ const stylePropObject = defineRule({
|
|
|
58480
57688
|
create: (context) => {
|
|
58481
57689
|
const { allow } = resolveSettings(context.settings);
|
|
58482
57690
|
const allowSet = new Set(allow);
|
|
58483
|
-
let fileIsProvenSolidJsx = false;
|
|
58484
57691
|
return {
|
|
58485
|
-
Program: (node) => {
|
|
58486
|
-
const runtimeImports = collectJsxRuntimeImports(node);
|
|
58487
|
-
let hasSolidSyntaxMarker = false;
|
|
58488
|
-
if (!runtimeImports.hasReactRuntime && !runtimeImports.hasSolidRuntime) walkAst(node, (descendantNode) => {
|
|
58489
|
-
if (isNodeOfType(descendantNode, "JSXOpeningElement") && hasObjectValuedClassList(descendantNode)) {
|
|
58490
|
-
hasSolidSyntaxMarker = true;
|
|
58491
|
-
return false;
|
|
58492
|
-
}
|
|
58493
|
-
});
|
|
58494
|
-
fileIsProvenSolidJsx = !runtimeImports.hasReactRuntime && (runtimeImports.hasSolidRuntime || hasSolidSyntaxMarker);
|
|
58495
|
-
},
|
|
58496
57692
|
JSXOpeningElement(node) {
|
|
58497
|
-
if (fileIsProvenSolidJsx) return;
|
|
58498
57693
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
58499
57694
|
const elementName = resolveJsxElementType(node);
|
|
58500
57695
|
if (elementName && allowSet.has(elementName)) return;
|
|
@@ -58525,7 +57720,6 @@ const stylePropObject = defineRule({
|
|
|
58525
57720
|
}
|
|
58526
57721
|
},
|
|
58527
57722
|
CallExpression(node) {
|
|
58528
|
-
if (fileIsProvenSolidJsx) return;
|
|
58529
57723
|
if (!isCreateElementCall(node)) return;
|
|
58530
57724
|
const firstArgument = node.arguments[0];
|
|
58531
57725
|
if (!firstArgument) return;
|
|
@@ -59320,7 +58514,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59320
58514
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
59321
58515
|
const parent = functionNode.parent;
|
|
59322
58516
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
59323
|
-
const outerFunction = findEnclosingFunction
|
|
58517
|
+
const outerFunction = findEnclosingFunction(parent);
|
|
59324
58518
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
59325
58519
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
59326
58520
|
}
|
|
@@ -59337,13 +58531,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59337
58531
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
59338
58532
|
};
|
|
59339
58533
|
const isDeferredNavigateCall = (callNode) => {
|
|
59340
|
-
let enclosingFunction = findEnclosingFunction
|
|
58534
|
+
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59341
58535
|
while (enclosingFunction) {
|
|
59342
58536
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
59343
58537
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
59344
58538
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
59345
58539
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
59346
|
-
enclosingFunction = findEnclosingFunction
|
|
58540
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59347
58541
|
}
|
|
59348
58542
|
return false;
|
|
59349
58543
|
};
|