oxlint-plugin-react-doctor 0.7.7-dev.89dbf08 → 0.7.7-dev.9c612c5
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 +1139 -376
- 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
|
};
|
|
@@ -36079,10 +36769,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
36079
36769
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
36080
36770
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
36081
36771
|
visitedFunctionNodes.add(functionNode);
|
|
36082
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36772
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
36083
36773
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
36084
36774
|
if (immediateCall) {
|
|
36085
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36775
|
+
const immediateCallFunction = findEnclosingFunction$1(immediateCall);
|
|
36086
36776
|
if (immediateCallFunction === usageFunction) {
|
|
36087
36777
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
36088
36778
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -36091,7 +36781,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36091
36781
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
36092
36782
|
}
|
|
36093
36783
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
36094
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36784
|
+
const methodCallFunction = findEnclosingFunction$1(methodCall);
|
|
36095
36785
|
if (methodCallFunction === usageFunction) {
|
|
36096
36786
|
const methodCallStart = getRangeStart(methodCall);
|
|
36097
36787
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -36114,7 +36804,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36114
36804
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
36115
36805
|
const call = getDirectCallForExpression(child);
|
|
36116
36806
|
if (!call) return false;
|
|
36117
|
-
const callFunction = findEnclosingFunction(call);
|
|
36807
|
+
const callFunction = findEnclosingFunction$1(call);
|
|
36118
36808
|
if (callFunction === usageFunction) {
|
|
36119
36809
|
const callStart = getRangeStart(call);
|
|
36120
36810
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -36145,14 +36835,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
36145
36835
|
visitedMutationSymbolIds.add(symbol.id);
|
|
36146
36836
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
36147
36837
|
if (typeof usageBoundary !== "number") return true;
|
|
36148
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36838
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
36149
36839
|
return symbol.references.some((reference) => {
|
|
36150
36840
|
const referenceStart = getRangeStart(reference.identifier);
|
|
36151
36841
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
36152
36842
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
36153
36843
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
36154
36844
|
if (referenceStart === null) return true;
|
|
36155
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36845
|
+
const mutationFunction = findEnclosingFunction$1(reference.identifier);
|
|
36156
36846
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
36157
36847
|
if (!mutationFunction) return usageFunction !== null;
|
|
36158
36848
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -36734,13 +37424,6 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
36734
37424
|
return isReactNamespaceImport(node, scopes);
|
|
36735
37425
|
};
|
|
36736
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));
|
|
36737
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36738
|
-
const property = symbol.bindingIdentifier.parent;
|
|
36739
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36740
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36741
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36742
|
-
return null;
|
|
36743
|
-
};
|
|
36744
37427
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
36745
37428
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
36746
37429
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -36750,7 +37433,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
36750
37433
|
}
|
|
36751
37434
|
const init = symbol.initializer;
|
|
36752
37435
|
if (!init) return false;
|
|
36753
|
-
const destructuredPropertyName =
|
|
37436
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
36754
37437
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
36755
37438
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
36756
37439
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -38504,12 +39187,6 @@ const getDeclarationKind = (declarator) => {
|
|
|
38504
39187
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
38505
39188
|
};
|
|
38506
39189
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
38507
|
-
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
38508
|
-
const property = bindingIdentifier.parent;
|
|
38509
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
38510
|
-
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
38511
|
-
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
38512
|
-
};
|
|
38513
39190
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
38514
39191
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38515
39192
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -38519,7 +39196,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38519
39196
|
if (isProp(analysis, callbackReference)) {
|
|
38520
39197
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
38521
39198
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
38522
|
-
return (bindingIdentifier &&
|
|
39199
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
38523
39200
|
}
|
|
38524
39201
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
38525
39202
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -38529,7 +39206,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38529
39206
|
visitedVariables.add(callbackVariable);
|
|
38530
39207
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
38531
39208
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
38532
|
-
const propertyName = bindingIdentifier ?
|
|
39209
|
+
const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
|
|
38533
39210
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
38534
39211
|
return propertyName && propsReference ? propertyName : null;
|
|
38535
39212
|
}
|
|
@@ -38632,7 +39309,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
38632
39309
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
38633
39310
|
if (!bindingProvenance) return null;
|
|
38634
39311
|
const { refCall, variables } = bindingProvenance;
|
|
38635
|
-
const componentFunction = findEnclosingFunction(effectCall);
|
|
39312
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
38636
39313
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
38637
39314
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
38638
39315
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39088,14 +39765,25 @@ const isPropsChildrenLength = (node, scopes) => {
|
|
|
39088
39765
|
const unwrappedNode = stripParenExpression(node);
|
|
39089
39766
|
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
39090
39767
|
};
|
|
39768
|
+
const resolveStaticNumericValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39769
|
+
const unwrappedNode = stripParenExpression(node);
|
|
39770
|
+
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "number") return Number.isFinite(unwrappedNode.value) ? unwrappedNode.value : null;
|
|
39771
|
+
if (!isNodeOfType(unwrappedNode, "Identifier")) return null;
|
|
39772
|
+
const symbol = scopes.symbolFor(unwrappedNode);
|
|
39773
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
39774
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
39775
|
+
if (!initializer) return null;
|
|
39776
|
+
return resolveStaticNumericValue(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id));
|
|
39777
|
+
};
|
|
39091
39778
|
const isLargeTextLengthComparison = (node, scopes) => {
|
|
39092
39779
|
const unwrappedNode = stripParenExpression(node);
|
|
39093
39780
|
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
39094
39781
|
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
39095
39782
|
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
39096
39783
|
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
39097
|
-
if (!leftIsLength && !rightIsLength
|
|
39098
|
-
|
|
39784
|
+
if (!leftIsLength && !rightIsLength) return false;
|
|
39785
|
+
const thresholdValue = resolveStaticNumericValue(thresholdNode, scopes);
|
|
39786
|
+
if (thresholdValue === null || thresholdValue < 1e3) return false;
|
|
39099
39787
|
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
39100
39788
|
};
|
|
39101
39789
|
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
@@ -39620,7 +40308,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
39620
40308
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39621
40309
|
if (!binding) return false;
|
|
39622
40310
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
39623
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40311
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
|
|
39624
40312
|
return false;
|
|
39625
40313
|
};
|
|
39626
40314
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -41537,9 +42225,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
41537
42225
|
return isReturnedFromEffect;
|
|
41538
42226
|
};
|
|
41539
42227
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
41540
|
-
let functionNode = findEnclosingFunction(node);
|
|
42228
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
41541
42229
|
while (functionNode) {
|
|
41542
|
-
const outerFunction = findEnclosingFunction(functionNode);
|
|
42230
|
+
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
41543
42231
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
41544
42232
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
41545
42233
|
functionNode = outerFunction;
|
|
@@ -41547,7 +42235,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
41547
42235
|
return false;
|
|
41548
42236
|
};
|
|
41549
42237
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
41550
|
-
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42238
|
+
const enclosingFunction = findEnclosingFunction$1(clearCall);
|
|
41551
42239
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
41552
42240
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41553
42241
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -42377,7 +43065,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
42377
43065
|
return false;
|
|
42378
43066
|
};
|
|
42379
43067
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
42380
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
43068
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
42381
43069
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42382
43070
|
let currentNode = node;
|
|
42383
43071
|
while (currentNode !== enclosingFunction) {
|
|
@@ -43644,7 +44332,11 @@ const resolveSettings$8 = (settings) => {
|
|
|
43644
44332
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43645
44333
|
};
|
|
43646
44334
|
};
|
|
43647
|
-
const
|
|
44335
|
+
const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
|
|
44336
|
+
allowGlobalReactNamespace: true,
|
|
44337
|
+
resolveNamedAliases: true
|
|
44338
|
+
});
|
|
44339
|
+
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43648
44340
|
let found = false;
|
|
43649
44341
|
walkAst(root, (node) => {
|
|
43650
44342
|
if (found) return false;
|
|
@@ -43653,17 +44345,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
43653
44345
|
found = true;
|
|
43654
44346
|
return false;
|
|
43655
44347
|
}
|
|
43656
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
44348
|
+
if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
|
|
43657
44349
|
found = true;
|
|
43658
44350
|
return false;
|
|
43659
44351
|
}
|
|
43660
44352
|
});
|
|
43661
44353
|
return found;
|
|
43662
44354
|
};
|
|
43663
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43664
|
-
const isReactClassComponent = (classNode) => {
|
|
44355
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
|
|
44356
|
+
const isReactClassComponent = (classNode, scopes) => {
|
|
43665
44357
|
if (isEs6Component(classNode)) return true;
|
|
43666
|
-
return expressionContainsJsxOrCreateElement(classNode);
|
|
44358
|
+
return expressionContainsJsxOrCreateElement(classNode, scopes);
|
|
43667
44359
|
};
|
|
43668
44360
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43669
44361
|
let walker = node.parent;
|
|
@@ -43680,7 +44372,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43680
44372
|
};
|
|
43681
44373
|
}
|
|
43682
44374
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43683
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44375
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
|
|
43684
44376
|
component: walker,
|
|
43685
44377
|
name: walker.id.name
|
|
43686
44378
|
};
|
|
@@ -43769,12 +44461,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43769
44461
|
}
|
|
43770
44462
|
return false;
|
|
43771
44463
|
};
|
|
43772
|
-
const hocCallContainsComponent = (call) => {
|
|
44464
|
+
const hocCallContainsComponent = (call, scopes) => {
|
|
43773
44465
|
const firstArgument = call.arguments[0];
|
|
43774
44466
|
if (!firstArgument) return false;
|
|
43775
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43776
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43777
|
-
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
44467
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
44468
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
|
|
44469
|
+
return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
43778
44470
|
};
|
|
43779
44471
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43780
44472
|
const parent = node.parent;
|
|
@@ -43807,19 +44499,35 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43807
44499
|
}
|
|
43808
44500
|
return false;
|
|
43809
44501
|
};
|
|
43810
|
-
const isCloneElementCall = (node) => {
|
|
43811
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43812
|
-
const callee = node.callee;
|
|
43813
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43814
|
-
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43815
|
-
};
|
|
43816
44502
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43817
44503
|
"TSAsExpression",
|
|
43818
44504
|
"TSNonNullExpression",
|
|
43819
44505
|
"TSSatisfiesExpression",
|
|
43820
44506
|
"TSTypeAssertion"
|
|
43821
44507
|
]);
|
|
43822
|
-
const
|
|
44508
|
+
const ELEMENT_TYPE_PROP_NAMES = new Set([
|
|
44509
|
+
"as",
|
|
44510
|
+
"body",
|
|
44511
|
+
"calendarcontainer",
|
|
44512
|
+
"component",
|
|
44513
|
+
"fallback",
|
|
44514
|
+
"tooltip"
|
|
44515
|
+
]);
|
|
44516
|
+
const isElementTypeJsxAttribute = (node) => {
|
|
44517
|
+
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44518
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44519
|
+
const attributeName = node.name.name;
|
|
44520
|
+
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44521
|
+
};
|
|
44522
|
+
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44523
|
+
allowGlobalReactNamespace: true,
|
|
44524
|
+
resolveNamedAliases: true
|
|
44525
|
+
});
|
|
44526
|
+
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44527
|
+
allowGlobalReactNamespace: true,
|
|
44528
|
+
resolveNamedAliases: true
|
|
44529
|
+
});
|
|
44530
|
+
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43823
44531
|
let valueNode = identifier;
|
|
43824
44532
|
let parent = valueNode.parent;
|
|
43825
44533
|
while (parent) {
|
|
@@ -43829,20 +44537,26 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
43829
44537
|
continue;
|
|
43830
44538
|
}
|
|
43831
44539
|
switch (parent.type) {
|
|
43832
|
-
case "
|
|
43833
|
-
case "
|
|
43834
|
-
case "
|
|
44540
|
+
case "JSXOpeningElement": return parent.name === valueNode;
|
|
44541
|
+
case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
|
|
44542
|
+
case "ReturnStatement": return false;
|
|
44543
|
+
case "ArrowFunctionExpression": return false;
|
|
43835
44544
|
case "CallExpression":
|
|
43836
44545
|
if (parent.callee === valueNode) return false;
|
|
43837
|
-
if (
|
|
44546
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
44547
|
+
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43838
44548
|
valueNode = parent;
|
|
43839
44549
|
parent = parent.parent;
|
|
43840
44550
|
continue;
|
|
43841
|
-
case "VariableDeclarator":
|
|
43842
|
-
|
|
43843
|
-
const
|
|
43844
|
-
|
|
44551
|
+
case "VariableDeclarator": {
|
|
44552
|
+
if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
|
|
44553
|
+
const aliasSymbol = scopes.symbolFor(parent.id);
|
|
44554
|
+
if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
|
|
44555
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44556
|
+
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44557
|
+
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43845
44558
|
}
|
|
44559
|
+
case "AssignmentExpression": return false;
|
|
43846
44560
|
case "Property":
|
|
43847
44561
|
if (parent.value !== valueNode) return false;
|
|
43848
44562
|
valueNode = parent;
|
|
@@ -43880,6 +44594,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43880
44594
|
severity: "warn",
|
|
43881
44595
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43882
44596
|
category: "Performance",
|
|
44597
|
+
tags: ["react-jsx-only"],
|
|
43883
44598
|
create: (context) => {
|
|
43884
44599
|
const settings = resolveSettings$8(context.settings);
|
|
43885
44600
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -43942,7 +44657,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43942
44657
|
},
|
|
43943
44658
|
Identifier(node) {
|
|
43944
44659
|
if (!isReactComponentName(node.name)) return;
|
|
43945
|
-
if (!isRenderFlowingReadReference(node)) return;
|
|
44660
|
+
if (!isRenderFlowingReadReference(node, context.scopes)) return;
|
|
43946
44661
|
recordInstantiation(node, node.name);
|
|
43947
44662
|
},
|
|
43948
44663
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -43951,28 +44666,33 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43951
44666
|
ClassDeclaration(node) {
|
|
43952
44667
|
if (!node.id) return;
|
|
43953
44668
|
if (!isReactComponentName(node.id.name)) return;
|
|
43954
|
-
if (!isReactClassComponent(node)) return;
|
|
44669
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43955
44670
|
enqueueCandidate(node, null);
|
|
43956
44671
|
},
|
|
43957
44672
|
ClassExpression(node) {
|
|
43958
44673
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
43959
44674
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
43960
|
-
if (!isReactClassComponent(node)) return;
|
|
44675
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43961
44676
|
enqueueCandidate(node, null);
|
|
43962
44677
|
},
|
|
43963
44678
|
CallExpression(node) {
|
|
43964
|
-
if (
|
|
44679
|
+
if (isReactCreateElementCall(node, context.scopes)) {
|
|
43965
44680
|
const firstArgument = node.arguments[0];
|
|
43966
44681
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
43967
44682
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
43968
44683
|
}
|
|
43969
|
-
|
|
43970
|
-
if (!
|
|
43971
|
-
|
|
44684
|
+
const isReactLazy = isReactLazyCall(node, context.scopes);
|
|
44685
|
+
if (!isReactLazy && !isHocCallee$1(node)) return;
|
|
44686
|
+
if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
|
|
44687
|
+
const inferredName = inferFunctionLikeName(node);
|
|
44688
|
+
const propInfo = isComponentDeclaredInProp(node);
|
|
44689
|
+
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44690
|
+
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43972
44691
|
},
|
|
43973
44692
|
"Program:exit"() {
|
|
43974
44693
|
for (const report of queuedReports) {
|
|
43975
44694
|
if (report.requiredInstantiationName !== null) {
|
|
44695
|
+
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
43976
44696
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
43977
44697
|
}
|
|
43978
44698
|
context.report({
|
|
@@ -45005,7 +45725,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
45005
45725
|
const symbol = scopes.symbolFor(identifier);
|
|
45006
45726
|
if (symbol) {
|
|
45007
45727
|
if (symbol.kind !== "parameter") return null;
|
|
45008
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45728
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
45009
45729
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
45010
45730
|
return null;
|
|
45011
45731
|
}
|
|
@@ -45051,7 +45771,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45051
45771
|
const symbol = scopes.symbolFor(propsObject);
|
|
45052
45772
|
if (symbol) {
|
|
45053
45773
|
if (symbol.kind !== "parameter") return false;
|
|
45054
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45774
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
45055
45775
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45056
45776
|
}
|
|
45057
45777
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45703,7 +46423,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
45703
46423
|
//#endregion
|
|
45704
46424
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
45705
46425
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
45706
|
-
const functionNode = findEnclosingFunction(startNode);
|
|
46426
|
+
const functionNode = findEnclosingFunction$1(startNode);
|
|
45707
46427
|
if (!functionNode) return null;
|
|
45708
46428
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
45709
46429
|
if (!displayName) return null;
|
|
@@ -46765,7 +47485,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
46765
47485
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
46766
47486
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
46767
47487
|
const objectParent = objectExpression.parent;
|
|
46768
|
-
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47488
|
+
const enclosingFunction = findEnclosingFunction$1(objectExpression);
|
|
46769
47489
|
if (!enclosingFunction) return false;
|
|
46770
47490
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
46771
47491
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47143,10 +47863,10 @@ const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
|
47143
47863
|
return hasSuspension;
|
|
47144
47864
|
};
|
|
47145
47865
|
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47146
|
-
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47866
|
+
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47147
47867
|
while (enclosingFunction) {
|
|
47148
47868
|
if (enclosingFunction === ancestor) return true;
|
|
47149
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47869
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47150
47870
|
}
|
|
47151
47871
|
return false;
|
|
47152
47872
|
};
|
|
@@ -47168,7 +47888,7 @@ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedF
|
|
|
47168
47888
|
return invokesTarget;
|
|
47169
47889
|
};
|
|
47170
47890
|
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47171
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47891
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47172
47892
|
const boundaryStart = getRangeStart(boundary);
|
|
47173
47893
|
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47174
47894
|
let isInvokedBefore = false;
|
|
@@ -47205,11 +47925,11 @@ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, conte
|
|
|
47205
47925
|
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47206
47926
|
const writeStart = getRangeStart(writeNode);
|
|
47207
47927
|
const boundaryStart = getRangeStart(boundary);
|
|
47208
|
-
const writeFunction = findEnclosingFunction(writeNode);
|
|
47928
|
+
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47209
47929
|
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47210
47930
|
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47211
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47212
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47931
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47932
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47213
47933
|
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47214
47934
|
if (!writeFunction) return writeStart < boundaryStart;
|
|
47215
47935
|
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
@@ -47481,8 +48201,8 @@ const queryStableQueryClient = defineRule({
|
|
|
47481
48201
|
create: (context) => ({ NewExpression(node) {
|
|
47482
48202
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
47483
48203
|
if (isStableHookWrapperArgument(node)) return;
|
|
47484
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
47485
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48204
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
48205
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47486
48206
|
if (!enclosingFunction) return;
|
|
47487
48207
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
47488
48208
|
context.report({
|
|
@@ -47577,7 +48297,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47577
48297
|
const comparatorArgument = node.arguments?.[1];
|
|
47578
48298
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
47579
48299
|
} else {
|
|
47580
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
48300
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
47581
48301
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
47582
48302
|
}
|
|
47583
48303
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -50682,7 +51402,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
50682
51402
|
};
|
|
50683
51403
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
50684
51404
|
const isInsideStyleFactoryCallback = (node) => {
|
|
50685
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
51405
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
50686
51406
|
if (!enclosingFunction) return false;
|
|
50687
51407
|
const callExpression = enclosingFunction.parent;
|
|
50688
51408
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -57538,12 +58258,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
57538
58258
|
}
|
|
57539
58259
|
return false;
|
|
57540
58260
|
};
|
|
57541
|
-
const declarationAwaitsGate = (declaration) => {
|
|
58261
|
+
const declarationAwaitsGate = (declaration, context) => {
|
|
57542
58262
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
57543
58263
|
for (const declarator of declaration.declarations ?? []) {
|
|
57544
58264
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
57545
58265
|
const argument = declarator.init.argument;
|
|
57546
58266
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58267
|
+
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58268
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
57547
58269
|
const calleeName = getCalleeName$2(argument);
|
|
57548
58270
|
if (!calleeName) continue;
|
|
57549
58271
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -57571,7 +58293,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
57571
58293
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
57572
58294
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
57573
58295
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
57574
|
-
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58296
|
+
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
57575
58297
|
context.report({
|
|
57576
58298
|
node: nextStatement,
|
|
57577
58299
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -57661,6 +58383,26 @@ const stateInConstructor = defineRule({
|
|
|
57661
58383
|
}
|
|
57662
58384
|
});
|
|
57663
58385
|
//#endregion
|
|
58386
|
+
//#region src/plugin/utils/collect-jsx-runtime-imports.ts
|
|
58387
|
+
const REACT_RUNTIME_PACKAGE_PREFIXES = ["react", "react-dom"];
|
|
58388
|
+
const matchesPackage = (source, packageName) => source === packageName || source.startsWith(`${packageName}/`);
|
|
58389
|
+
const collectJsxRuntimeImports = (program) => {
|
|
58390
|
+
let hasReactRuntime = false;
|
|
58391
|
+
let hasSolidRuntime = false;
|
|
58392
|
+
for (const statement of program.body) {
|
|
58393
|
+
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
58394
|
+
if (isTypeOnlyImport(statement)) continue;
|
|
58395
|
+
const source = statement.source.value;
|
|
58396
|
+
if (typeof source !== "string") continue;
|
|
58397
|
+
if (matchesPackage(source, "solid-js")) hasSolidRuntime = true;
|
|
58398
|
+
if (REACT_RUNTIME_PACKAGE_PREFIXES.some((packageName) => matchesPackage(source, packageName))) hasReactRuntime = true;
|
|
58399
|
+
}
|
|
58400
|
+
return {
|
|
58401
|
+
hasReactRuntime,
|
|
58402
|
+
hasSolidRuntime
|
|
58403
|
+
};
|
|
58404
|
+
};
|
|
58405
|
+
//#endregion
|
|
57664
58406
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
57665
58407
|
const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
57666
58408
|
const resolveSettings = (settings) => {
|
|
@@ -57722,6 +58464,13 @@ const isInvalidStyleExpression = (expression) => {
|
|
|
57722
58464
|
}
|
|
57723
58465
|
return false;
|
|
57724
58466
|
};
|
|
58467
|
+
const hasObjectValuedClassList = (openingElement) => openingElement.attributes.some((attribute) => {
|
|
58468
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) return false;
|
|
58469
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
58470
|
+
if (attribute.name.name !== "classList") return false;
|
|
58471
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
58472
|
+
return isNodeOfType(attribute.value.expression, "ObjectExpression");
|
|
58473
|
+
});
|
|
57725
58474
|
const stylePropObject = defineRule({
|
|
57726
58475
|
id: "style-prop-object",
|
|
57727
58476
|
title: "Style prop is not an object",
|
|
@@ -57731,8 +58480,21 @@ const stylePropObject = defineRule({
|
|
|
57731
58480
|
create: (context) => {
|
|
57732
58481
|
const { allow } = resolveSettings(context.settings);
|
|
57733
58482
|
const allowSet = new Set(allow);
|
|
58483
|
+
let fileIsProvenSolidJsx = false;
|
|
57734
58484
|
return {
|
|
58485
|
+
Program: (node) => {
|
|
58486
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
58487
|
+
let hasSolidSyntaxMarker = false;
|
|
58488
|
+
if (!runtimeImports.hasReactRuntime && !runtimeImports.hasSolidRuntime) walkAst(node, (descendantNode) => {
|
|
58489
|
+
if (isNodeOfType(descendantNode, "JSXOpeningElement") && hasObjectValuedClassList(descendantNode)) {
|
|
58490
|
+
hasSolidSyntaxMarker = true;
|
|
58491
|
+
return false;
|
|
58492
|
+
}
|
|
58493
|
+
});
|
|
58494
|
+
fileIsProvenSolidJsx = !runtimeImports.hasReactRuntime && (runtimeImports.hasSolidRuntime || hasSolidSyntaxMarker);
|
|
58495
|
+
},
|
|
57735
58496
|
JSXOpeningElement(node) {
|
|
58497
|
+
if (fileIsProvenSolidJsx) return;
|
|
57736
58498
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
57737
58499
|
const elementName = resolveJsxElementType(node);
|
|
57738
58500
|
if (elementName && allowSet.has(elementName)) return;
|
|
@@ -57763,6 +58525,7 @@ const stylePropObject = defineRule({
|
|
|
57763
58525
|
}
|
|
57764
58526
|
},
|
|
57765
58527
|
CallExpression(node) {
|
|
58528
|
+
if (fileIsProvenSolidJsx) return;
|
|
57766
58529
|
if (!isCreateElementCall(node)) return;
|
|
57767
58530
|
const firstArgument = node.arguments[0];
|
|
57768
58531
|
if (!firstArgument) return;
|
|
@@ -58557,7 +59320,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58557
59320
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
58558
59321
|
const parent = functionNode.parent;
|
|
58559
59322
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
58560
|
-
const outerFunction = findEnclosingFunction(parent);
|
|
59323
|
+
const outerFunction = findEnclosingFunction$1(parent);
|
|
58561
59324
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
58562
59325
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
58563
59326
|
}
|
|
@@ -58574,13 +59337,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58574
59337
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
58575
59338
|
};
|
|
58576
59339
|
const isDeferredNavigateCall = (callNode) => {
|
|
58577
|
-
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59340
|
+
let enclosingFunction = findEnclosingFunction$1(callNode);
|
|
58578
59341
|
while (enclosingFunction) {
|
|
58579
59342
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
58580
59343
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
58581
59344
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
58582
59345
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
58583
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59346
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
58584
59347
|
}
|
|
58585
59348
|
return false;
|
|
58586
59349
|
};
|