oxlint-plugin-react-doctor 0.7.7-dev.9fc93f8 → 0.7.7-dev.a468589
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 +1349 -554
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ 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
|
|
10
17
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
18
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
12
19
|
"solid-js",
|
|
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
21
28
|
"vidode"
|
|
22
29
|
]);
|
|
23
30
|
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
|
+
];
|
|
24
36
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
25
|
-
const
|
|
37
|
+
const collectJsxRuntimeImports$1 = (program) => {
|
|
38
|
+
let hasNonReactRuntime = false;
|
|
39
|
+
let hasReactRuntime = false;
|
|
26
40
|
for (const statement of program.body) {
|
|
27
41
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
28
|
-
const
|
|
42
|
+
const importDeclaration = statement;
|
|
43
|
+
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
+
const source = importDeclaration.source;
|
|
29
45
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
30
46
|
if (!value) continue;
|
|
31
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value))
|
|
32
|
-
if (startsWithAny(value,
|
|
47
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
|
|
48
|
+
if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
|
|
33
49
|
}
|
|
34
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
hasNonReactRuntime,
|
|
52
|
+
hasReactRuntime
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
+
const runtimeImports = collectJsxRuntimeImports$1(program);
|
|
57
|
+
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
35
58
|
};
|
|
36
59
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
37
60
|
for (const attribute of openingNode.attributes) {
|
|
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
244
267
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
245
268
|
const innerVisitors = create(context);
|
|
246
269
|
let fileIsNonReactJsx = false;
|
|
270
|
+
let fileImportsReactRuntime = false;
|
|
247
271
|
const wrappedVisitors = {};
|
|
248
272
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
249
273
|
if (typeof visitor !== "function") {
|
|
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
256
280
|
}
|
|
257
281
|
if (key === "Program") {
|
|
258
282
|
wrappedVisitors.Program = (node) => {
|
|
259
|
-
|
|
283
|
+
const runtimeImports = collectJsxRuntimeImports$1(node);
|
|
284
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
260
286
|
visitor(node);
|
|
261
287
|
};
|
|
262
288
|
continue;
|
|
263
289
|
}
|
|
264
290
|
if (key === "JSXOpeningElement") {
|
|
265
291
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
266
|
-
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
292
|
+
if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
267
293
|
if (fileIsNonReactJsx) return;
|
|
268
294
|
visitor(node);
|
|
269
295
|
};
|
|
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
275
301
|
};
|
|
276
302
|
}
|
|
277
303
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
278
|
-
|
|
304
|
+
const runtimeImports = collectJsxRuntimeImports$1(node);
|
|
305
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
279
307
|
};
|
|
280
308
|
return wrappedVisitors;
|
|
281
309
|
});
|
|
@@ -4314,6 +4342,397 @@ const findTransparentExpressionRoot = (node) => {
|
|
|
4314
4342
|
return current;
|
|
4315
4343
|
};
|
|
4316
4344
|
//#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
|
|
4317
4736
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
4318
4737
|
/**
|
|
4319
4738
|
* Type-guard for the two "inline function expression" ESTree forms:
|
|
@@ -4347,13 +4766,15 @@ const isIntentionalSequencingCallee = (callee) => {
|
|
|
4347
4766
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
|
|
4348
4767
|
return false;
|
|
4349
4768
|
};
|
|
4350
|
-
const isAwaitingSleepLikeCall = (awaitNode) => {
|
|
4769
|
+
const isAwaitingSleepLikeCall = (awaitNode, context) => {
|
|
4351
4770
|
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
4352
4771
|
const argument = awaitNode.argument;
|
|
4353
4772
|
if (!argument) return false;
|
|
4354
4773
|
if (!isNodeOfType(argument, "CallExpression")) return false;
|
|
4774
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
|
|
4355
4775
|
return isIntentionalSequencingCallee(argument.callee);
|
|
4356
4776
|
};
|
|
4777
|
+
const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
|
|
4357
4778
|
const PROMISE_CONCURRENCY_METHODS = new Set([
|
|
4358
4779
|
"all",
|
|
4359
4780
|
"allSettled",
|
|
@@ -4396,7 +4817,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
4396
4817
|
});
|
|
4397
4818
|
return isWaitLike;
|
|
4398
4819
|
};
|
|
4399
|
-
const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4820
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4400
4821
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
4401
4822
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
4402
4823
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -4587,12 +5008,12 @@ const getLoopLabelName = (loopNode) => {
|
|
|
4587
5008
|
if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
|
|
4588
5009
|
return null;
|
|
4589
5010
|
};
|
|
4590
|
-
const loopBodyHasIntentionallySequentialAwait = (block) => {
|
|
5011
|
+
const loopBodyHasIntentionallySequentialAwait = (block, context) => {
|
|
4591
5012
|
let foundIntentional = false;
|
|
4592
5013
|
walkAst(block, (child) => {
|
|
4593
5014
|
if (foundIntentional) return false;
|
|
4594
5015
|
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
4595
|
-
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
|
|
5016
|
+
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child, context)) {
|
|
4596
5017
|
foundIntentional = true;
|
|
4597
5018
|
return false;
|
|
4598
5019
|
}
|
|
@@ -4697,7 +5118,7 @@ const asyncAwaitInLoop = defineRule({
|
|
|
4697
5118
|
const inspectLoop = (loopNode, label) => {
|
|
4698
5119
|
const loopBody = loopNode.body;
|
|
4699
5120
|
if (!loopBody) return;
|
|
4700
|
-
if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
|
|
5121
|
+
if (loopBodyHasIntentionallySequentialAwait(loopBody, context)) return;
|
|
4701
5122
|
if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
|
|
4702
5123
|
if (hasLoopCarriedDependency(loopBody)) return;
|
|
4703
5124
|
if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
|
|
@@ -4957,7 +5378,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
|
|
|
4957
5378
|
});
|
|
4958
5379
|
return performsSideEffects;
|
|
4959
5380
|
};
|
|
4960
|
-
const findEnclosingFunction
|
|
5381
|
+
const findEnclosingFunction = (node) => {
|
|
4961
5382
|
let ancestor = node.parent;
|
|
4962
5383
|
while (ancestor) {
|
|
4963
5384
|
if (isFunctionLike$2(ancestor)) return ancestor;
|
|
@@ -4970,7 +5391,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
|
|
|
4970
5391
|
const testIdentifierNames = /* @__PURE__ */ new Set();
|
|
4971
5392
|
collectReferenceIdentifierNames(test, testIdentifierNames);
|
|
4972
5393
|
if (testIdentifierNames.size === 0) return false;
|
|
4973
|
-
const enclosingFunction = findEnclosingFunction
|
|
5394
|
+
const enclosingFunction = findEnclosingFunction(guardStatement);
|
|
4974
5395
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
|
|
4975
5396
|
let readsReassignedLocal = false;
|
|
4976
5397
|
walkAst(enclosingFunction.body, (child) => {
|
|
@@ -5141,19 +5562,36 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
|
|
|
5141
5562
|
return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
|
|
5142
5563
|
};
|
|
5143
5564
|
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
|
+
};
|
|
5144
5574
|
const isNonCallAwait = (statement) => {
|
|
5145
5575
|
const awaitedExpression = getAwaitedCall(statement);
|
|
5146
5576
|
if (!awaitedExpression) return false;
|
|
5147
5577
|
const stripped = stripParenExpression(awaitedExpression);
|
|
5148
5578
|
return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
|
|
5149
5579
|
};
|
|
5150
|
-
const sequenceContainsSerializationSignal = (statements) => {
|
|
5580
|
+
const sequenceContainsSerializationSignal = (statements, context) => {
|
|
5581
|
+
let bareAwaitFunction = null;
|
|
5151
5582
|
for (const statement of statements) {
|
|
5152
|
-
if (isBareExpressionAwait(statement)) return true;
|
|
5153
5583
|
if (isNonCallAwait(statement)) return true;
|
|
5154
5584
|
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
|
+
}
|
|
5155
5593
|
if (isOrderedUiFlowAwait(awaitedCall)) return true;
|
|
5156
|
-
if (isIntentionalSequencingAwait(awaitedCall)) return true;
|
|
5594
|
+
if (isIntentionalSequencingAwait(awaitedCall) && orderIndependentFunction === null) return true;
|
|
5157
5595
|
}
|
|
5158
5596
|
return false;
|
|
5159
5597
|
};
|
|
@@ -5211,7 +5649,7 @@ const asyncParallel = defineRule({
|
|
|
5211
5649
|
const consecutiveAwaitStatements = [];
|
|
5212
5650
|
const flushConsecutiveAwaits = () => {
|
|
5213
5651
|
if (consecutiveAwaitStatements.length >= 3) {
|
|
5214
|
-
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5652
|
+
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements, context)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5215
5653
|
}
|
|
5216
5654
|
consecutiveAwaitStatements.length = 0;
|
|
5217
5655
|
};
|
|
@@ -5989,23 +6427,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5989
6427
|
}
|
|
5990
6428
|
});
|
|
5991
6429
|
//#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
|
|
6009
6430
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
6010
6431
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
6011
6432
|
if (!a || !b) return a === b;
|
|
@@ -6689,15 +7110,6 @@ const getDirectConstInitializer = (symbol) => {
|
|
|
6689
7110
|
return symbol.initializer;
|
|
6690
7111
|
};
|
|
6691
7112
|
//#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
|
|
6701
7113
|
//#region src/plugin/utils/get-range-start.ts
|
|
6702
7114
|
const getRangeStart = (node) => {
|
|
6703
7115
|
const rangeStart = node.range?.[0];
|
|
@@ -7093,16 +7505,6 @@ const collectFunctionReturnStatements = (functionNode) => {
|
|
|
7093
7505
|
return returnStatements;
|
|
7094
7506
|
};
|
|
7095
7507
|
//#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
|
|
7106
7508
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
7107
7509
|
const statementAlwaysExits = (statement) => {
|
|
7108
7510
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -7148,8 +7550,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
|
|
|
7148
7550
|
const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
|
|
7149
7551
|
if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
|
|
7150
7552
|
if (!controlFlow) return [];
|
|
7151
|
-
const referenceFunction = findEnclosingFunction(referenceNode);
|
|
7152
|
-
if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7553
|
+
const referenceFunction = findEnclosingFunction$1(referenceNode);
|
|
7554
|
+
if (findEnclosingFunction$1(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7153
7555
|
if (!referenceFunction) return [];
|
|
7154
7556
|
const functionControlFlow = controlFlow.cfgFor(referenceFunction);
|
|
7155
7557
|
if (!functionControlFlow) return [];
|
|
@@ -7173,7 +7575,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
|
|
|
7173
7575
|
if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
|
|
7174
7576
|
for (const reference of symbol.references) {
|
|
7175
7577
|
const writePosition = getRangeStart(reference.identifier);
|
|
7176
|
-
if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7578
|
+
if (reference.flag === "read" || findEnclosingFunction$1(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7177
7579
|
const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
|
|
7178
7580
|
if (!assignedExpression) continue;
|
|
7179
7581
|
addDefinition(assignedExpression, reference.identifier, writePosition);
|
|
@@ -8845,6 +9247,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
8845
9247
|
const LABEL_ELEMENT = "label";
|
|
8846
9248
|
const LABEL_COMPONENT_NAME = "Label";
|
|
8847
9249
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
9250
|
+
const TITLE_ATTRIBUTE = "title";
|
|
8848
9251
|
const WRAPPER_LABEL_PROP = "label";
|
|
8849
9252
|
const SELECT_ELEMENT = "select";
|
|
8850
9253
|
const DEFAULT_DEPTH = 5;
|
|
@@ -8890,6 +9293,96 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
8890
9293
|
}
|
|
8891
9294
|
return true;
|
|
8892
9295
|
};
|
|
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
|
+
};
|
|
8893
9386
|
const toAttributeMatchKey = (kind, value) => {
|
|
8894
9387
|
const trimmedValue = value.trim();
|
|
8895
9388
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9146,6 +9639,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9146
9639
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9147
9640
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9148
9641
|
}
|
|
9642
|
+
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9149
9643
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9150
9644
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9151
9645
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -11095,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11095
11589
|
//#endregion
|
|
11096
11590
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11097
11591
|
const enclosingComponentOrHookName = (node) => {
|
|
11098
|
-
const functionNode = findEnclosingFunction(node);
|
|
11592
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
11099
11593
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11100
11594
|
};
|
|
11101
11595
|
//#endregion
|
|
@@ -11152,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11152
11646
|
//#endregion
|
|
11153
11647
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11154
11648
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11155
|
-
let functionNode = findEnclosingFunction(node);
|
|
11649
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
11156
11650
|
while (functionNode) {
|
|
11157
11651
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11158
11652
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11159
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
11653
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
11160
11654
|
}
|
|
11161
11655
|
return null;
|
|
11162
11656
|
};
|
|
@@ -11405,13 +11899,13 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11405
11899
|
};
|
|
11406
11900
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11407
11901
|
let pathAnchor = usageNode;
|
|
11408
|
-
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11902
|
+
let pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11409
11903
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11410
11904
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11411
11905
|
const iteratorCall = pathOwner.parent;
|
|
11412
11906
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11413
11907
|
pathAnchor = iteratorCall;
|
|
11414
|
-
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11908
|
+
pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11415
11909
|
}
|
|
11416
11910
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11417
11911
|
if (!owner) return false;
|
|
@@ -11640,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
11640
12134
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
11641
12135
|
};
|
|
11642
12136
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
11643
|
-
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12137
|
+
const callbackNode = findEnclosingFunction$1(resourceNode);
|
|
11644
12138
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
11645
12139
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
11646
12140
|
if (!mappingCall) return null;
|
|
@@ -11751,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
11751
12245
|
});
|
|
11752
12246
|
if (invocationCalls.length !== 1) return null;
|
|
11753
12247
|
const invocationCall = invocationCalls[0];
|
|
11754
|
-
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12248
|
+
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
11755
12249
|
};
|
|
11756
12250
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
11757
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
12251
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
11758
12252
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
11759
12253
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
11760
12254
|
};
|
|
@@ -11769,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
11769
12263
|
const assignmentReference = assignmentReferences[0];
|
|
11770
12264
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
11771
12265
|
const assignmentNode = assignmentTarget.parent;
|
|
11772
|
-
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
12266
|
+
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction$1(assignmentNode) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
11773
12267
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
11774
12268
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
11775
12269
|
};
|
|
@@ -11858,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
11858
12352
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
11859
12353
|
};
|
|
11860
12354
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
11861
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
12355
|
+
const componentFunction = findEnclosingFunction$1(callback);
|
|
11862
12356
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
11863
12357
|
let didFindUnmountCleanup = false;
|
|
11864
12358
|
walkAst(componentFunction.body, (child) => {
|
|
11865
12359
|
if (didFindUnmountCleanup) return false;
|
|
11866
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12360
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
11867
12361
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
11868
12362
|
const dependencyList = child.arguments?.[1];
|
|
11869
12363
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -11959,7 +12453,7 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
11959
12453
|
return didWriteGuard;
|
|
11960
12454
|
};
|
|
11961
12455
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11962
|
-
const usageFunction = findEnclosingFunction(usage.node);
|
|
12456
|
+
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
11963
12457
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11964
12458
|
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11965
12459
|
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
@@ -11967,7 +12461,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
11967
12461
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11968
12462
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11969
12463
|
if (callback.async) return false;
|
|
11970
|
-
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
12464
|
+
if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
11971
12465
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
11972
12466
|
const matchingCleanupReturns = [];
|
|
11973
12467
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -12136,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12136
12630
|
parentNode = currentNode.parent;
|
|
12137
12631
|
}
|
|
12138
12632
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12139
|
-
const effectCallback = findEnclosingFunction(parentNode);
|
|
12633
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
12140
12634
|
const effectCall = effectCallback?.parent;
|
|
12141
12635
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12142
12636
|
};
|
|
@@ -12146,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12146
12640
|
if (!bindingIdentifier) return false;
|
|
12147
12641
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12148
12642
|
if (!symbol) return false;
|
|
12149
|
-
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12643
|
+
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
12150
12644
|
};
|
|
12151
12645
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12152
12646
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12153
|
-
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12647
|
+
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
12154
12648
|
if (!releaseFunction) return true;
|
|
12155
|
-
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12649
|
+
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
12156
12650
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12157
12651
|
};
|
|
12158
12652
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12335,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12335
12829
|
}
|
|
12336
12830
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12337
12831
|
const assignedValue = stripParenExpression(child.right);
|
|
12338
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12832
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
|
|
12339
12833
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12340
12834
|
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);
|
|
12341
|
-
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12835
|
+
if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12342
12836
|
hasUnsafeRefWrite = true;
|
|
12343
12837
|
return false;
|
|
12344
12838
|
}
|
|
@@ -12359,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12359
12853
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12360
12854
|
};
|
|
12361
12855
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12362
|
-
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12856
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
12363
12857
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12364
12858
|
let didFindCleanupEffect = false;
|
|
12365
12859
|
walkAst(componentFunction.body, (child) => {
|
|
12366
12860
|
if (didFindCleanupEffect) return false;
|
|
12367
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12861
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12368
12862
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12369
12863
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12370
12864
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13218,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13218
13712
|
};
|
|
13219
13713
|
};
|
|
13220
13714
|
//#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
|
|
13221
13911
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13222
13912
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13223
13913
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13289,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13289
13979
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13290
13980
|
};
|
|
13291
13981
|
//#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
|
|
13311
13982
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13312
13983
|
/**
|
|
13313
13984
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -13488,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
13488
14159
|
"useLayoutEffect",
|
|
13489
14160
|
"useInsertionEffect"
|
|
13490
14161
|
]);
|
|
14162
|
+
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
13491
14163
|
const buildAdditionalHooksRegex = (additional) => {
|
|
13492
14164
|
if (!additional) return null;
|
|
13493
14165
|
try {
|
|
@@ -13624,7 +14296,7 @@ const stringifyMemberChain = (node) => {
|
|
|
13624
14296
|
}
|
|
13625
14297
|
return null;
|
|
13626
14298
|
};
|
|
13627
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14299
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
|
|
13628
14300
|
const keys = /* @__PURE__ */ new Set();
|
|
13629
14301
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
13630
14302
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -13635,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
|
13635
14307
|
const symbol = reference.resolvedSymbol;
|
|
13636
14308
|
if (!symbol) continue;
|
|
13637
14309
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
+
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
+
stableCapturedNames.add(symbol.name);
|
|
14312
|
+
continue;
|
|
14313
|
+
}
|
|
13638
14314
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
13639
14315
|
stableCapturedNames.add(symbol.name);
|
|
13640
14316
|
continue;
|
|
@@ -14307,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14307
14983
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14308
14984
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14309
14985
|
}
|
|
14310
|
-
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
|
|
14986
|
+
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys, SOLE_WRITER_GUARD_HOOKS.has(hookName));
|
|
14311
14987
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14312
14988
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14313
14989
|
const missingCaptureKeys = [];
|
|
@@ -16121,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16121
16797
|
if (didFindRenderPathInvocation) return false;
|
|
16122
16798
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16123
16799
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16124
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
16800
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
16125
16801
|
while (enclosingFunction) {
|
|
16126
16802
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16127
16803
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16128
16804
|
didFindRenderPathInvocation = true;
|
|
16129
16805
|
return;
|
|
16130
16806
|
}
|
|
16131
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16807
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
16132
16808
|
}
|
|
16133
16809
|
});
|
|
16134
16810
|
return didFindRenderPathInvocation;
|
|
@@ -16163,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16163
16839
|
};
|
|
16164
16840
|
return { CallExpression(node) {
|
|
16165
16841
|
if (!isImportedSelectAtom(node)) return;
|
|
16166
|
-
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16842
|
+
const nearestFunctionLike = findEnclosingFunction$1(node);
|
|
16167
16843
|
if (!nearestFunctionLike) return;
|
|
16168
16844
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16169
16845
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17160,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17160
17836
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17161
17837
|
};
|
|
17162
17838
|
const isInsideCacheMemo = (node) => {
|
|
17163
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17839
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17164
17840
|
let child = node;
|
|
17165
17841
|
let cursor = node.parent ?? null;
|
|
17166
17842
|
while (cursor) {
|
|
@@ -17198,7 +17874,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17198
17874
|
return null;
|
|
17199
17875
|
};
|
|
17200
17876
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17201
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17877
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17202
17878
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17203
17879
|
const functionName = getFunctionName(enclosingFunction);
|
|
17204
17880
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -17389,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
17389
18065
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
17390
18066
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
17391
18067
|
};
|
|
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
|
-
};
|
|
17399
18068
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
17400
18069
|
if (basePath === "global") {
|
|
17401
18070
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -17685,7 +18354,89 @@ const collectEarlierAndGuardOperands = (node) => {
|
|
|
17685
18354
|
return earlierOperands;
|
|
17686
18355
|
};
|
|
17687
18356
|
//#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
|
|
17688
18383
|
//#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
|
+
};
|
|
17689
18440
|
const findIndexedArrayObject = (callbackBody, indexParameterName) => {
|
|
17690
18441
|
let indexedArrayObject = null;
|
|
17691
18442
|
walkAst(callbackBody, (child) => {
|
|
@@ -17900,6 +18651,7 @@ const jsLengthCheckFirst = defineRule({
|
|
|
17900
18651
|
const resolvedReceiverSource = resolveComparedArraySource(receiverArrayObject, node);
|
|
17901
18652
|
const resolvedIndexedSource = resolveComparedArraySource(indexedArrayObject, node);
|
|
17902
18653
|
if (areExpressionsStructurallyEqual(resolvedReceiverSource, resolvedIndexedSource)) return;
|
|
18654
|
+
if (areEqualCardinalityObjectProjections(receiverArrayObject, indexedArrayObject, context) || areEqualCardinalityObjectProjections(resolvedReceiverSource, resolvedIndexedSource, context)) return;
|
|
17903
18655
|
const comparedArrayPairs = [[receiverArrayObject, indexedArrayObject], [resolvedReceiverSource, resolvedIndexedSource]];
|
|
17904
18656
|
if (collectEarlierAndGuardOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_ANY_COMPARISON_OPERATORS)))) return;
|
|
17905
18657
|
if (collectEarlierOrOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_MISMATCH_OPERATORS)))) return;
|
|
@@ -23777,32 +24529,6 @@ const hasDirective = (programNode, directive) => {
|
|
|
23777
24529
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
23778
24530
|
};
|
|
23779
24531
|
//#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
|
|
23806
24532
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
23807
24533
|
const nextjsAsyncClientComponent = defineRule({
|
|
23808
24534
|
id: "nextjs-async-client-component",
|
|
@@ -26142,7 +26868,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26142
26868
|
};
|
|
26143
26869
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26144
26870
|
if (!setterRef.resolved) return false;
|
|
26145
|
-
const componentFunction = findEnclosingFunction(effectNode);
|
|
26871
|
+
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
26146
26872
|
if (!componentFunction) return false;
|
|
26147
26873
|
for (const reference of setterRef.resolved.references) {
|
|
26148
26874
|
if (reference.init) continue;
|
|
@@ -28915,138 +29641,6 @@ const noBarrelImport = defineRule({
|
|
|
28915
29641
|
}
|
|
28916
29642
|
});
|
|
28917
29643
|
//#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
|
|
29050
29644
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29051
29645
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29052
29646
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -29515,8 +30109,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
29515
30109
|
return isReactFunctionCall(parent, "useMemo");
|
|
29516
30110
|
};
|
|
29517
30111
|
const findEnclosingRenderFunction = (node) => {
|
|
29518
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
29519
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30112
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
30113
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
29520
30114
|
return enclosingFunction;
|
|
29521
30115
|
};
|
|
29522
30116
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -30567,7 +31161,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
30567
31161
|
const isEffectDrivenResync = (useStateCall) => {
|
|
30568
31162
|
const setterName = getStateSetterName(useStateCall);
|
|
30569
31163
|
if (!setterName) return false;
|
|
30570
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31164
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30571
31165
|
if (!componentFunction) return false;
|
|
30572
31166
|
let isExempt = false;
|
|
30573
31167
|
walkAst(componentFunction, (child) => {
|
|
@@ -30671,7 +31265,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
30671
31265
|
const setterName = getStateSetterName(useStateCall);
|
|
30672
31266
|
if (!setterName) return false;
|
|
30673
31267
|
const stateValueName = getStateValueName(useStateCall);
|
|
30674
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31268
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30675
31269
|
if (!componentFunction) return false;
|
|
30676
31270
|
let isExempt = false;
|
|
30677
31271
|
walkAst(componentFunction, (child) => {
|
|
@@ -30717,7 +31311,7 @@ const noDerivedUseState = defineRule({
|
|
|
30717
31311
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
30718
31312
|
if (isEffectDrivenResync(node)) return;
|
|
30719
31313
|
if (isNextjsDataFetchingPage(node)) return;
|
|
30720
|
-
const componentFunction = findEnclosingFunction(node);
|
|
31314
|
+
const componentFunction = findEnclosingFunction$1(node);
|
|
30721
31315
|
if (componentFunction) {
|
|
30722
31316
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
30723
31317
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -32192,6 +32786,29 @@ const isReturnOnlyStatement = (node) => {
|
|
|
32192
32786
|
return isNodeOfType(node, "BlockStatement") && (node.body?.length ?? 0) === 1 && isNodeOfType(node.body?.[0], "ReturnStatement");
|
|
32193
32787
|
};
|
|
32194
32788
|
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
|
+
};
|
|
32195
32812
|
const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
32196
32813
|
const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
|
|
32197
32814
|
if (!unwrappedDependencyExpression) return false;
|
|
@@ -32199,6 +32816,69 @@ const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
|
32199
32816
|
return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
|
|
32200
32817
|
};
|
|
32201
32818
|
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
|
+
};
|
|
32202
32882
|
const isEqualityToLiteralGuard = (guardExpression) => {
|
|
32203
32883
|
const parent = guardExpression.expression.parent;
|
|
32204
32884
|
if (!isNodeOfType(parent, "BinaryExpression")) return false;
|
|
@@ -32263,18 +32943,35 @@ const noEffectEventHandler = defineRule({
|
|
|
32263
32943
|
if (statements.length === 0) return;
|
|
32264
32944
|
const soleStatement = statements[0];
|
|
32265
32945
|
if (!isNodeOfType(soleStatement, "IfStatement")) return;
|
|
32266
|
-
const
|
|
32267
|
-
collectGuardExpressions(soleStatement.test,
|
|
32268
|
-
const
|
|
32946
|
+
const initialGuardExpressions = [];
|
|
32947
|
+
collectGuardExpressions(soleStatement.test, initialGuardExpressions);
|
|
32948
|
+
const guardExpressions = collectLeadingEarlyReturnGuards(statements);
|
|
32949
|
+
if (guardExpressions.length === 0) guardExpressions.push(...initialGuardExpressions);
|
|
32950
|
+
const matchingPropGuardExpressions = initialGuardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
|
|
32269
32951
|
if (matchingPropGuardExpressions.length === 0) return;
|
|
32270
32952
|
const isSingleGuardedEventLikeStatement = statements.length === 1 && hasEventLikeNode(soleStatement.consequent);
|
|
32271
32953
|
const isEarlyReturnGuardedEventLikeBody = statements.length > 1 && !soleStatement.alternate && isReturnOnlyStatement(soleStatement.consequent) && hasEventLikeRemainingStatements(statements.slice(1));
|
|
32272
32954
|
if (!isSingleGuardedEventLikeStatement && !isEarlyReturnGuardedEventLikeBody) return;
|
|
32273
32955
|
if (isEarlyReturnGuardedEventLikeBody && !isSingleGuardedEventLikeStatement && matchingPropGuardExpressions.every(isEqualityToLiteralGuard)) return;
|
|
32274
|
-
if (
|
|
32956
|
+
if (initialGuardExpressions.some((guardExpression) => !matchingPropGuardExpressions.some((matchingGuardExpression) => matchingGuardExpression.expression === guardExpression.expression))) {
|
|
32275
32957
|
const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32276
32958
|
if (!(isSingleGuardedEventLikeStatement ? doesEventLikeCallReferenceAnyRoot(soleStatement.consequent, matchingPropRootNames) : doesAnyEventLikeCallReferenceAnyRoot(statements.slice(1), matchingPropRootNames))) return;
|
|
32277
32959
|
}
|
|
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
|
+
}
|
|
32278
32975
|
context.report({
|
|
32279
32976
|
node,
|
|
32280
32977
|
message: "This useEffect is simulating an event handler, which costs an extra render & runs late."
|
|
@@ -32555,7 +33252,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
32555
33252
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
32556
33253
|
const parent = identifier.parent;
|
|
32557
33254
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
32558
|
-
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33255
|
+
if (findEnclosingFunction$1(parent) === functionNode) return false;
|
|
32559
33256
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
32560
33257
|
}
|
|
32561
33258
|
return true;
|
|
@@ -32601,7 +33298,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
32601
33298
|
if (!setterVariable) return false;
|
|
32602
33299
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
32603
33300
|
if (!stateDeclarator) return false;
|
|
32604
|
-
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33301
|
+
const componentFunction = findEnclosingFunction$1(stateDeclarator);
|
|
32605
33302
|
if (!componentFunction) return false;
|
|
32606
33303
|
let hasWriter = false;
|
|
32607
33304
|
for (const reference of setterVariable.references) {
|
|
@@ -33663,8 +34360,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
33663
34360
|
return false;
|
|
33664
34361
|
};
|
|
33665
34362
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
33666
|
-
const requestFunction = findEnclosingFunction(request);
|
|
33667
|
-
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34363
|
+
const requestFunction = findEnclosingFunction$1(request);
|
|
34364
|
+
const completionSinkFunction = findEnclosingFunction$1(completionSink);
|
|
33668
34365
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
33669
34366
|
if (requestFunction === completionSinkFunction) {
|
|
33670
34367
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -33951,13 +34648,6 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33951
34648
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33952
34649
|
};
|
|
33953
34650
|
//#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
|
|
33961
34651
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33962
34652
|
const isLibraryDevPage = (filename) => {
|
|
33963
34653
|
if (!filename) return false;
|
|
@@ -34521,7 +35211,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
34521
35211
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
34522
35212
|
}
|
|
34523
35213
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
34524
|
-
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35214
|
+
if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
|
|
34525
35215
|
}
|
|
34526
35216
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
34527
35217
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -34644,7 +35334,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34644
35334
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
34645
35335
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
34646
35336
|
if (!componentOrHookNode) return;
|
|
34647
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
35337
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
34648
35338
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
34649
35339
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
34650
35340
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -35092,10 +35782,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35092
35782
|
return false;
|
|
35093
35783
|
};
|
|
35094
35784
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35095
|
-
let functionNode = findEnclosingFunction(node);
|
|
35785
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
35096
35786
|
while (functionNode) {
|
|
35097
35787
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35098
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
35788
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
35099
35789
|
}
|
|
35100
35790
|
return !isInsideInstanceField(node);
|
|
35101
35791
|
};
|
|
@@ -35613,6 +36303,221 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35613
36303
|
}
|
|
35614
36304
|
});
|
|
35615
36305
|
//#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
|
|
35616
36521
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35617
36522
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35618
36523
|
"childContextTypes",
|
|
@@ -35623,15 +36528,6 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35623
36528
|
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.`;
|
|
35624
36529
|
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.";
|
|
35625
36530
|
};
|
|
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
|
-
};
|
|
35635
36531
|
const noLegacyContextApi = defineRule({
|
|
35636
36532
|
id: "no-legacy-context-api",
|
|
35637
36533
|
title: "Legacy context API",
|
|
@@ -35646,6 +36542,7 @@ const noLegacyContextApi = defineRule({
|
|
|
35646
36542
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35647
36543
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35648
36544
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36545
|
+
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35649
36546
|
context.report({
|
|
35650
36547
|
node: memberNode.key,
|
|
35651
36548
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35653,6 +36550,8 @@ const noLegacyContextApi = defineRule({
|
|
|
35653
36550
|
};
|
|
35654
36551
|
return {
|
|
35655
36552
|
ClassBody(node) {
|
|
36553
|
+
const classNode = node.parent;
|
|
36554
|
+
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35656
36555
|
for (const member of node.body ?? []) checkMember(member);
|
|
35657
36556
|
},
|
|
35658
36557
|
AssignmentExpression(node) {
|
|
@@ -35662,9 +36561,11 @@ const noLegacyContextApi = defineRule({
|
|
|
35662
36561
|
if (left.computed) return;
|
|
35663
36562
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35664
36563
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35665
|
-
if (
|
|
35666
|
-
|
|
35667
|
-
if (
|
|
36564
|
+
if (left.property.name === "getChildContext") return;
|
|
36565
|
+
const component = stripParenExpression(left.object);
|
|
36566
|
+
if (!isNodeOfType(component, "Identifier")) return;
|
|
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;
|
|
35668
36569
|
context.report({
|
|
35669
36570
|
node: left,
|
|
35670
36571
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -35868,10 +36769,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
35868
36769
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
35869
36770
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
35870
36771
|
visitedFunctionNodes.add(functionNode);
|
|
35871
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36772
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35872
36773
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
35873
36774
|
if (immediateCall) {
|
|
35874
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36775
|
+
const immediateCallFunction = findEnclosingFunction$1(immediateCall);
|
|
35875
36776
|
if (immediateCallFunction === usageFunction) {
|
|
35876
36777
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
35877
36778
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -35880,7 +36781,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35880
36781
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
35881
36782
|
}
|
|
35882
36783
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
35883
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36784
|
+
const methodCallFunction = findEnclosingFunction$1(methodCall);
|
|
35884
36785
|
if (methodCallFunction === usageFunction) {
|
|
35885
36786
|
const methodCallStart = getRangeStart(methodCall);
|
|
35886
36787
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -35903,7 +36804,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35903
36804
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
35904
36805
|
const call = getDirectCallForExpression(child);
|
|
35905
36806
|
if (!call) return false;
|
|
35906
|
-
const callFunction = findEnclosingFunction(call);
|
|
36807
|
+
const callFunction = findEnclosingFunction$1(call);
|
|
35907
36808
|
if (callFunction === usageFunction) {
|
|
35908
36809
|
const callStart = getRangeStart(call);
|
|
35909
36810
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -35934,14 +36835,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
35934
36835
|
visitedMutationSymbolIds.add(symbol.id);
|
|
35935
36836
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
35936
36837
|
if (typeof usageBoundary !== "number") return true;
|
|
35937
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36838
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35938
36839
|
return symbol.references.some((reference) => {
|
|
35939
36840
|
const referenceStart = getRangeStart(reference.identifier);
|
|
35940
36841
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
35941
36842
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
35942
36843
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
35943
36844
|
if (referenceStart === null) return true;
|
|
35944
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36845
|
+
const mutationFunction = findEnclosingFunction$1(reference.identifier);
|
|
35945
36846
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
35946
36847
|
if (!mutationFunction) return usageFunction !== null;
|
|
35947
36848
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -36523,13 +37424,6 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
36523
37424
|
return isReactNamespaceImport(node, scopes);
|
|
36524
37425
|
};
|
|
36525
37426
|
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
|
-
};
|
|
36533
37427
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
36534
37428
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
36535
37429
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -36539,7 +37433,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
36539
37433
|
}
|
|
36540
37434
|
const init = symbol.initializer;
|
|
36541
37435
|
if (!init) return false;
|
|
36542
|
-
const destructuredPropertyName =
|
|
37436
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
36543
37437
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
36544
37438
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
36545
37439
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -38293,12 +39187,6 @@ const getDeclarationKind = (declarator) => {
|
|
|
38293
39187
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
38294
39188
|
};
|
|
38295
39189
|
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
|
-
};
|
|
38302
39190
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
38303
39191
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38304
39192
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -38308,7 +39196,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38308
39196
|
if (isProp(analysis, callbackReference)) {
|
|
38309
39197
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
38310
39198
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
38311
|
-
return (bindingIdentifier &&
|
|
39199
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
38312
39200
|
}
|
|
38313
39201
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
38314
39202
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -38318,7 +39206,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38318
39206
|
visitedVariables.add(callbackVariable);
|
|
38319
39207
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
38320
39208
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
38321
|
-
const propertyName = bindingIdentifier ?
|
|
39209
|
+
const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
|
|
38322
39210
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
38323
39211
|
return propertyName && propsReference ? propertyName : null;
|
|
38324
39212
|
}
|
|
@@ -38421,7 +39309,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
38421
39309
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
38422
39310
|
if (!bindingProvenance) return null;
|
|
38423
39311
|
const { refCall, variables } = bindingProvenance;
|
|
38424
|
-
const componentFunction = findEnclosingFunction(effectCall);
|
|
39312
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
38425
39313
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
38426
39314
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
38427
39315
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39257,174 +40145,6 @@ const noPropCallbackInRender = defineRule({
|
|
|
39257
40145
|
} })
|
|
39258
40146
|
});
|
|
39259
40147
|
//#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
|
|
39428
40148
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39429
40149
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39430
40150
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39577,7 +40297,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
39577
40297
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39578
40298
|
if (!binding) return false;
|
|
39579
40299
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
39580
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40300
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
|
|
39581
40301
|
return false;
|
|
39582
40302
|
};
|
|
39583
40303
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -41494,9 +42214,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
41494
42214
|
return isReturnedFromEffect;
|
|
41495
42215
|
};
|
|
41496
42216
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
41497
|
-
let functionNode = findEnclosingFunction(node);
|
|
42217
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
41498
42218
|
while (functionNode) {
|
|
41499
|
-
const outerFunction = findEnclosingFunction(functionNode);
|
|
42219
|
+
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
41500
42220
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
41501
42221
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
41502
42222
|
functionNode = outerFunction;
|
|
@@ -41504,7 +42224,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
41504
42224
|
return false;
|
|
41505
42225
|
};
|
|
41506
42226
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
41507
|
-
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42227
|
+
const enclosingFunction = findEnclosingFunction$1(clearCall);
|
|
41508
42228
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
41509
42229
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41510
42230
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -42334,7 +43054,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
42334
43054
|
return false;
|
|
42335
43055
|
};
|
|
42336
43056
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
42337
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
43057
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
42338
43058
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42339
43059
|
let currentNode = node;
|
|
42340
43060
|
while (currentNode !== enclosingFunction) {
|
|
@@ -43601,7 +44321,11 @@ const resolveSettings$8 = (settings) => {
|
|
|
43601
44321
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43602
44322
|
};
|
|
43603
44323
|
};
|
|
43604
|
-
const
|
|
44324
|
+
const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
|
|
44325
|
+
allowGlobalReactNamespace: true,
|
|
44326
|
+
resolveNamedAliases: true
|
|
44327
|
+
});
|
|
44328
|
+
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43605
44329
|
let found = false;
|
|
43606
44330
|
walkAst(root, (node) => {
|
|
43607
44331
|
if (found) return false;
|
|
@@ -43610,17 +44334,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
43610
44334
|
found = true;
|
|
43611
44335
|
return false;
|
|
43612
44336
|
}
|
|
43613
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
44337
|
+
if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
|
|
43614
44338
|
found = true;
|
|
43615
44339
|
return false;
|
|
43616
44340
|
}
|
|
43617
44341
|
});
|
|
43618
44342
|
return found;
|
|
43619
44343
|
};
|
|
43620
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43621
|
-
const isReactClassComponent = (classNode) => {
|
|
44344
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
|
|
44345
|
+
const isReactClassComponent = (classNode, scopes) => {
|
|
43622
44346
|
if (isEs6Component(classNode)) return true;
|
|
43623
|
-
return expressionContainsJsxOrCreateElement(classNode);
|
|
44347
|
+
return expressionContainsJsxOrCreateElement(classNode, scopes);
|
|
43624
44348
|
};
|
|
43625
44349
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43626
44350
|
let walker = node.parent;
|
|
@@ -43637,7 +44361,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43637
44361
|
};
|
|
43638
44362
|
}
|
|
43639
44363
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43640
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44364
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
|
|
43641
44365
|
component: walker,
|
|
43642
44366
|
name: walker.id.name
|
|
43643
44367
|
};
|
|
@@ -43726,12 +44450,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43726
44450
|
}
|
|
43727
44451
|
return false;
|
|
43728
44452
|
};
|
|
43729
|
-
const hocCallContainsComponent = (call) => {
|
|
44453
|
+
const hocCallContainsComponent = (call, scopes) => {
|
|
43730
44454
|
const firstArgument = call.arguments[0];
|
|
43731
44455
|
if (!firstArgument) return false;
|
|
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);
|
|
44456
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
44457
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
|
|
44458
|
+
return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
43735
44459
|
};
|
|
43736
44460
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43737
44461
|
const parent = node.parent;
|
|
@@ -43764,19 +44488,35 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43764
44488
|
}
|
|
43765
44489
|
return false;
|
|
43766
44490
|
};
|
|
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
|
-
};
|
|
43773
44491
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43774
44492
|
"TSAsExpression",
|
|
43775
44493
|
"TSNonNullExpression",
|
|
43776
44494
|
"TSSatisfiesExpression",
|
|
43777
44495
|
"TSTypeAssertion"
|
|
43778
44496
|
]);
|
|
43779
|
-
const
|
|
44497
|
+
const ELEMENT_TYPE_PROP_NAMES = new Set([
|
|
44498
|
+
"as",
|
|
44499
|
+
"body",
|
|
44500
|
+
"calendarcontainer",
|
|
44501
|
+
"component",
|
|
44502
|
+
"fallback",
|
|
44503
|
+
"tooltip"
|
|
44504
|
+
]);
|
|
44505
|
+
const isElementTypeJsxAttribute = (node) => {
|
|
44506
|
+
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44507
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44508
|
+
const attributeName = node.name.name;
|
|
44509
|
+
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44510
|
+
};
|
|
44511
|
+
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44512
|
+
allowGlobalReactNamespace: true,
|
|
44513
|
+
resolveNamedAliases: true
|
|
44514
|
+
});
|
|
44515
|
+
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44516
|
+
allowGlobalReactNamespace: true,
|
|
44517
|
+
resolveNamedAliases: true
|
|
44518
|
+
});
|
|
44519
|
+
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43780
44520
|
let valueNode = identifier;
|
|
43781
44521
|
let parent = valueNode.parent;
|
|
43782
44522
|
while (parent) {
|
|
@@ -43786,20 +44526,26 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
43786
44526
|
continue;
|
|
43787
44527
|
}
|
|
43788
44528
|
switch (parent.type) {
|
|
43789
|
-
case "
|
|
43790
|
-
case "
|
|
43791
|
-
case "
|
|
44529
|
+
case "JSXOpeningElement": return parent.name === valueNode;
|
|
44530
|
+
case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
|
|
44531
|
+
case "ReturnStatement": return false;
|
|
44532
|
+
case "ArrowFunctionExpression": return false;
|
|
43792
44533
|
case "CallExpression":
|
|
43793
44534
|
if (parent.callee === valueNode) return false;
|
|
43794
|
-
if (
|
|
44535
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
44536
|
+
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43795
44537
|
valueNode = parent;
|
|
43796
44538
|
parent = parent.parent;
|
|
43797
44539
|
continue;
|
|
43798
|
-
case "VariableDeclarator":
|
|
43799
|
-
|
|
43800
|
-
const
|
|
43801
|
-
|
|
44540
|
+
case "VariableDeclarator": {
|
|
44541
|
+
if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
|
|
44542
|
+
const aliasSymbol = scopes.symbolFor(parent.id);
|
|
44543
|
+
if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
|
|
44544
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44545
|
+
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44546
|
+
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43802
44547
|
}
|
|
44548
|
+
case "AssignmentExpression": return false;
|
|
43803
44549
|
case "Property":
|
|
43804
44550
|
if (parent.value !== valueNode) return false;
|
|
43805
44551
|
valueNode = parent;
|
|
@@ -43837,6 +44583,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43837
44583
|
severity: "warn",
|
|
43838
44584
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43839
44585
|
category: "Performance",
|
|
44586
|
+
tags: ["react-jsx-only"],
|
|
43840
44587
|
create: (context) => {
|
|
43841
44588
|
const settings = resolveSettings$8(context.settings);
|
|
43842
44589
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -43899,7 +44646,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43899
44646
|
},
|
|
43900
44647
|
Identifier(node) {
|
|
43901
44648
|
if (!isReactComponentName(node.name)) return;
|
|
43902
|
-
if (!isRenderFlowingReadReference(node)) return;
|
|
44649
|
+
if (!isRenderFlowingReadReference(node, context.scopes)) return;
|
|
43903
44650
|
recordInstantiation(node, node.name);
|
|
43904
44651
|
},
|
|
43905
44652
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -43908,28 +44655,33 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43908
44655
|
ClassDeclaration(node) {
|
|
43909
44656
|
if (!node.id) return;
|
|
43910
44657
|
if (!isReactComponentName(node.id.name)) return;
|
|
43911
|
-
if (!isReactClassComponent(node)) return;
|
|
44658
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43912
44659
|
enqueueCandidate(node, null);
|
|
43913
44660
|
},
|
|
43914
44661
|
ClassExpression(node) {
|
|
43915
44662
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
43916
44663
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
43917
|
-
if (!isReactClassComponent(node)) return;
|
|
44664
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43918
44665
|
enqueueCandidate(node, null);
|
|
43919
44666
|
},
|
|
43920
44667
|
CallExpression(node) {
|
|
43921
|
-
if (
|
|
44668
|
+
if (isReactCreateElementCall(node, context.scopes)) {
|
|
43922
44669
|
const firstArgument = node.arguments[0];
|
|
43923
44670
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
43924
44671
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
43925
44672
|
}
|
|
43926
|
-
|
|
43927
|
-
if (!
|
|
43928
|
-
|
|
44673
|
+
const isReactLazy = isReactLazyCall(node, context.scopes);
|
|
44674
|
+
if (!isReactLazy && !isHocCallee$1(node)) return;
|
|
44675
|
+
if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
|
|
44676
|
+
const inferredName = inferFunctionLikeName(node);
|
|
44677
|
+
const propInfo = isComponentDeclaredInProp(node);
|
|
44678
|
+
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44679
|
+
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43929
44680
|
},
|
|
43930
44681
|
"Program:exit"() {
|
|
43931
44682
|
for (const report of queuedReports) {
|
|
43932
44683
|
if (report.requiredInstantiationName !== null) {
|
|
44684
|
+
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
43933
44685
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
43934
44686
|
}
|
|
43935
44687
|
context.report({
|
|
@@ -44962,7 +45714,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
44962
45714
|
const symbol = scopes.symbolFor(identifier);
|
|
44963
45715
|
if (symbol) {
|
|
44964
45716
|
if (symbol.kind !== "parameter") return null;
|
|
44965
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45717
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44966
45718
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
44967
45719
|
return null;
|
|
44968
45720
|
}
|
|
@@ -45008,7 +45760,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45008
45760
|
const symbol = scopes.symbolFor(propsObject);
|
|
45009
45761
|
if (symbol) {
|
|
45010
45762
|
if (symbol.kind !== "parameter") return false;
|
|
45011
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45763
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
45012
45764
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45013
45765
|
}
|
|
45014
45766
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45660,7 +46412,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
45660
46412
|
//#endregion
|
|
45661
46413
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
45662
46414
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
45663
|
-
const functionNode = findEnclosingFunction(startNode);
|
|
46415
|
+
const functionNode = findEnclosingFunction$1(startNode);
|
|
45664
46416
|
if (!functionNode) return null;
|
|
45665
46417
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
45666
46418
|
if (!displayName) return null;
|
|
@@ -46722,7 +47474,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
46722
47474
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
46723
47475
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
46724
47476
|
const objectParent = objectExpression.parent;
|
|
46725
|
-
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47477
|
+
const enclosingFunction = findEnclosingFunction$1(objectExpression);
|
|
46726
47478
|
if (!enclosingFunction) return false;
|
|
46727
47479
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
46728
47480
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47100,10 +47852,10 @@ const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
|
47100
47852
|
return hasSuspension;
|
|
47101
47853
|
};
|
|
47102
47854
|
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47103
|
-
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47855
|
+
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47104
47856
|
while (enclosingFunction) {
|
|
47105
47857
|
if (enclosingFunction === ancestor) return true;
|
|
47106
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47858
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47107
47859
|
}
|
|
47108
47860
|
return false;
|
|
47109
47861
|
};
|
|
@@ -47125,7 +47877,7 @@ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedF
|
|
|
47125
47877
|
return invokesTarget;
|
|
47126
47878
|
};
|
|
47127
47879
|
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47128
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47880
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47129
47881
|
const boundaryStart = getRangeStart(boundary);
|
|
47130
47882
|
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47131
47883
|
let isInvokedBefore = false;
|
|
@@ -47162,11 +47914,11 @@ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, conte
|
|
|
47162
47914
|
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47163
47915
|
const writeStart = getRangeStart(writeNode);
|
|
47164
47916
|
const boundaryStart = getRangeStart(boundary);
|
|
47165
|
-
const writeFunction = findEnclosingFunction(writeNode);
|
|
47917
|
+
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47166
47918
|
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47167
47919
|
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47168
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47169
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47920
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47921
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47170
47922
|
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47171
47923
|
if (!writeFunction) return writeStart < boundaryStart;
|
|
47172
47924
|
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
@@ -47438,8 +48190,8 @@ const queryStableQueryClient = defineRule({
|
|
|
47438
48190
|
create: (context) => ({ NewExpression(node) {
|
|
47439
48191
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
47440
48192
|
if (isStableHookWrapperArgument(node)) return;
|
|
47441
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
47442
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48193
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
48194
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47443
48195
|
if (!enclosingFunction) return;
|
|
47444
48196
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
47445
48197
|
context.report({
|
|
@@ -47534,7 +48286,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47534
48286
|
const comparatorArgument = node.arguments?.[1];
|
|
47535
48287
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
47536
48288
|
} else {
|
|
47537
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
48289
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
47538
48290
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
47539
48291
|
}
|
|
47540
48292
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -50639,7 +51391,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
50639
51391
|
};
|
|
50640
51392
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
50641
51393
|
const isInsideStyleFactoryCallback = (node) => {
|
|
50642
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
51394
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
50643
51395
|
if (!enclosingFunction) return false;
|
|
50644
51396
|
const callExpression = enclosingFunction.parent;
|
|
50645
51397
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -57495,12 +58247,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
57495
58247
|
}
|
|
57496
58248
|
return false;
|
|
57497
58249
|
};
|
|
57498
|
-
const declarationAwaitsGate = (declaration) => {
|
|
58250
|
+
const declarationAwaitsGate = (declaration, context) => {
|
|
57499
58251
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
57500
58252
|
for (const declarator of declaration.declarations ?? []) {
|
|
57501
58253
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
57502
58254
|
const argument = declarator.init.argument;
|
|
57503
58255
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58256
|
+
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58257
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
57504
58258
|
const calleeName = getCalleeName$2(argument);
|
|
57505
58259
|
if (!calleeName) continue;
|
|
57506
58260
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -57528,7 +58282,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
57528
58282
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
57529
58283
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
57530
58284
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
57531
|
-
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58285
|
+
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
57532
58286
|
context.report({
|
|
57533
58287
|
node: nextStatement,
|
|
57534
58288
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -57618,6 +58372,26 @@ const stateInConstructor = defineRule({
|
|
|
57618
58372
|
}
|
|
57619
58373
|
});
|
|
57620
58374
|
//#endregion
|
|
58375
|
+
//#region src/plugin/utils/collect-jsx-runtime-imports.ts
|
|
58376
|
+
const REACT_RUNTIME_PACKAGE_PREFIXES = ["react", "react-dom"];
|
|
58377
|
+
const matchesPackage = (source, packageName) => source === packageName || source.startsWith(`${packageName}/`);
|
|
58378
|
+
const collectJsxRuntimeImports = (program) => {
|
|
58379
|
+
let hasReactRuntime = false;
|
|
58380
|
+
let hasSolidRuntime = false;
|
|
58381
|
+
for (const statement of program.body) {
|
|
58382
|
+
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
58383
|
+
if (isTypeOnlyImport(statement)) continue;
|
|
58384
|
+
const source = statement.source.value;
|
|
58385
|
+
if (typeof source !== "string") continue;
|
|
58386
|
+
if (matchesPackage(source, "solid-js")) hasSolidRuntime = true;
|
|
58387
|
+
if (REACT_RUNTIME_PACKAGE_PREFIXES.some((packageName) => matchesPackage(source, packageName))) hasReactRuntime = true;
|
|
58388
|
+
}
|
|
58389
|
+
return {
|
|
58390
|
+
hasReactRuntime,
|
|
58391
|
+
hasSolidRuntime
|
|
58392
|
+
};
|
|
58393
|
+
};
|
|
58394
|
+
//#endregion
|
|
57621
58395
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
57622
58396
|
const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
57623
58397
|
const resolveSettings = (settings) => {
|
|
@@ -57679,6 +58453,13 @@ const isInvalidStyleExpression = (expression) => {
|
|
|
57679
58453
|
}
|
|
57680
58454
|
return false;
|
|
57681
58455
|
};
|
|
58456
|
+
const hasObjectValuedClassList = (openingElement) => openingElement.attributes.some((attribute) => {
|
|
58457
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) return false;
|
|
58458
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
58459
|
+
if (attribute.name.name !== "classList") return false;
|
|
58460
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
58461
|
+
return isNodeOfType(attribute.value.expression, "ObjectExpression");
|
|
58462
|
+
});
|
|
57682
58463
|
const stylePropObject = defineRule({
|
|
57683
58464
|
id: "style-prop-object",
|
|
57684
58465
|
title: "Style prop is not an object",
|
|
@@ -57688,8 +58469,21 @@ const stylePropObject = defineRule({
|
|
|
57688
58469
|
create: (context) => {
|
|
57689
58470
|
const { allow } = resolveSettings(context.settings);
|
|
57690
58471
|
const allowSet = new Set(allow);
|
|
58472
|
+
let fileIsProvenSolidJsx = false;
|
|
57691
58473
|
return {
|
|
58474
|
+
Program: (node) => {
|
|
58475
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
58476
|
+
let hasSolidSyntaxMarker = false;
|
|
58477
|
+
if (!runtimeImports.hasReactRuntime && !runtimeImports.hasSolidRuntime) walkAst(node, (descendantNode) => {
|
|
58478
|
+
if (isNodeOfType(descendantNode, "JSXOpeningElement") && hasObjectValuedClassList(descendantNode)) {
|
|
58479
|
+
hasSolidSyntaxMarker = true;
|
|
58480
|
+
return false;
|
|
58481
|
+
}
|
|
58482
|
+
});
|
|
58483
|
+
fileIsProvenSolidJsx = !runtimeImports.hasReactRuntime && (runtimeImports.hasSolidRuntime || hasSolidSyntaxMarker);
|
|
58484
|
+
},
|
|
57692
58485
|
JSXOpeningElement(node) {
|
|
58486
|
+
if (fileIsProvenSolidJsx) return;
|
|
57693
58487
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
57694
58488
|
const elementName = resolveJsxElementType(node);
|
|
57695
58489
|
if (elementName && allowSet.has(elementName)) return;
|
|
@@ -57720,6 +58514,7 @@ const stylePropObject = defineRule({
|
|
|
57720
58514
|
}
|
|
57721
58515
|
},
|
|
57722
58516
|
CallExpression(node) {
|
|
58517
|
+
if (fileIsProvenSolidJsx) return;
|
|
57723
58518
|
if (!isCreateElementCall(node)) return;
|
|
57724
58519
|
const firstArgument = node.arguments[0];
|
|
57725
58520
|
if (!firstArgument) return;
|
|
@@ -58514,7 +59309,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58514
59309
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
58515
59310
|
const parent = functionNode.parent;
|
|
58516
59311
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
58517
|
-
const outerFunction = findEnclosingFunction(parent);
|
|
59312
|
+
const outerFunction = findEnclosingFunction$1(parent);
|
|
58518
59313
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
58519
59314
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
58520
59315
|
}
|
|
@@ -58531,13 +59326,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58531
59326
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
58532
59327
|
};
|
|
58533
59328
|
const isDeferredNavigateCall = (callNode) => {
|
|
58534
|
-
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59329
|
+
let enclosingFunction = findEnclosingFunction$1(callNode);
|
|
58535
59330
|
while (enclosingFunction) {
|
|
58536
59331
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
58537
59332
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
58538
59333
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
58539
59334
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
58540
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59335
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
58541
59336
|
}
|
|
58542
59337
|
return false;
|
|
58543
59338
|
};
|