oxlint-plugin-react-doctor 0.9.0-dev.db9d300 → 0.9.1-dev.4fbab2d
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.d.ts +433 -1
- package/dist/index.js +2258 -130
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1017,11 +1017,14 @@ const forEachChildNode = (node, visit) => {
|
|
|
1017
1017
|
};
|
|
1018
1018
|
const walkAst = (node, visitor) => {
|
|
1019
1019
|
if (!node || typeof node !== "object") return;
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1020
|
+
const pendingNodes = [node];
|
|
1021
|
+
while (pendingNodes.length > 0) {
|
|
1022
|
+
const currentNode = pendingNodes.pop();
|
|
1023
|
+
if (currentNode === void 0 || visitor(currentNode) === false) continue;
|
|
1024
|
+
const childNodes = [];
|
|
1025
|
+
forEachChildNode(currentNode, (childNode) => childNodes.push(childNode));
|
|
1026
|
+
for (let childIndex = childNodes.length - 1; childIndex >= 0; childIndex -= 1) pendingNodes.push(childNodes[childIndex]);
|
|
1027
|
+
}
|
|
1025
1028
|
};
|
|
1026
1029
|
//#endregion
|
|
1027
1030
|
//#region src/plugin/rules/state-and-effects/activity-wraps-effect-heavy-subtree.ts
|
|
@@ -14548,16 +14551,136 @@ const dangerousHtmlSink = defineRule({
|
|
|
14548
14551
|
}
|
|
14549
14552
|
});
|
|
14550
14553
|
//#endregion
|
|
14554
|
+
//#region src/plugin/utils/get-static-logical-expression-result-branches.ts
|
|
14555
|
+
const deduplicateResultBranches = (branches) => {
|
|
14556
|
+
const resultBranches = [];
|
|
14557
|
+
const seenStatesByExpression = /* @__PURE__ */ new Map();
|
|
14558
|
+
for (const branch of branches) {
|
|
14559
|
+
const branchState = `${branch.truthiness}:${branch.nullishness}`;
|
|
14560
|
+
const seenStates = seenStatesByExpression.get(branch.expression);
|
|
14561
|
+
if (seenStates?.has(branchState)) continue;
|
|
14562
|
+
if (seenStates) seenStates.add(branchState);
|
|
14563
|
+
else seenStatesByExpression.set(branch.expression, new Set([branchState]));
|
|
14564
|
+
resultBranches.push(branch);
|
|
14565
|
+
}
|
|
14566
|
+
return resultBranches;
|
|
14567
|
+
};
|
|
14568
|
+
const getAtomicExpressionResultBranch = (expression) => {
|
|
14569
|
+
if (isNodeOfType(expression, "JSXElement") || isNodeOfType(expression, "JSXFragment") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression") || isNodeOfType(expression, "NewExpression")) return {
|
|
14570
|
+
expression,
|
|
14571
|
+
truthiness: "truthy",
|
|
14572
|
+
nullishness: "non-nullish"
|
|
14573
|
+
};
|
|
14574
|
+
if (isNodeOfType(expression, "Literal")) {
|
|
14575
|
+
if (expression.value === null) return {
|
|
14576
|
+
expression,
|
|
14577
|
+
truthiness: "falsy",
|
|
14578
|
+
nullishness: "nullish"
|
|
14579
|
+
};
|
|
14580
|
+
return {
|
|
14581
|
+
expression,
|
|
14582
|
+
truthiness: expression.value ? "truthy" : "falsy",
|
|
14583
|
+
nullishness: "non-nullish"
|
|
14584
|
+
};
|
|
14585
|
+
}
|
|
14586
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
|
|
14587
|
+
expression,
|
|
14588
|
+
truthiness: "falsy",
|
|
14589
|
+
nullishness: "nullish"
|
|
14590
|
+
};
|
|
14591
|
+
return {
|
|
14592
|
+
expression,
|
|
14593
|
+
truthiness: "unknown",
|
|
14594
|
+
nullishness: "unknown"
|
|
14595
|
+
};
|
|
14596
|
+
};
|
|
14597
|
+
const getStaticExpressionResultBranches = (expression) => {
|
|
14598
|
+
const finalExpression = getFinalSequenceExpressionValue(expression);
|
|
14599
|
+
if (isNodeOfType(finalExpression, "ConditionalExpression")) return deduplicateResultBranches([...getStaticExpressionResultBranches(finalExpression.consequent), ...getStaticExpressionResultBranches(finalExpression.alternate)]);
|
|
14600
|
+
if (!isNodeOfType(finalExpression, "LogicalExpression")) return [getAtomicExpressionResultBranch(finalExpression)];
|
|
14601
|
+
const leftBranches = getStaticExpressionResultBranches(finalExpression.left);
|
|
14602
|
+
const rightBranches = getStaticExpressionResultBranches(finalExpression.right);
|
|
14603
|
+
const resultBranches = [];
|
|
14604
|
+
for (const leftBranch of leftBranches) {
|
|
14605
|
+
if (finalExpression.operator === "&&") {
|
|
14606
|
+
if (leftBranch.truthiness !== "truthy") resultBranches.push(leftBranch.truthiness === "falsy" ? leftBranch : {
|
|
14607
|
+
...leftBranch,
|
|
14608
|
+
truthiness: "falsy"
|
|
14609
|
+
});
|
|
14610
|
+
if (leftBranch.truthiness !== "falsy") resultBranches.push(...rightBranches);
|
|
14611
|
+
continue;
|
|
14612
|
+
}
|
|
14613
|
+
if (finalExpression.operator === "||") {
|
|
14614
|
+
if (leftBranch.truthiness !== "falsy") resultBranches.push(leftBranch.truthiness === "truthy" ? leftBranch : {
|
|
14615
|
+
...leftBranch,
|
|
14616
|
+
truthiness: "truthy",
|
|
14617
|
+
nullishness: "non-nullish"
|
|
14618
|
+
});
|
|
14619
|
+
if (leftBranch.truthiness !== "truthy") resultBranches.push(...rightBranches);
|
|
14620
|
+
continue;
|
|
14621
|
+
}
|
|
14622
|
+
if (leftBranch.nullishness !== "nullish") resultBranches.push(leftBranch.nullishness === "non-nullish" ? leftBranch : {
|
|
14623
|
+
...leftBranch,
|
|
14624
|
+
nullishness: "non-nullish"
|
|
14625
|
+
});
|
|
14626
|
+
if (leftBranch.nullishness !== "non-nullish") resultBranches.push(...rightBranches);
|
|
14627
|
+
}
|
|
14628
|
+
return deduplicateResultBranches(resultBranches);
|
|
14629
|
+
};
|
|
14630
|
+
const getStaticLogicalExpressionResultBranches = (expression) => {
|
|
14631
|
+
const resultExpressions = [];
|
|
14632
|
+
const seenExpressions = /* @__PURE__ */ new Set();
|
|
14633
|
+
for (const resultBranch of getStaticExpressionResultBranches(expression)) {
|
|
14634
|
+
if (seenExpressions.has(resultBranch.expression)) continue;
|
|
14635
|
+
seenExpressions.add(resultBranch.expression);
|
|
14636
|
+
resultExpressions.push(resultBranch.expression);
|
|
14637
|
+
}
|
|
14638
|
+
return resultExpressions;
|
|
14639
|
+
};
|
|
14640
|
+
//#endregion
|
|
14551
14641
|
//#region src/plugin/utils/get-static-jsx-descendant-opening-elements.ts
|
|
14552
|
-
const
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14642
|
+
const appendDescendant = (node, descendants, includeStaticExpressionBranches) => {
|
|
14643
|
+
const expression = stripParenExpression(node);
|
|
14644
|
+
if (isNodeOfType(expression, "JSXElement")) {
|
|
14645
|
+
descendants.push(expression.openingElement);
|
|
14646
|
+
for (const child of expression.children) appendDescendant(child, descendants, includeStaticExpressionBranches);
|
|
14647
|
+
return;
|
|
14648
|
+
}
|
|
14649
|
+
if (isNodeOfType(expression, "JSXFragment")) {
|
|
14650
|
+
for (const child of expression.children) appendDescendant(child, descendants, includeStaticExpressionBranches);
|
|
14651
|
+
return;
|
|
14652
|
+
}
|
|
14653
|
+
if (!includeStaticExpressionBranches) return;
|
|
14654
|
+
if (isNodeOfType(expression, "JSXExpressionContainer")) {
|
|
14655
|
+
appendDescendant(expression.expression, descendants, true);
|
|
14656
|
+
return;
|
|
14657
|
+
}
|
|
14658
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
14659
|
+
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(expression.test));
|
|
14660
|
+
if (staticTestValue !== null) {
|
|
14661
|
+
appendDescendant(staticTestValue ? expression.consequent : expression.alternate, descendants, true);
|
|
14662
|
+
return;
|
|
14663
|
+
}
|
|
14664
|
+
appendDescendant(expression.consequent, descendants, true);
|
|
14665
|
+
appendDescendant(expression.alternate, descendants, true);
|
|
14666
|
+
return;
|
|
14667
|
+
}
|
|
14668
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
14669
|
+
for (const resultBranch of getStaticLogicalExpressionResultBranches(expression)) appendDescendant(resultBranch, descendants, true);
|
|
14670
|
+
return;
|
|
14671
|
+
}
|
|
14672
|
+
if (isNodeOfType(expression, "ArrayExpression")) {
|
|
14673
|
+
for (const element of expression.elements) if (element && !isNodeOfType(element, "SpreadElement")) appendDescendant(element, descendants, true);
|
|
14674
|
+
return;
|
|
14675
|
+
}
|
|
14676
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
14677
|
+
if (expression.expressions.length === 0) return;
|
|
14678
|
+
appendDescendant(getFinalSequenceExpressionValue(expression), descendants, true);
|
|
14679
|
+
}
|
|
14557
14680
|
};
|
|
14558
|
-
const getStaticJsxDescendantOpeningElements = (element) => {
|
|
14681
|
+
const getStaticJsxDescendantOpeningElements = (element, options = {}) => {
|
|
14559
14682
|
const descendants = [];
|
|
14560
|
-
|
|
14683
|
+
for (const child of element.children) appendDescendant(child, descendants, options.includeStaticExpressionBranches === true);
|
|
14561
14684
|
return descendants;
|
|
14562
14685
|
};
|
|
14563
14686
|
//#endregion
|
|
@@ -22022,10 +22145,10 @@ const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
|
|
|
22022
22145
|
for (const otherDeclaredKey of declaredKeys) if (otherDeclaredKey !== declaredKey && declaredKey.startsWith(`${otherDeclaredKey}.`)) return true;
|
|
22023
22146
|
return false;
|
|
22024
22147
|
};
|
|
22025
|
-
const getMemberRootIdentifier = (node) => {
|
|
22148
|
+
const getMemberRootIdentifier$1 = (node) => {
|
|
22026
22149
|
const stripped = unwrapExpression$3(node);
|
|
22027
22150
|
if (isNodeOfType(stripped, "Identifier")) return stripped;
|
|
22028
|
-
if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier(stripped.object);
|
|
22151
|
+
if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier$1(stripped.object);
|
|
22029
22152
|
return null;
|
|
22030
22153
|
};
|
|
22031
22154
|
const hasComputedMemberExpression = (node) => {
|
|
@@ -22062,7 +22185,7 @@ const resolveIdentitySourceKeysFromExpression = (expression, scopes, visitedSymb
|
|
|
22062
22185
|
if (isNodeOfType(stripped, "MemberExpression")) {
|
|
22063
22186
|
if (hasComputedMemberExpression(stripped)) return null;
|
|
22064
22187
|
const sourceKey = stringifyMemberChain(stripped);
|
|
22065
|
-
const rootIdentifier = getMemberRootIdentifier(stripped);
|
|
22188
|
+
const rootIdentifier = getMemberRootIdentifier$1(stripped);
|
|
22066
22189
|
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
22067
22190
|
if (!sourceKey || !rootSymbol) return null;
|
|
22068
22191
|
if (isOutsideAllFunctions(rootSymbol)) return /* @__PURE__ */ new Set();
|
|
@@ -22156,7 +22279,7 @@ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds
|
|
|
22156
22279
|
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
22157
22280
|
if (hasComputedMemberExpression(candidate)) return null;
|
|
22158
22281
|
const sourceKey = stringifyMemberChain(candidate);
|
|
22159
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
22282
|
+
const rootIdentifier = getMemberRootIdentifier$1(candidate);
|
|
22160
22283
|
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
22161
22284
|
if (!sourceKey || !rootSymbol) return null;
|
|
22162
22285
|
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
@@ -22243,14 +22366,14 @@ const isUseCallbackResultDep = (node, scopes) => {
|
|
|
22243
22366
|
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && getHookName(initializer.callee, scopes) === "useCallback");
|
|
22244
22367
|
};
|
|
22245
22368
|
const isExtraReactiveDepAllowed = (node, scopes) => {
|
|
22246
|
-
const rootIdentifier = getMemberRootIdentifier(node);
|
|
22369
|
+
const rootIdentifier = getMemberRootIdentifier$1(node);
|
|
22247
22370
|
if (!rootIdentifier) return false;
|
|
22248
22371
|
const symbol = scopes.symbolFor(rootIdentifier);
|
|
22249
22372
|
if (!symbol || isOutsideAllFunctions(symbol)) return false;
|
|
22250
22373
|
return !isUseCallbackResultDep(node, scopes);
|
|
22251
22374
|
};
|
|
22252
22375
|
const getRootSymbol = (node, scopes) => {
|
|
22253
|
-
const rootIdentifier = getMemberRootIdentifier(node);
|
|
22376
|
+
const rootIdentifier = getMemberRootIdentifier$1(node);
|
|
22254
22377
|
return rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
22255
22378
|
};
|
|
22256
22379
|
const getDeclaredDepSymbolSource = (node) => {
|
|
@@ -22539,7 +22662,7 @@ const findRefCurrentInCleanup = (callback, scopes) => {
|
|
|
22539
22662
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
22540
22663
|
const candidateName = getRefCurrentNameFromMemberExpression(node);
|
|
22541
22664
|
if (candidateName) {
|
|
22542
|
-
const rootIdentifier = getMemberRootIdentifier(node);
|
|
22665
|
+
const rootIdentifier = getMemberRootIdentifier$1(node);
|
|
22543
22666
|
const symbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
22544
22667
|
const callbackScope = scopes.ownScopeFor(callback) ?? scopes.scopeFor(callback);
|
|
22545
22668
|
if (!symbol || !isDescendantScope(symbol.scope, callbackScope)) {
|
|
@@ -22612,7 +22735,7 @@ const hasRefCurrentAssignment = (callback, refCurrentName) => {
|
|
|
22612
22735
|
return didAssignRefCurrent;
|
|
22613
22736
|
};
|
|
22614
22737
|
const isOuterFunctionScopeDep = (node, callback, scopes) => {
|
|
22615
|
-
const rootIdentifier = getMemberRootIdentifier(node);
|
|
22738
|
+
const rootIdentifier = getMemberRootIdentifier$1(node);
|
|
22616
22739
|
if (!rootIdentifier) return false;
|
|
22617
22740
|
const symbol = scopes.symbolFor(rootIdentifier);
|
|
22618
22741
|
if (!symbol || isOutsideAllFunctions(symbol)) return false;
|
|
@@ -25373,6 +25496,27 @@ const flattenLogicalAndChain = (node) => {
|
|
|
25373
25496
|
return [node];
|
|
25374
25497
|
};
|
|
25375
25498
|
//#endregion
|
|
25499
|
+
//#region src/plugin/utils/resolve-imported-jsx-component-name.ts
|
|
25500
|
+
const resolveImportedJsxComponentName = (openingElement, moduleSource, scopes) => {
|
|
25501
|
+
const elementName = openingElement.name;
|
|
25502
|
+
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
25503
|
+
const symbol = scopes.symbolFor(elementName);
|
|
25504
|
+
if (!symbol || symbol.kind !== "import") return null;
|
|
25505
|
+
const importDeclaration = getImportDeclarationForSymbol(symbol);
|
|
25506
|
+
if (!importDeclaration || importDeclaration.source.value !== moduleSource || isTypeOnlyImport(importDeclaration)) return null;
|
|
25507
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return "default";
|
|
25508
|
+
if (!isNodeOfType(symbol.declarationNode, "ImportSpecifier")) return null;
|
|
25509
|
+
if (symbol.declarationNode.importKind === "type") return null;
|
|
25510
|
+
return getImportedName(symbol.declarationNode) ?? null;
|
|
25511
|
+
}
|
|
25512
|
+
if (!isNodeOfType(elementName, "JSXMemberExpression") || !isNodeOfType(elementName.object, "JSXIdentifier") || !isNodeOfType(elementName.property, "JSXIdentifier")) return null;
|
|
25513
|
+
const namespaceSymbol = scopes.symbolFor(elementName.object);
|
|
25514
|
+
if (!namespaceSymbol || namespaceSymbol.kind !== "import" || !isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier")) return null;
|
|
25515
|
+
const importDeclaration = getImportDeclarationForSymbol(namespaceSymbol);
|
|
25516
|
+
if (!importDeclaration || importDeclaration.source.value !== moduleSource || isTypeOnlyImport(importDeclaration)) return null;
|
|
25517
|
+
return elementName.property.name;
|
|
25518
|
+
};
|
|
25519
|
+
//#endregion
|
|
25376
25520
|
//#region src/plugin/utils/resolve-ink-api-name.ts
|
|
25377
25521
|
const resolveInkApiName = (node, scopes) => {
|
|
25378
25522
|
if (isNodeOfType(node, "Identifier")) {
|
|
@@ -25382,15 +25526,7 @@ const resolveInkApiName = (node, scopes) => {
|
|
|
25382
25526
|
if (isNodeOfType(node, "MemberExpression") && isNodeOfType(node.object, "Identifier") && scopes.symbolFor(node.object)?.kind === "import" && isNamespaceImportFromModule$1(node, node.object.name, "ink")) return getStaticPropertyName(node);
|
|
25383
25527
|
return null;
|
|
25384
25528
|
};
|
|
25385
|
-
const resolveInkJsxElementName = (openingElement, scopes) =>
|
|
25386
|
-
const elementName = openingElement.name;
|
|
25387
|
-
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
25388
|
-
if (scopes.symbolFor(elementName)?.kind !== "import") return null;
|
|
25389
|
-
return getImportedNameFromModule(openingElement, elementName.name, "ink");
|
|
25390
|
-
}
|
|
25391
|
-
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && scopes.symbolFor(elementName.object)?.kind === "import" && isNamespaceImportFromModule$1(openingElement, elementName.object.name, "ink")) return elementName.property.name;
|
|
25392
|
-
return null;
|
|
25393
|
-
};
|
|
25529
|
+
const resolveInkJsxElementName = (openingElement, scopes) => resolveImportedJsxComponentName(openingElement, "ink", scopes);
|
|
25394
25530
|
//#endregion
|
|
25395
25531
|
//#region src/plugin/utils/resolve-ink-render-calls.ts
|
|
25396
25532
|
const getRenderedComponentName = (renderCall) => {
|
|
@@ -33877,6 +34013,27 @@ const jsxNoTargetBlank = defineRule({
|
|
|
33877
34013
|
}
|
|
33878
34014
|
});
|
|
33879
34015
|
//#endregion
|
|
34016
|
+
//#region src/plugin/utils/get-project-relative-filename.ts
|
|
34017
|
+
const getProjectRelativeFilename = (filename, rootDirectory) => {
|
|
34018
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
34019
|
+
if (!rootDirectory) return normalizedFilename;
|
|
34020
|
+
const rootDirectoryPrefix = `${normalizeFilename(rootDirectory).replace(/\/+$/, "")}/`;
|
|
34021
|
+
if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
|
|
34022
|
+
return normalizedFilename.slice(rootDirectoryPrefix.length);
|
|
34023
|
+
};
|
|
34024
|
+
//#endregion
|
|
34025
|
+
//#region src/plugin/utils/get-project-relative-filename-from-roots.ts
|
|
34026
|
+
const getProjectRelativeFilenameFromRoots = (filename, rootDirectories) => {
|
|
34027
|
+
const normalizedFilename = normalizeFilename(filename);
|
|
34028
|
+
if (normalizedFilename.length === 0) return null;
|
|
34029
|
+
if (!path.isAbsolute(filename)) return normalizedFilename;
|
|
34030
|
+
for (const rootDirectory of rootDirectories) {
|
|
34031
|
+
const relativeFilename = getProjectRelativeFilename(normalizedFilename, rootDirectory);
|
|
34032
|
+
if (relativeFilename !== normalizedFilename) return relativeFilename;
|
|
34033
|
+
}
|
|
34034
|
+
return null;
|
|
34035
|
+
};
|
|
34036
|
+
//#endregion
|
|
33880
34037
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
33881
34038
|
const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
33882
34039
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -33927,24 +34084,60 @@ const getRootIdentifier$1 = (elementName) => {
|
|
|
33927
34084
|
}
|
|
33928
34085
|
return null;
|
|
33929
34086
|
};
|
|
34087
|
+
const resolveUnpluginAutoImportGlobalScopes = (settings) => {
|
|
34088
|
+
const reactDoctorSettings = settings?.["react-doctor"];
|
|
34089
|
+
if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return [];
|
|
34090
|
+
const rawScopes = Object.getOwnPropertyDescriptor(reactDoctorSettings, "unpluginAutoImportGlobalScopes")?.value;
|
|
34091
|
+
if (!Array.isArray(rawScopes)) return [];
|
|
34092
|
+
const scopes = [];
|
|
34093
|
+
for (const rawScope of rawScopes) {
|
|
34094
|
+
if (typeof rawScope !== "object" || rawScope === null || Array.isArray(rawScope)) continue;
|
|
34095
|
+
const directory = Object.getOwnPropertyDescriptor(rawScope, "directory")?.value;
|
|
34096
|
+
const names = Object.getOwnPropertyDescriptor(rawScope, "names")?.value;
|
|
34097
|
+
if (typeof directory !== "string" || !Array.isArray(names)) continue;
|
|
34098
|
+
const validNames = names.filter((name) => typeof name === "string" && name.length > 0);
|
|
34099
|
+
scopes.push({
|
|
34100
|
+
directory,
|
|
34101
|
+
names: new Set(validNames)
|
|
34102
|
+
});
|
|
34103
|
+
}
|
|
34104
|
+
return scopes;
|
|
34105
|
+
};
|
|
34106
|
+
const isInjectedRuntimeGlobal = (name, relativeFilename, configuredRuntimeGlobals, scopes) => {
|
|
34107
|
+
if (configuredRuntimeGlobals.has(name)) return true;
|
|
34108
|
+
if (relativeFilename === null) return false;
|
|
34109
|
+
let nearestScope = null;
|
|
34110
|
+
for (const scope of scopes) if ((scope.directory.length === 0 || relativeFilename.startsWith(`${scope.directory}/`)) && (!nearestScope || scope.directory.length > nearestScope.directory.length)) nearestScope = scope;
|
|
34111
|
+
return nearestScope?.names.has(name) ?? false;
|
|
34112
|
+
};
|
|
33930
34113
|
const jsxNoUndef = defineRule({
|
|
33931
34114
|
id: "jsx-no-undef",
|
|
33932
34115
|
title: "Undefined JSX component",
|
|
33933
34116
|
severity: "error",
|
|
33934
34117
|
recommendation: "Import the component or fix the typo so React can resolve the JSX identifier at runtime.",
|
|
33935
|
-
create: (context) =>
|
|
33936
|
-
const
|
|
33937
|
-
|
|
33938
|
-
|
|
33939
|
-
const
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
|
|
33947
|
-
|
|
34118
|
+
create: (context) => {
|
|
34119
|
+
const autoImportGlobalScopes = resolveUnpluginAutoImportGlobalScopes(context.settings);
|
|
34120
|
+
const configuredRuntimeGlobals = new Set(getReactDoctorStringArraySetting(context.settings, "runtimeGlobals"));
|
|
34121
|
+
const configuredAutoImportRootDirectories = getReactDoctorStringArraySetting(context.settings, "unpluginAutoImportRootDirectories");
|
|
34122
|
+
const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
34123
|
+
const fallbackAutoImportRootDirectories = rootDirectory ? [rootDirectory] : [];
|
|
34124
|
+
const autoImportRootDirectories = configuredAutoImportRootDirectories.length > 0 ? configuredAutoImportRootDirectories : fallbackAutoImportRootDirectories;
|
|
34125
|
+
const relativeFilename = getProjectRelativeFilenameFromRoots(context.filename ?? "", autoImportRootDirectories);
|
|
34126
|
+
return { JSXOpeningElement(node) {
|
|
34127
|
+
const rootIdentifier = getRootIdentifier$1(node.name);
|
|
34128
|
+
if (!rootIdentifier) return;
|
|
34129
|
+
if (KNOWN_GLOBALS.has(rootIdentifier)) return;
|
|
34130
|
+
if (isInjectedRuntimeGlobal(rootIdentifier, relativeFilename, configuredRuntimeGlobals, autoImportGlobalScopes)) return;
|
|
34131
|
+
const programRoot = findProgramRoot(node);
|
|
34132
|
+
if (!programRoot) return;
|
|
34133
|
+
if (isReactLiveStyleScript(programRoot)) return;
|
|
34134
|
+
if (findVariableInitializer(node, rootIdentifier)) return;
|
|
34135
|
+
context.report({
|
|
34136
|
+
node: node.name,
|
|
34137
|
+
message: buildMessage$18(rootIdentifier)
|
|
34138
|
+
});
|
|
34139
|
+
} };
|
|
34140
|
+
}
|
|
33948
34141
|
});
|
|
33949
34142
|
//#endregion
|
|
33950
34143
|
//#region src/plugin/rules/react-builtins/jsx-no-useless-fragment.ts
|
|
@@ -35934,6 +36127,7 @@ const MOBX_RULE_GATES = {
|
|
|
35934
36127
|
const resolveImportSymbol = (symbol) => {
|
|
35935
36128
|
const importDeclaration = getImportDeclarationForSymbol(symbol);
|
|
35936
36129
|
if (!importDeclaration || typeof importDeclaration.source.value !== "string") return null;
|
|
36130
|
+
if (importDeclaration.importKind === "type" || isNodeOfType(symbol.declarationNode, "ImportSpecifier") && symbol.declarationNode.importKind === "type") return null;
|
|
35937
36131
|
if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return {
|
|
35938
36132
|
source: importDeclaration.source.value,
|
|
35939
36133
|
importedName: null,
|
|
@@ -37419,15 +37613,6 @@ const getSingleReturnExpression = (functionNode) => {
|
|
|
37419
37613
|
return isNodeOfType(statement, "ReturnStatement") && statement.argument ? statement.argument : null;
|
|
37420
37614
|
};
|
|
37421
37615
|
//#endregion
|
|
37422
|
-
//#region src/plugin/utils/get-project-relative-filename.ts
|
|
37423
|
-
const getProjectRelativeFilename = (filename, rootDirectory) => {
|
|
37424
|
-
const normalizedFilename = normalizeFilename(filename);
|
|
37425
|
-
if (!rootDirectory) return normalizedFilename;
|
|
37426
|
-
const rootDirectoryPrefix = `${normalizeFilename(rootDirectory).replace(/\/+$/, "")}/`;
|
|
37427
|
-
if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
|
|
37428
|
-
return normalizedFilename.slice(rootDirectoryPrefix.length);
|
|
37429
|
-
};
|
|
37430
|
-
//#endregion
|
|
37431
37616
|
//#region src/plugin/utils/get-react-router-framework-module-kind.ts
|
|
37432
37617
|
const REACT_ROUTER_ROUTE_DIRECTORY_PATTERN = /(?:^|\/)app\/routes\//;
|
|
37433
37618
|
const REACT_ROUTER_ROOT_FILE_PATTERN = /(?:^|\/)app\/root\.(?:jsx?|tsx?)$/;
|
|
@@ -43678,8 +43863,8 @@ const BODY_TEXT_ELEMENT_NAMES$1 = new Set([
|
|
|
43678
43863
|
"p",
|
|
43679
43864
|
"td"
|
|
43680
43865
|
]);
|
|
43681
|
-
const
|
|
43682
|
-
const
|
|
43866
|
+
const CASED_LETTER_PATTERN = /[\p{Lu}\p{Ll}\p{Lt}]/u;
|
|
43867
|
+
const UPPERCASE_LETTER_PATTERN = /\p{Lu}/u;
|
|
43683
43868
|
const hasUppercaseStyle = (node) => {
|
|
43684
43869
|
const classNameValue = getStringFromClassNameAttr(node);
|
|
43685
43870
|
if (classNameValue && getUnvariantClassNameTokens(classNameValue).includes("uppercase")) return true;
|
|
@@ -43704,8 +43889,8 @@ const noAllCapsBodyText = defineRule({
|
|
|
43704
43889
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
|
|
43705
43890
|
if (!BODY_TEXT_ELEMENT_NAMES$1.has(openingElement.name.name)) return;
|
|
43706
43891
|
const staticText = getStaticJsxText(node).replace(/\s+/g, " ").trim();
|
|
43707
|
-
if (staticText.length < 48 || !
|
|
43708
|
-
if (
|
|
43892
|
+
if (staticText.length < 48 || !CASED_LETTER_PATTERN.test(staticText)) return;
|
|
43893
|
+
if (!(UPPERCASE_LETTER_PATTERN.test(staticText) && staticText === staticText.toUpperCase()) && !hasUppercaseStyle(openingElement)) return;
|
|
43709
43894
|
context.report({
|
|
43710
43895
|
node: openingElement,
|
|
43711
43896
|
message: "Long all-caps copy is difficult to scan. Use sentence case here and keep uppercase treatment for compact labels."
|
|
@@ -51164,6 +51349,23 @@ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @
|
|
|
51164
51349
|
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
51165
51350
|
};
|
|
51166
51351
|
//#endregion
|
|
51352
|
+
//#region src/plugin/utils/unwrap-proven-react-hoc-function.ts
|
|
51353
|
+
const unwrapProvenReactHocFunction = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
51354
|
+
if (!node) return null;
|
|
51355
|
+
const expression = stripParenExpression(node);
|
|
51356
|
+
if (isFunctionLike$1(expression)) return expression;
|
|
51357
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
51358
|
+
const symbol = scopes.symbolFor(expression);
|
|
51359
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer || hasSymbolWriteBefore(symbol, expression, scopes)) return null;
|
|
51360
|
+
visitedSymbolIds.add(symbol.id);
|
|
51361
|
+
return unwrapProvenReactHocFunction(symbol.initializer, scopes, visitedSymbolIds);
|
|
51362
|
+
}
|
|
51363
|
+
if (!isNodeOfType(expression, "CallExpression") || !isReactApiCall(expression, "memo", scopes, { resolveNamedAliases: true }) && !isReactApiCall(expression, "forwardRef", scopes, { resolveNamedAliases: true })) return null;
|
|
51364
|
+
const componentArgument = expression.arguments[0];
|
|
51365
|
+
if (!componentArgument || isNodeOfType(componentArgument, "SpreadElement")) return null;
|
|
51366
|
+
return unwrapProvenReactHocFunction(componentArgument, scopes, visitedSymbolIds);
|
|
51367
|
+
};
|
|
51368
|
+
//#endregion
|
|
51167
51369
|
//#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
|
|
51168
51370
|
const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
51169
51371
|
const functionExpression = findTransparentExpressionRoot(functionNode);
|
|
@@ -51276,22 +51478,6 @@ const getEnvironment = (program, filename, state) => {
|
|
|
51276
51478
|
state.environmentsByProgram.set(program, environment);
|
|
51277
51479
|
return environment;
|
|
51278
51480
|
};
|
|
51279
|
-
const unwrapProvenReactHocFunction = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
51280
|
-
if (!node) return null;
|
|
51281
|
-
const current = findTransparentExpressionRoot(node);
|
|
51282
|
-
if (isFunctionLike$1(current)) return current;
|
|
51283
|
-
if (isNodeOfType(current, "Identifier")) {
|
|
51284
|
-
const symbol = scopes.symbolFor(current);
|
|
51285
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer || hasSymbolWriteBefore(symbol, current, scopes)) return null;
|
|
51286
|
-
visitedSymbolIds.add(symbol.id);
|
|
51287
|
-
return unwrapProvenReactHocFunction(symbol.initializer, scopes, visitedSymbolIds);
|
|
51288
|
-
}
|
|
51289
|
-
if (!isNodeOfType(current, "CallExpression")) return null;
|
|
51290
|
-
if (!isProvenReactCall(current, "memo", scopes) && !isProvenReactCall(current, "forwardRef", scopes)) return null;
|
|
51291
|
-
const firstArgument = current.arguments[0];
|
|
51292
|
-
if (!firstArgument || isNodeOfType(firstArgument, "SpreadElement")) return null;
|
|
51293
|
-
return unwrapProvenReactHocFunction(firstArgument, scopes, visitedSymbolIds);
|
|
51294
|
-
};
|
|
51295
51481
|
const isForwardRefValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
51296
51482
|
const current = findTransparentExpressionRoot(node);
|
|
51297
51483
|
if (isNodeOfType(current, "Identifier")) {
|
|
@@ -74632,6 +74818,98 @@ const noPassLiveStateToParent = defineRule({
|
|
|
74632
74818
|
} })
|
|
74633
74819
|
});
|
|
74634
74820
|
//#endregion
|
|
74821
|
+
//#region src/plugin/rules/security/no-path-prefix-containment.ts
|
|
74822
|
+
const PATH_MODULES = new Set(["node:path", "path"]);
|
|
74823
|
+
const PATH_SEPARATORS = new Set(["/", "\\"]);
|
|
74824
|
+
const resolveStableExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
74825
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
74826
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return unwrappedExpression;
|
|
74827
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
74828
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return unwrappedExpression;
|
|
74829
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
74830
|
+
if (!initializer) return unwrappedExpression;
|
|
74831
|
+
visitedSymbolIds.add(symbol.id);
|
|
74832
|
+
return resolveStableExpression(initializer, context, visitedSymbolIds);
|
|
74833
|
+
};
|
|
74834
|
+
const areSameStableExpressions = (firstExpression, secondExpression, context) => areExpressionsStructurallyEqual(resolveStableExpression(firstExpression, context), resolveStableExpression(secondExpression, context), { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
|
|
74835
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier")) return false;
|
|
74836
|
+
const firstSymbol = context.scopes.symbolFor(firstIdentifier);
|
|
74837
|
+
const secondSymbol = context.scopes.symbolFor(secondIdentifier);
|
|
74838
|
+
if (firstSymbol || secondSymbol) {
|
|
74839
|
+
if (!firstSymbol || firstSymbol !== secondSymbol) return false;
|
|
74840
|
+
const earlierIdentifier = firstIdentifier.range[0] <= secondIdentifier.range[0] ? firstIdentifier : secondIdentifier;
|
|
74841
|
+
return !hasBindingWriteBetween(earlierIdentifier, earlierIdentifier, earlierIdentifier === firstIdentifier ? secondIdentifier : firstIdentifier, context.scopes);
|
|
74842
|
+
}
|
|
74843
|
+
return firstIdentifier.name === secondIdentifier.name && context.scopes.isGlobalReference(firstIdentifier) && context.scopes.isGlobalReference(secondIdentifier);
|
|
74844
|
+
} });
|
|
74845
|
+
const getResolvedPathCall = (expression, context) => {
|
|
74846
|
+
const resolvedExpression = resolveStableExpression(expression, context);
|
|
74847
|
+
if (!isNodeOfType(resolvedExpression, "CallExpression")) return null;
|
|
74848
|
+
const importedApi = resolveImportedApiReference(resolvedExpression.callee, context.scopes);
|
|
74849
|
+
if (!importedApi || !PATH_MODULES.has(importedApi.source) || importedApi.importedName !== "resolve") return null;
|
|
74850
|
+
return resolvedExpression;
|
|
74851
|
+
};
|
|
74852
|
+
const isPathSeparatorExpression = (expression, context) => {
|
|
74853
|
+
const resolvedExpression = resolveStableExpression(expression, context);
|
|
74854
|
+
if (isNodeOfType(resolvedExpression, "Literal") && typeof resolvedExpression.value === "string") return PATH_SEPARATORS.has(resolvedExpression.value);
|
|
74855
|
+
const importedApi = resolveImportedApiReference(resolvedExpression, context.scopes);
|
|
74856
|
+
return Boolean(importedApi && PATH_MODULES.has(importedApi.source) && importedApi.importedName === "sep");
|
|
74857
|
+
};
|
|
74858
|
+
const hasPathSeparatorSuffix = (expression, context) => {
|
|
74859
|
+
const resolvedExpression = resolveStableExpression(expression, context);
|
|
74860
|
+
if (isNodeOfType(resolvedExpression, "Literal") && typeof resolvedExpression.value === "string") return [...PATH_SEPARATORS].some((separator) => resolvedExpression.value.endsWith(separator));
|
|
74861
|
+
if (isNodeOfType(resolvedExpression, "BinaryExpression")) return resolvedExpression.operator === "+" && isPathSeparatorExpression(resolvedExpression.right, context);
|
|
74862
|
+
if (!isNodeOfType(resolvedExpression, "TemplateLiteral")) return false;
|
|
74863
|
+
const trailingQuasi = resolvedExpression.quasis.at(-1);
|
|
74864
|
+
const trailingText = trailingQuasi?.value.cooked ?? trailingQuasi?.value.raw ?? "";
|
|
74865
|
+
if ([...PATH_SEPARATORS].some((separator) => trailingText.endsWith(separator))) return true;
|
|
74866
|
+
if (trailingText !== "") return false;
|
|
74867
|
+
const trailingExpression = resolvedExpression.expressions.at(-1);
|
|
74868
|
+
return Boolean(trailingExpression && isPathSeparatorExpression(trailingExpression, context));
|
|
74869
|
+
};
|
|
74870
|
+
const isPathSeparatorSuffixedVersionOf = (suffixedExpression, bareExpression, context) => {
|
|
74871
|
+
const resolvedSuffixedExpression = resolveStableExpression(suffixedExpression, context);
|
|
74872
|
+
const resolvedBareExpression = resolveStableExpression(bareExpression, context);
|
|
74873
|
+
const suffixedStaticValue = getStaticStringExpression$1(resolvedSuffixedExpression);
|
|
74874
|
+
const bareStaticValue = getStaticStringExpression$1(resolvedBareExpression);
|
|
74875
|
+
if (suffixedStaticValue !== null && bareStaticValue !== null) return [...PATH_SEPARATORS].some((separator) => suffixedStaticValue === `${bareStaticValue}${separator}`);
|
|
74876
|
+
if (isNodeOfType(resolvedSuffixedExpression, "BinaryExpression") && resolvedSuffixedExpression.operator === "+" && isPathSeparatorExpression(resolvedSuffixedExpression.right, context)) return areSameStableExpressions(resolvedSuffixedExpression.left, bareExpression, context);
|
|
74877
|
+
if (!isNodeOfType(resolvedSuffixedExpression, "TemplateLiteral")) return false;
|
|
74878
|
+
const trailingQuasi = resolvedSuffixedExpression.quasis.at(-1);
|
|
74879
|
+
const trailingText = trailingQuasi?.value.cooked ?? trailingQuasi?.value.raw ?? "";
|
|
74880
|
+
if (resolvedSuffixedExpression.expressions.length === 1 && resolvedSuffixedExpression.quasis.length === 2 && (resolvedSuffixedExpression.quasis[0]?.value.cooked ?? resolvedSuffixedExpression.quasis[0]?.value.raw ?? "") === "" && PATH_SEPARATORS.has(trailingText)) {
|
|
74881
|
+
const baseExpression = resolvedSuffixedExpression.expressions[0];
|
|
74882
|
+
return Boolean(baseExpression && areSameStableExpressions(baseExpression, bareExpression, context));
|
|
74883
|
+
}
|
|
74884
|
+
if (resolvedSuffixedExpression.expressions.length !== 2 || resolvedSuffixedExpression.quasis.some((quasi) => (quasi.value.cooked ?? quasi.value.raw ?? "") !== "")) return false;
|
|
74885
|
+
const baseExpression = resolvedSuffixedExpression.expressions[0];
|
|
74886
|
+
const separatorExpression = resolvedSuffixedExpression.expressions[1];
|
|
74887
|
+
return Boolean(baseExpression && separatorExpression && isPathSeparatorExpression(separatorExpression, context) && areSameStableExpressions(baseExpression, bareExpression, context));
|
|
74888
|
+
};
|
|
74889
|
+
const noPathPrefixContainment = defineRule({
|
|
74890
|
+
id: "no-path-prefix-containment",
|
|
74891
|
+
title: "Path containment check uses a string prefix",
|
|
74892
|
+
tags: ["test-noise"],
|
|
74893
|
+
severity: "warn",
|
|
74894
|
+
recommendation: "Use `path.relative(root, candidate)` and reject `..` or absolute results instead of comparing path strings with the bare root prefix.",
|
|
74895
|
+
create: (context) => ({ CallExpression(node) {
|
|
74896
|
+
if (!isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "startsWith") return;
|
|
74897
|
+
if (node.arguments.length !== 1) return;
|
|
74898
|
+
const prefixExpression = node.arguments?.[0];
|
|
74899
|
+
if (!prefixExpression || isNodeOfType(prefixExpression, "SpreadElement")) return;
|
|
74900
|
+
if (hasPathSeparatorSuffix(prefixExpression, context)) return;
|
|
74901
|
+
const resolvedPathCall = getResolvedPathCall(node.callee.object, context);
|
|
74902
|
+
if (!resolvedPathCall || resolvedPathCall.arguments.length < 2) return;
|
|
74903
|
+
const rootExpression = resolvedPathCall.arguments[0];
|
|
74904
|
+
if (!rootExpression || isNodeOfType(rootExpression, "SpreadElement")) return;
|
|
74905
|
+
if (!areSameStableExpressions(rootExpression, prefixExpression, context) && !isPathSeparatorSuffixedVersionOf(rootExpression, prefixExpression, context)) return;
|
|
74906
|
+
context.report({
|
|
74907
|
+
node,
|
|
74908
|
+
message: "A bare path string prefix also accepts sibling paths such as `<root>-backup`. Use a boundary-aware path containment check."
|
|
74909
|
+
});
|
|
74910
|
+
} })
|
|
74911
|
+
});
|
|
74912
|
+
//#endregion
|
|
74635
74913
|
//#region src/plugin/rules/performance/no-permanent-will-change.ts
|
|
74636
74914
|
const isPermanentWillChangeClass = (token) => {
|
|
74637
74915
|
const utility = token.startsWith("!") ? token.slice(1) : token;
|
|
@@ -99787,9 +100065,9 @@ const rawSqlInjectionRisk = defineRule({
|
|
|
99787
100065
|
//#endregion
|
|
99788
100066
|
//#region src/plugin/rules/architecture/react-compiler-no-manual-memoization.ts
|
|
99789
100067
|
const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
|
|
99790
|
-
["useMemo", "
|
|
99791
|
-
["useCallback", "
|
|
99792
|
-
["memo", "
|
|
100068
|
+
["useMemo", "React Compiler can cache this value automatically. Verify that removing `useMemo` preserves behavior before simplifying it."],
|
|
100069
|
+
["useCallback", "React Compiler can cache this function automatically. Verify that removing `useCallback` preserves behavior before simplifying it."],
|
|
100070
|
+
["memo", "React Compiler can cache this component output automatically. Verify that removing `memo()` preserves behavior before simplifying it."]
|
|
99793
100071
|
]);
|
|
99794
100072
|
const resolveReactApiNameForIdentifier = (callee, context) => {
|
|
99795
100073
|
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
@@ -99835,10 +100113,10 @@ const isCompilerInferableFunction = (functionNode) => {
|
|
|
99835
100113
|
};
|
|
99836
100114
|
const reactCompilerNoManualMemoization = defineRule({
|
|
99837
100115
|
id: "react-compiler-no-manual-memoization",
|
|
99838
|
-
title: "
|
|
100116
|
+
title: "Manual memoization in compiler-managed code",
|
|
99839
100117
|
severity: "warn",
|
|
99840
100118
|
requires: ["react-compiler"],
|
|
99841
|
-
recommendation: "
|
|
100119
|
+
recommendation: "Profile compiler-managed code and remove `useMemo`, `useCallback`, or `memo` only when the manual cache no longer carries behavioral or performance intent.",
|
|
99842
100120
|
create: (context) => ({ CallExpression(node) {
|
|
99843
100121
|
const apiName = resolveReactApiNameForCallee(node.callee, context);
|
|
99844
100122
|
if (!apiName) return;
|
|
@@ -106446,7 +106724,7 @@ const rnAnimateLayoutProperty = defineRetiredRule({
|
|
|
106446
106724
|
});
|
|
106447
106725
|
//#endregion
|
|
106448
106726
|
//#region src/plugin/rules/react-native/rn-animation-reaction-as-derived.ts
|
|
106449
|
-
const REANIMATED_MODULE_SOURCE = "react-native-reanimated";
|
|
106727
|
+
const REANIMATED_MODULE_SOURCE$1 = "react-native-reanimated";
|
|
106450
106728
|
const rnAnimationReactionAsDerived = defineRule({
|
|
106451
106729
|
id: "rn-animation-reaction-as-derived",
|
|
106452
106730
|
title: "useAnimatedReaction just copies a value",
|
|
@@ -106456,7 +106734,7 @@ const rnAnimationReactionAsDerived = defineRule({
|
|
|
106456
106734
|
recommendation: "This useAnimatedReaction just copies one value to another. Replace it with `useDerivedValue(() => ..., [deps])`, which is shorter and tracks changes for you.",
|
|
106457
106735
|
create: (context) => ({ CallExpression(node) {
|
|
106458
106736
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedReaction") return;
|
|
106459
|
-
if (getImportSourceForName(node, node.callee.name) !== REANIMATED_MODULE_SOURCE) return;
|
|
106737
|
+
if (getImportSourceForName(node, node.callee.name) !== REANIMATED_MODULE_SOURCE$1) return;
|
|
106460
106738
|
const reactionFn = node.arguments?.[1];
|
|
106461
106739
|
if (!reactionFn) return;
|
|
106462
106740
|
if (!isNodeOfType(reactionFn, "ArrowFunctionExpression") && !isNodeOfType(reactionFn, "FunctionExpression")) return;
|
|
@@ -106480,6 +106758,69 @@ const rnAnimationReactionAsDerived = defineRule({
|
|
|
106480
106758
|
} })
|
|
106481
106759
|
});
|
|
106482
106760
|
//#endregion
|
|
106761
|
+
//#region src/plugin/rules/react-native/rn-bottom-sheet-no-ignored-scroll-prop.ts
|
|
106762
|
+
const GORHOM_BOTTOM_SHEET_MODULE$2 = "@gorhom/bottom-sheet";
|
|
106763
|
+
const IGNORED_SCROLL_PROPERTY_NAMES = new Set([
|
|
106764
|
+
"decelerationRate",
|
|
106765
|
+
"onScrollBeginDrag",
|
|
106766
|
+
"scrollEventThrottle"
|
|
106767
|
+
]);
|
|
106768
|
+
const rnBottomSheetNoIgnoredScrollProp = defineRule({
|
|
106769
|
+
id: "rn-bottom-sheet-no-ignored-scroll-prop",
|
|
106770
|
+
title: "Ignored BottomSheetScrollView prop",
|
|
106771
|
+
requires: ["react-native"],
|
|
106772
|
+
severity: "warn",
|
|
106773
|
+
recommendation: "Remove scrollEventThrottle, decelerationRate, and onScrollBeginDrag from BottomSheetScrollView because the component ignores them.",
|
|
106774
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
106775
|
+
if (resolveImportedJsxComponentName(node, GORHOM_BOTTOM_SHEET_MODULE$2, context.scopes) !== "BottomSheetScrollView") return;
|
|
106776
|
+
for (const attribute of node.attributes) {
|
|
106777
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
106778
|
+
const propertyName = getJsxAttributeName(attribute.name);
|
|
106779
|
+
if (!propertyName || !IGNORED_SCROLL_PROPERTY_NAMES.has(propertyName)) continue;
|
|
106780
|
+
context.report({
|
|
106781
|
+
node: attribute,
|
|
106782
|
+
message: `BottomSheetScrollView ignores \`${propertyName}\`, so this prop cannot affect scrolling. Remove it or handle the behavior outside the scrollable.`
|
|
106783
|
+
});
|
|
106784
|
+
}
|
|
106785
|
+
} })
|
|
106786
|
+
});
|
|
106787
|
+
//#endregion
|
|
106788
|
+
//#region src/plugin/rules/react-native/rn-bottom-sheet-no-state-in-on-animate.ts
|
|
106789
|
+
const GORHOM_BOTTOM_SHEET_MODULE$1 = "@gorhom/bottom-sheet";
|
|
106790
|
+
const BOTTOM_SHEET_CONTAINER_NAMES$1 = new Set([
|
|
106791
|
+
"BottomSheet",
|
|
106792
|
+
"BottomSheetModal",
|
|
106793
|
+
"default"
|
|
106794
|
+
]);
|
|
106795
|
+
const rnBottomSheetNoStateInOnAnimate = defineRule({
|
|
106796
|
+
id: "rn-bottom-sheet-no-state-in-on-animate",
|
|
106797
|
+
title: "React state update in Bottom Sheet onAnimate",
|
|
106798
|
+
requires: ["react-native"],
|
|
106799
|
+
severity: "warn",
|
|
106800
|
+
recommendation: "Avoid starting React renders from onAnimate. Use animatedIndex or animatedPosition for animation-coupled UI, or onChange for committed index state.",
|
|
106801
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
106802
|
+
const componentName = resolveImportedJsxComponentName(node, GORHOM_BOTTOM_SHEET_MODULE$1, context.scopes);
|
|
106803
|
+
if (!componentName || !BOTTOM_SHEET_CONTAINER_NAMES$1.has(componentName)) return;
|
|
106804
|
+
const onAnimateAttribute = findJsxAttribute(node.attributes, "onAnimate");
|
|
106805
|
+
if (!onAnimateAttribute?.value || !isNodeOfType(onAnimateAttribute.value, "JSXExpressionContainer")) return;
|
|
106806
|
+
const handler = resolveExactLocalFunction(onAnimateAttribute.value.expression, context.scopes);
|
|
106807
|
+
if (!handler) return;
|
|
106808
|
+
let stateSetterCall = null;
|
|
106809
|
+
walkOwnFunctionScope(handler, (child) => {
|
|
106810
|
+
if (stateSetterCall) return false;
|
|
106811
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier")) return;
|
|
106812
|
+
if (!resolveReactUseStatePair(child.callee, context.scopes)) return;
|
|
106813
|
+
stateSetterCall = child;
|
|
106814
|
+
return false;
|
|
106815
|
+
});
|
|
106816
|
+
if (!stateSetterCall) return;
|
|
106817
|
+
context.report({
|
|
106818
|
+
node: stateSetterCall,
|
|
106819
|
+
message: "This onAnimate handler starts a React state update as the Bottom Sheet begins moving, adding render work to the transition. Use animatedIndex or animatedPosition for animation-coupled UI."
|
|
106820
|
+
});
|
|
106821
|
+
} })
|
|
106822
|
+
});
|
|
106823
|
+
//#endregion
|
|
106483
106824
|
//#region src/plugin/rules/react-native/rn-bottom-sheet-prefer-native.ts
|
|
106484
106825
|
const JS_BOTTOM_SHEET_PACKAGES = new Set([
|
|
106485
106826
|
"react-native-bottom-sheet",
|
|
@@ -106496,7 +106837,7 @@ const rnBottomSheetPreferNative = defineRule({
|
|
|
106496
106837
|
tags: ["test-noise"],
|
|
106497
106838
|
requires: ["react-native"],
|
|
106498
106839
|
severity: "warn",
|
|
106499
|
-
recommendation: "
|
|
106840
|
+
recommendation: "When native presentation fits the design, use `<Modal presentationStyle=\"formSheet\">` for platform-native gestures, accessibility, and presentation behavior.",
|
|
106500
106841
|
create: (context) => ({ ImportDeclaration(node) {
|
|
106501
106842
|
const source = node.source?.value;
|
|
106502
106843
|
if (typeof source !== "string" || !JS_BOTTOM_SHEET_PACKAGES.has(source)) return;
|
|
@@ -106508,6 +106849,45 @@ const rnBottomSheetPreferNative = defineRule({
|
|
|
106508
106849
|
} })
|
|
106509
106850
|
});
|
|
106510
106851
|
//#endregion
|
|
106852
|
+
//#region src/plugin/rules/react-native/rn-bottom-sheet-use-integrated-scrollable.ts
|
|
106853
|
+
const GORHOM_BOTTOM_SHEET_MODULE = "@gorhom/bottom-sheet";
|
|
106854
|
+
const REACT_NATIVE_MODULE$1 = "react-native";
|
|
106855
|
+
const BOTTOM_SHEET_CONTAINER_NAMES = new Set([
|
|
106856
|
+
"BottomSheet",
|
|
106857
|
+
"BottomSheetModal",
|
|
106858
|
+
"default"
|
|
106859
|
+
]);
|
|
106860
|
+
const REACT_NATIVE_SCROLLABLE_NAMES = new Set([
|
|
106861
|
+
"FlatList",
|
|
106862
|
+
"ScrollView",
|
|
106863
|
+
"SectionList",
|
|
106864
|
+
"VirtualizedList"
|
|
106865
|
+
]);
|
|
106866
|
+
const rnBottomSheetUseIntegratedScrollable = defineRule({
|
|
106867
|
+
id: "rn-bottom-sheet-use-integrated-scrollable",
|
|
106868
|
+
title: "React Native scrollable inside a Bottom Sheet",
|
|
106869
|
+
requires: ["react-native"],
|
|
106870
|
+
severity: "warn",
|
|
106871
|
+
recommendation: "Use @gorhom/bottom-sheet's integrated BottomSheetScrollView, BottomSheetFlatList, BottomSheetSectionList, or BottomSheetVirtualizedList so gestures coordinate with the sheet.",
|
|
106872
|
+
create: (context) => {
|
|
106873
|
+
const reportedScrollables = /* @__PURE__ */ new WeakSet();
|
|
106874
|
+
return { JSXElement(node) {
|
|
106875
|
+
const containerName = resolveImportedJsxComponentName(node.openingElement, GORHOM_BOTTOM_SHEET_MODULE, context.scopes);
|
|
106876
|
+
if (!containerName || !BOTTOM_SHEET_CONTAINER_NAMES.has(containerName)) return;
|
|
106877
|
+
for (const descendant of getStaticJsxDescendantOpeningElements(node, { includeStaticExpressionBranches: true })) {
|
|
106878
|
+
if (reportedScrollables.has(descendant)) continue;
|
|
106879
|
+
const scrollableName = resolveImportedJsxComponentName(descendant, REACT_NATIVE_MODULE$1, context.scopes);
|
|
106880
|
+
if (!scrollableName || !REACT_NATIVE_SCROLLABLE_NAMES.has(scrollableName)) continue;
|
|
106881
|
+
reportedScrollables.add(descendant);
|
|
106882
|
+
context.report({
|
|
106883
|
+
node: descendant,
|
|
106884
|
+
message: `React Native's \`${scrollableName}\` does not coordinate gestures with this Bottom Sheet. Use \`BottomSheet${scrollableName}\` from @gorhom/bottom-sheet.`
|
|
106885
|
+
});
|
|
106886
|
+
}
|
|
106887
|
+
} };
|
|
106888
|
+
}
|
|
106889
|
+
});
|
|
106890
|
+
//#endregion
|
|
106511
106891
|
//#region src/plugin/rules/react-native/rn-detox-missing-await.ts
|
|
106512
106892
|
const EMPTY_VISITORS$6 = {};
|
|
106513
106893
|
const DETOX_TEST_FILE = /(\.e2e\.[cm]?[jt]sx?$)|((^|\/)e2e\/)/;
|
|
@@ -106797,8 +107177,20 @@ const REACT_NATIVE_BUILTIN_LIST_COMPONENTS = new Set([
|
|
|
106797
107177
|
]);
|
|
106798
107178
|
const RECYCLABLE_LIST_PACKAGES = {
|
|
106799
107179
|
FlashList: ["@shopify/flash-list"],
|
|
106800
|
-
|
|
106801
|
-
|
|
107180
|
+
AnimatedFlashList: ["@shopify/flash-list"],
|
|
107181
|
+
LegendList: ["@legendapp/list", "@legendapp/list/react-native"],
|
|
107182
|
+
AnimatedLegendList: ["@legendapp/list/animated", "@legendapp/list/reanimated"],
|
|
107183
|
+
KeyboardAwareLegendList: ["@legendapp/list/keyboard"],
|
|
107184
|
+
KeyboardAvoidingLegendList: ["@legendapp/list/keyboard-legacy"]
|
|
107185
|
+
};
|
|
107186
|
+
const SHOPIFY_FLASH_LIST_COMPONENTS = new Set(["FlashList", "AnimatedFlashList"]);
|
|
107187
|
+
const LEGEND_LIST_V3_PACKAGE_SOURCES = new Set([
|
|
107188
|
+
"@legendapp/list/react-native",
|
|
107189
|
+
"@legendapp/list/animated",
|
|
107190
|
+
"@legendapp/list/reanimated",
|
|
107191
|
+
"@legendapp/list/keyboard",
|
|
107192
|
+
"@legendapp/list/keyboard-legacy"
|
|
107193
|
+
]);
|
|
106802
107194
|
const RECYCLABLE_LIST_PACKAGE_SOURCES = Object.values(RECYCLABLE_LIST_PACKAGES).flat();
|
|
106803
107195
|
const REACT_NATIVE_LIST_COMPONENTS = new Set([...REACT_NATIVE_BUILTIN_LIST_COMPONENTS, ...Object.keys(RECYCLABLE_LIST_PACKAGES)]);
|
|
106804
107196
|
const RENDER_ITEM_PROP_NAMES = new Set([
|
|
@@ -106886,21 +107278,9 @@ const getInitializerModuleSource = (contextNode, initializer) => {
|
|
|
106886
107278
|
};
|
|
106887
107279
|
//#endregion
|
|
106888
107280
|
//#region src/plugin/rules/react-native/utils/resolve-imported-recycler-name.ts
|
|
106889
|
-
const
|
|
106890
|
-
if (
|
|
106891
|
-
const
|
|
106892
|
-
if (!elementName || !isNodeOfType(elementName, "JSXMemberExpression")) return null;
|
|
106893
|
-
return isNodeOfType(elementName.object, "JSXIdentifier") ? elementName.object.name : null;
|
|
106894
|
-
};
|
|
106895
|
-
const resolveImportedRecyclerName = (node, localName, options) => {
|
|
106896
|
-
const jsxMemberObjectName = options?.allowNamespaceMemberAccess ? getJsxMemberObjectName(node) : null;
|
|
106897
|
-
for (const [canonicalName, packageSources] of Object.entries(RECYCLABLE_LIST_PACKAGES)) {
|
|
106898
|
-
if (jsxMemberObjectName !== null) {
|
|
106899
|
-
if (localName === canonicalName && packageSources.some((packageSource) => isNamespaceImportFromModule$1(node, jsxMemberObjectName, packageSource))) return canonicalName;
|
|
106900
|
-
continue;
|
|
106901
|
-
}
|
|
106902
|
-
if (packageSources.some((packageSource) => getImportedNameFromModule(node, localName, packageSource) === canonicalName)) return canonicalName;
|
|
106903
|
-
}
|
|
107281
|
+
const resolveImportedRecyclerName = (node, scopes, options) => {
|
|
107282
|
+
if (isNodeOfType(node.name, "JSXMemberExpression") && !options?.allowNamespaceMemberAccess) return null;
|
|
107283
|
+
for (const [canonicalName, packageSources] of Object.entries(RECYCLABLE_LIST_PACKAGES)) for (const packageSource of packageSources) if (resolveImportedJsxComponentName(node, packageSource, scopes) === canonicalName) return canonicalName;
|
|
106904
107284
|
return null;
|
|
106905
107285
|
};
|
|
106906
107286
|
//#endregion
|
|
@@ -106920,8 +107300,8 @@ const isLocalBindingReactNativeList = (node, elementName) => {
|
|
|
106920
107300
|
const initializerModuleSource = getInitializerModuleSource(node, declaratorInitializer);
|
|
106921
107301
|
return initializerModuleSource !== null && REACT_NATIVE_LIST_MODULE_SOURCES.has(initializerModuleSource);
|
|
106922
107302
|
};
|
|
106923
|
-
const isVirtualizedList = (node, elementName) => {
|
|
106924
|
-
if (resolveImportedRecyclerName(node,
|
|
107303
|
+
const isVirtualizedList = (node, elementName, scopes) => {
|
|
107304
|
+
if (resolveImportedRecyclerName(node, scopes, { allowNamespaceMemberAccess: true }) !== null) return true;
|
|
106925
107305
|
if (isNodeOfType(node.name, "JSXMemberExpression")) {
|
|
106926
107306
|
if (!REACT_NATIVE_BUILTIN_LIST_COMPONENTS.has(elementName)) return false;
|
|
106927
107307
|
const memberObjectName = getJsxMemberRootObjectName(node.name);
|
|
@@ -106978,7 +107358,7 @@ const rnListDataMapped = defineRule({
|
|
|
106978
107358
|
recommendation: "This builds a new array each time the parent redraws, so every row redraws too. Wrap it in `useMemo(() => items.map(...), [items])` to keep the same array.",
|
|
106979
107359
|
create: (context) => ({ JSXOpeningElement(node) {
|
|
106980
107360
|
const elementName = resolveJsxElementName(node);
|
|
106981
|
-
if (!elementName || !isVirtualizedList(node, elementName)) return;
|
|
107361
|
+
if (!elementName || !isVirtualizedList(node, elementName, context.scopes)) return;
|
|
106982
107362
|
for (const attr of node.attributes ?? []) {
|
|
106983
107363
|
if (!isNodeOfType(attr, "JSXAttribute")) continue;
|
|
106984
107364
|
if (!isNodeOfType(attr.name, "JSXIdentifier") || attr.name.name !== "data") continue;
|
|
@@ -106994,12 +107374,14 @@ const rnListDataMapped = defineRule({
|
|
|
106994
107374
|
} })
|
|
106995
107375
|
});
|
|
106996
107376
|
//#endregion
|
|
106997
|
-
//#region src/plugin/rules/react-native/
|
|
106998
|
-
const SIZING_HINT_ATTRIBUTE_NAMES = new Set(["estimatedItemSize", "estimatedListSize"]);
|
|
107377
|
+
//#region src/plugin/rules/react-native/utils/is-flash-list-v2-or-newer.ts
|
|
106999
107378
|
const isFlashListV2OrNewer = (context) => {
|
|
107000
107379
|
const flashListMajorVersion = getReactDoctorNumberSetting(context.settings, "shopifyFlashListMajorVersion");
|
|
107001
107380
|
return flashListMajorVersion !== void 0 && flashListMajorVersion >= 2;
|
|
107002
107381
|
};
|
|
107382
|
+
//#endregion
|
|
107383
|
+
//#region src/plugin/rules/react-native/rn-list-missing-estimated-item-size.ts
|
|
107384
|
+
const SIZING_HINT_ATTRIBUTE_NAMES = new Set(["estimatedItemSize", "estimatedListSize"]);
|
|
107003
107385
|
const isEmptyArrayLiteral = (node) => {
|
|
107004
107386
|
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return false;
|
|
107005
107387
|
const expression = node.value.expression;
|
|
@@ -107022,9 +107404,10 @@ const rnListMissingEstimatedItemSize = defineRule({
|
|
|
107022
107404
|
if (!fileImportsRecycler) return;
|
|
107023
107405
|
const localElementName = resolveJsxElementName(node);
|
|
107024
107406
|
if (!localElementName) return;
|
|
107025
|
-
const canonicalRecyclerName = resolveImportedRecyclerName(node,
|
|
107407
|
+
const canonicalRecyclerName = resolveImportedRecyclerName(node, context.scopes);
|
|
107026
107408
|
if (canonicalRecyclerName === null) return;
|
|
107027
|
-
if (
|
|
107409
|
+
if ([...LEGEND_LIST_V3_PACKAGE_SOURCES].some((packageSource) => resolveImportedJsxComponentName(node, packageSource, context.scopes) !== null)) return;
|
|
107410
|
+
if (SHOPIFY_FLASH_LIST_COMPONENTS.has(canonicalRecyclerName) && isFlashListV2OrNewer(context)) return;
|
|
107028
107411
|
let hasSizingHint = false;
|
|
107029
107412
|
let dataIsEmptyLiteral = false;
|
|
107030
107413
|
let hasDataProp = false;
|
|
@@ -107051,15 +107434,1272 @@ const rnListMissingEstimatedItemSize = defineRule({
|
|
|
107051
107434
|
});
|
|
107052
107435
|
//#endregion
|
|
107053
107436
|
//#region src/plugin/rules/react-native/rn-list-recyclable-without-types.ts
|
|
107437
|
+
const RENDER_ITEM_INPUT_NAMES = new Set(["item", "index"]);
|
|
107438
|
+
const EMPTY_RENDERED_ROOT_NAME = "empty";
|
|
107439
|
+
const isSymbolStable = (symbol) => symbol.references.every((reference) => reference.flag === "read");
|
|
107440
|
+
const getSymbolVariableDeclarator = (symbol) => {
|
|
107441
|
+
let declaration = symbol.declarationNode;
|
|
107442
|
+
while (declaration && !isNodeOfType(declaration, "VariableDeclarator")) declaration = declaration.parent;
|
|
107443
|
+
return declaration && isNodeOfType(declaration, "VariableDeclarator") ? declaration : null;
|
|
107444
|
+
};
|
|
107445
|
+
const getConstInitializerExpressions = (symbol) => {
|
|
107446
|
+
if (symbol.kind !== "const" || !symbol.initializer) return [];
|
|
107447
|
+
const declarationInitializer = getSymbolVariableDeclarator(symbol)?.init;
|
|
107448
|
+
return declarationInitializer && declarationInitializer !== symbol.initializer ? [declarationInitializer, symbol.initializer] : [symbol.initializer];
|
|
107449
|
+
};
|
|
107450
|
+
const getSymbolIdentity = (symbol) => {
|
|
107451
|
+
if (symbol.kind !== "import") return `symbol:${symbol.id}`;
|
|
107452
|
+
const source = getImportDeclarationForSymbol(symbol)?.source.value;
|
|
107453
|
+
if (typeof source !== "string") return `symbol:${symbol.id}`;
|
|
107454
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return `import:${source}:default`;
|
|
107455
|
+
if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return `import:${source}:*`;
|
|
107456
|
+
return `import:${source}:${getImportedName(symbol.declarationNode) ?? symbol.name}`;
|
|
107457
|
+
};
|
|
107458
|
+
const appendComponentMemberIdentity = (receiverIdentity, propertyName) => {
|
|
107459
|
+
if (!receiverIdentity || !receiverIdentity.startsWith("import:") && !receiverIdentity.startsWith("global:")) return null;
|
|
107460
|
+
if (receiverIdentity.endsWith(":*")) return `${receiverIdentity.slice(0, -1)}${propertyName}`;
|
|
107461
|
+
return `${receiverIdentity}.${propertyName}`;
|
|
107462
|
+
};
|
|
107463
|
+
const getComponentReferenceIdentity = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
107464
|
+
const componentReference = stripParenExpression(expression);
|
|
107465
|
+
if (isNodeOfType(componentReference, "Identifier") || isNodeOfType(componentReference, "JSXIdentifier")) {
|
|
107466
|
+
const symbol = scopes.symbolFor(componentReference);
|
|
107467
|
+
if (!symbol) return `global:${componentReference.name}`;
|
|
107468
|
+
if (visitedSymbolIds.has(symbol.id) || !isSymbolStable(symbol)) return null;
|
|
107469
|
+
visitedSymbolIds.add(symbol.id);
|
|
107470
|
+
if (symbol.kind === "import" || symbol.kind === "function" || symbol.kind === "class") return getSymbolIdentity(symbol);
|
|
107471
|
+
if (symbol.kind !== "const" || !symbol.initializer) return null;
|
|
107472
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
107473
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
107474
|
+
if (destructuredPropertyName) return appendComponentMemberIdentity(getComponentReferenceIdentity(initializer, scopes, visitedSymbolIds), destructuredPropertyName);
|
|
107475
|
+
const isProvenReactHocCall = isNodeOfType(initializer, "CallExpression") && (isReactApiCall(initializer, "memo", scopes, { resolveNamedAliases: true }) || isReactApiCall(initializer, "forwardRef", scopes, { resolveNamedAliases: true })) && initializer.arguments[0] !== void 0 && !isNodeOfType(initializer.arguments[0], "SpreadElement");
|
|
107476
|
+
if (isFunctionLike$1(initializer) || isNodeOfType(initializer, "ClassExpression") || isProvenReactHocCall) return getSymbolIdentity(symbol);
|
|
107477
|
+
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return null;
|
|
107478
|
+
return getComponentReferenceIdentity(initializer, scopes, visitedSymbolIds);
|
|
107479
|
+
}
|
|
107480
|
+
if (!isNodeOfType(componentReference, "MemberExpression")) return null;
|
|
107481
|
+
const propertyName = getStaticPropertyName(componentReference);
|
|
107482
|
+
if (propertyName === null) return null;
|
|
107483
|
+
return appendComponentMemberIdentity(getComponentReferenceIdentity(componentReference.object, scopes, visitedSymbolIds), propertyName);
|
|
107484
|
+
};
|
|
107485
|
+
const getJsxElementIdentity = (node, scopes) => {
|
|
107486
|
+
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
107487
|
+
const identity = getComponentReferenceIdentity(node, scopes);
|
|
107488
|
+
if (identity !== `global:${node.name}`) return identity;
|
|
107489
|
+
return /^[a-z]/u.test(node.name) ? `intrinsic:${node.name}` : identity;
|
|
107490
|
+
}
|
|
107491
|
+
if (!isNodeOfType(node, "JSXMemberExpression")) return null;
|
|
107492
|
+
const objectIdentity = getJsxElementIdentity(node.object, scopes);
|
|
107493
|
+
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
107494
|
+
return appendComponentMemberIdentity(objectIdentity, node.property.name);
|
|
107495
|
+
};
|
|
107496
|
+
const isStaticallyEmptyJsxChild = (node) => {
|
|
107497
|
+
const expression = getFinalSequenceExpressionValue(node);
|
|
107498
|
+
if (isNodeOfType(expression, "JSXEmptyExpression")) return true;
|
|
107499
|
+
if (isNodeOfType(expression, "Literal") && (expression.value === null || typeof expression.value === "boolean")) return true;
|
|
107500
|
+
if (isNodeOfType(expression, "UnaryExpression")) return expression.operator === "!" || expression.operator === "void";
|
|
107501
|
+
if (!isNodeOfType(expression, "BinaryExpression")) return false;
|
|
107502
|
+
switch (expression.operator) {
|
|
107503
|
+
case "==":
|
|
107504
|
+
case "!=":
|
|
107505
|
+
case "===":
|
|
107506
|
+
case "!==":
|
|
107507
|
+
case "<":
|
|
107508
|
+
case "<=":
|
|
107509
|
+
case ">":
|
|
107510
|
+
case ">=":
|
|
107511
|
+
case "in":
|
|
107512
|
+
case "instanceof": return true;
|
|
107513
|
+
default: return false;
|
|
107514
|
+
}
|
|
107515
|
+
};
|
|
107516
|
+
const getStaticSelectorBindingPath = (pattern, bindingIdentifier, scopes) => {
|
|
107517
|
+
if (pattern === bindingIdentifier) return [];
|
|
107518
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return readStaticSelectorTruthiness(pattern.right, scopes) === false ? getStaticSelectorBindingPath(pattern.left, bindingIdentifier, scopes) : null;
|
|
107519
|
+
if (isNodeOfType(pattern, "RestElement")) return null;
|
|
107520
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
107521
|
+
for (const [elementIndex, element] of pattern.elements.entries()) {
|
|
107522
|
+
if (!element) continue;
|
|
107523
|
+
const nestedPath = getStaticSelectorBindingPath(element, bindingIdentifier, scopes);
|
|
107524
|
+
if (nestedPath !== null) return [String(elementIndex), ...nestedPath];
|
|
107525
|
+
}
|
|
107526
|
+
return null;
|
|
107527
|
+
}
|
|
107528
|
+
if (!isNodeOfType(pattern, "ObjectPattern")) return null;
|
|
107529
|
+
for (const property of pattern.properties) {
|
|
107530
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
107531
|
+
const nestedPath = getStaticSelectorBindingPath(property.value, bindingIdentifier, scopes);
|
|
107532
|
+
if (nestedPath === null) continue;
|
|
107533
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
107534
|
+
return propertyName === null ? null : [propertyName, ...nestedPath];
|
|
107535
|
+
}
|
|
107536
|
+
return null;
|
|
107537
|
+
};
|
|
107538
|
+
const getStaticSelectorPropertyName = (memberExpression) => getStaticPropertyName(memberExpression) ?? getStaticPropertyKeyName(memberExpression, {
|
|
107539
|
+
allowComputedString: true,
|
|
107540
|
+
stringifyNonStringLiterals: true
|
|
107541
|
+
});
|
|
107542
|
+
const getSelectorReferenceKey = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
107543
|
+
const selector = stripParenExpression(expression);
|
|
107544
|
+
if (isNodeOfType(selector, "Identifier")) {
|
|
107545
|
+
const symbol = scopes.referenceFor(selector)?.resolvedSymbol;
|
|
107546
|
+
if (!symbol || !isSymbolStable(symbol) || visitedSymbolIds.has(symbol.id)) return null;
|
|
107547
|
+
if (symbol.kind === "const" && symbol.initializer) {
|
|
107548
|
+
visitedSymbolIds.add(symbol.id);
|
|
107549
|
+
const declaration = getSymbolVariableDeclarator(symbol);
|
|
107550
|
+
if (declaration?.id === symbol.bindingIdentifier) return getSelectorReferenceKey(symbol.initializer, scopes, visitedSymbolIds) ?? JSON.stringify(["symbol", symbol.id]);
|
|
107551
|
+
if (declaration?.init) {
|
|
107552
|
+
const bindingPath = getStaticSelectorBindingPath(declaration.id, symbol.bindingIdentifier, scopes);
|
|
107553
|
+
let receiverKey = getSelectorReferenceKey(declaration.init, scopes, visitedSymbolIds);
|
|
107554
|
+
if (bindingPath !== null && receiverKey !== null) {
|
|
107555
|
+
for (const propertyName of bindingPath) receiverKey = JSON.stringify([
|
|
107556
|
+
"member",
|
|
107557
|
+
receiverKey,
|
|
107558
|
+
propertyName
|
|
107559
|
+
]);
|
|
107560
|
+
return receiverKey;
|
|
107561
|
+
}
|
|
107562
|
+
}
|
|
107563
|
+
return JSON.stringify(["symbol", symbol.id]);
|
|
107564
|
+
}
|
|
107565
|
+
return JSON.stringify(["symbol", symbol.id]);
|
|
107566
|
+
}
|
|
107567
|
+
if (!isNodeOfType(selector, "MemberExpression")) return null;
|
|
107568
|
+
const propertyName = getStaticSelectorPropertyName(selector);
|
|
107569
|
+
if (propertyName === null) return null;
|
|
107570
|
+
const receiverKey = getSelectorReferenceKey(selector.object, scopes, visitedSymbolIds);
|
|
107571
|
+
return receiverKey === null ? null : JSON.stringify([
|
|
107572
|
+
"member",
|
|
107573
|
+
receiverKey,
|
|
107574
|
+
propertyName
|
|
107575
|
+
]);
|
|
107576
|
+
};
|
|
107577
|
+
const getStaticComparisonOperandKey = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
107578
|
+
const operand = stripParenExpression(expression);
|
|
107579
|
+
if (isNodeOfType(operand, "Identifier")) {
|
|
107580
|
+
const symbol = scopes.referenceFor(operand)?.resolvedSymbol;
|
|
107581
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && getSymbolVariableDeclarator(symbol)?.id === symbol.bindingIdentifier && !visitedSymbolIds.has(symbol.id)) {
|
|
107582
|
+
visitedSymbolIds.add(symbol.id);
|
|
107583
|
+
return getStaticComparisonOperandKey(symbol.initializer, scopes, visitedSymbolIds) ?? JSON.stringify(["symbol", symbol.id]);
|
|
107584
|
+
}
|
|
107585
|
+
}
|
|
107586
|
+
if (isNodeOfType(operand, "UnaryExpression") && operand.operator === "typeof") {
|
|
107587
|
+
const argumentKey = getSelectorReferenceKey(operand.argument, scopes, visitedSymbolIds);
|
|
107588
|
+
return argumentKey === null ? null : JSON.stringify(["typeof", argumentKey]);
|
|
107589
|
+
}
|
|
107590
|
+
return getSelectorReferenceKey(operand, scopes, visitedSymbolIds);
|
|
107591
|
+
};
|
|
107592
|
+
const getStaticPrimitiveLiteralKey = (expression) => {
|
|
107593
|
+
const literal = stripParenExpression(expression);
|
|
107594
|
+
if (isNodeOfType(literal, "UnaryExpression") && (literal.operator === "-" || literal.operator === "+")) {
|
|
107595
|
+
const argument = stripParenExpression(literal.argument);
|
|
107596
|
+
if (isNodeOfType(argument, "Literal") && typeof argument.value === "number") {
|
|
107597
|
+
const numericValue = literal.operator === "-" ? -argument.value : argument.value;
|
|
107598
|
+
return `number:${String(numericValue)}`;
|
|
107599
|
+
}
|
|
107600
|
+
}
|
|
107601
|
+
if (!isNodeOfType(literal, "Literal")) return null;
|
|
107602
|
+
if (literal.value === null) return "null";
|
|
107603
|
+
if (typeof literal.value === "string") return `string:${JSON.stringify(literal.value)}`;
|
|
107604
|
+
if (typeof literal.value === "number") return `number:${String(literal.value)}`;
|
|
107605
|
+
if (typeof literal.value === "boolean") return `boolean:${String(literal.value)}`;
|
|
107606
|
+
if (typeof literal.value === "bigint") return `bigint:${String(literal.value)}`;
|
|
107607
|
+
return null;
|
|
107608
|
+
};
|
|
107609
|
+
const getImportedStaticReferenceKey = (expression, scopes) => {
|
|
107610
|
+
const reference = stripParenExpression(expression);
|
|
107611
|
+
if (isNodeOfType(reference, "Identifier")) {
|
|
107612
|
+
const symbol = scopes.referenceFor(reference)?.resolvedSymbol;
|
|
107613
|
+
return symbol?.kind === "import" ? getSymbolIdentity(symbol) : null;
|
|
107614
|
+
}
|
|
107615
|
+
if (!isNodeOfType(reference, "MemberExpression")) return null;
|
|
107616
|
+
const propertyName = getStaticPropertyName(reference);
|
|
107617
|
+
if (propertyName === null) return null;
|
|
107618
|
+
const receiverKey = getImportedStaticReferenceKey(reference.object, scopes);
|
|
107619
|
+
return receiverKey === null ? null : JSON.stringify([
|
|
107620
|
+
"member",
|
|
107621
|
+
receiverKey,
|
|
107622
|
+
propertyName
|
|
107623
|
+
]);
|
|
107624
|
+
};
|
|
107625
|
+
const getStaticComparisonConstantKey = (expression, scopes) => getStaticPrimitiveLiteralKey(expression) ?? getImportedStaticReferenceKey(expression, scopes);
|
|
107626
|
+
const getStaticSelectorIdentity = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
107627
|
+
const selector = getFinalSequenceExpressionValue(expression);
|
|
107628
|
+
if (isNodeOfType(selector, "Identifier")) {
|
|
107629
|
+
const symbol = scopes.referenceFor(selector)?.resolvedSymbol;
|
|
107630
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && getSymbolVariableDeclarator(symbol)?.id === symbol.bindingIdentifier && !visitedSymbolIds.has(symbol.id)) {
|
|
107631
|
+
visitedSymbolIds.add(symbol.id);
|
|
107632
|
+
const initializerIdentity = getStaticSelectorIdentity(symbol.initializer, scopes, visitedSymbolIds);
|
|
107633
|
+
if (initializerIdentity) return initializerIdentity;
|
|
107634
|
+
}
|
|
107635
|
+
}
|
|
107636
|
+
if (isNodeOfType(selector, "UnaryExpression") && selector.operator === "!") {
|
|
107637
|
+
const argumentIdentity = getStaticSelectorIdentity(selector.argument, scopes, visitedSymbolIds);
|
|
107638
|
+
return argumentIdentity ? {
|
|
107639
|
+
isInverted: !argumentIdentity.isInverted,
|
|
107640
|
+
key: argumentIdentity.key
|
|
107641
|
+
} : null;
|
|
107642
|
+
}
|
|
107643
|
+
if (isNodeOfType(selector, "BinaryExpression") && [
|
|
107644
|
+
"==",
|
|
107645
|
+
"!=",
|
|
107646
|
+
"===",
|
|
107647
|
+
"!=="
|
|
107648
|
+
].includes(selector.operator)) {
|
|
107649
|
+
const operandPairs = [{
|
|
107650
|
+
literal: selector.right,
|
|
107651
|
+
selectorOperand: selector.left
|
|
107652
|
+
}, {
|
|
107653
|
+
literal: selector.left,
|
|
107654
|
+
selectorOperand: selector.right
|
|
107655
|
+
}];
|
|
107656
|
+
for (const operandPair of operandPairs) {
|
|
107657
|
+
const constantKey = getStaticComparisonConstantKey(operandPair.literal, scopes);
|
|
107658
|
+
if (constantKey === null) continue;
|
|
107659
|
+
const operandKey = getStaticComparisonOperandKey(operandPair.selectorOperand, scopes);
|
|
107660
|
+
if (operandKey === null) continue;
|
|
107661
|
+
return {
|
|
107662
|
+
isInverted: selector.operator === "!=" || selector.operator === "!==",
|
|
107663
|
+
key: JSON.stringify([
|
|
107664
|
+
"comparison",
|
|
107665
|
+
selector.operator.length === 3 ? "strict" : "loose",
|
|
107666
|
+
operandKey,
|
|
107667
|
+
constantKey
|
|
107668
|
+
])
|
|
107669
|
+
};
|
|
107670
|
+
}
|
|
107671
|
+
return null;
|
|
107672
|
+
}
|
|
107673
|
+
if (isNodeOfType(selector, "BinaryExpression") && [
|
|
107674
|
+
"<",
|
|
107675
|
+
"<=",
|
|
107676
|
+
">",
|
|
107677
|
+
">=",
|
|
107678
|
+
"in",
|
|
107679
|
+
"instanceof"
|
|
107680
|
+
].includes(selector.operator)) {
|
|
107681
|
+
const leftKey = getStaticPrimitiveLiteralKey(selector.left) ?? getStaticComparisonOperandKey(selector.left, scopes);
|
|
107682
|
+
const rightKey = getStaticPrimitiveLiteralKey(selector.right) ?? getStaticComparisonOperandKey(selector.right, scopes);
|
|
107683
|
+
if (leftKey !== null && rightKey !== null) return {
|
|
107684
|
+
isInverted: false,
|
|
107685
|
+
key: JSON.stringify([
|
|
107686
|
+
"binary-comparison",
|
|
107687
|
+
selector.operator,
|
|
107688
|
+
leftKey,
|
|
107689
|
+
rightKey
|
|
107690
|
+
])
|
|
107691
|
+
};
|
|
107692
|
+
}
|
|
107693
|
+
const key = getSelectorReferenceKey(selector, scopes);
|
|
107694
|
+
return key === null ? null : {
|
|
107695
|
+
isInverted: false,
|
|
107696
|
+
key
|
|
107697
|
+
};
|
|
107698
|
+
};
|
|
107699
|
+
const getRenderedRootShapeAlternativeKey = (alternative) => JSON.stringify({
|
|
107700
|
+
facts: [...alternative.facts].map(([key, fact]) => ({
|
|
107701
|
+
key,
|
|
107702
|
+
outcome: fact.outcome
|
|
107703
|
+
})).sort((firstFact, secondFact) => firstFact.key.localeCompare(secondFact.key)),
|
|
107704
|
+
roots: alternative.roots
|
|
107705
|
+
});
|
|
107706
|
+
const getRenderedRootFactStateKey = (alternative) => JSON.stringify([...alternative.facts].map(([key, fact]) => ({
|
|
107707
|
+
key,
|
|
107708
|
+
outcome: fact.outcome
|
|
107709
|
+
})).sort((firstFact, secondFact) => firstFact.key.localeCompare(secondFact.key)));
|
|
107710
|
+
const deduplicateRenderedRootShapeAlternatives = (alternatives) => {
|
|
107711
|
+
const deduplicatedAlternatives = [];
|
|
107712
|
+
const alternativeKeys = /* @__PURE__ */ new Set();
|
|
107713
|
+
const alternativeCountsByFactState = /* @__PURE__ */ new Map();
|
|
107714
|
+
for (const alternative of alternatives) {
|
|
107715
|
+
const alternativeKey = getRenderedRootShapeAlternativeKey(alternative);
|
|
107716
|
+
if (alternativeKeys.has(alternativeKey)) continue;
|
|
107717
|
+
const factStateKey = getRenderedRootFactStateKey(alternative);
|
|
107718
|
+
const factStateAlternativeCount = alternativeCountsByFactState.get(factStateKey) ?? 0;
|
|
107719
|
+
if (factStateAlternativeCount > 1) continue;
|
|
107720
|
+
alternativeKeys.add(alternativeKey);
|
|
107721
|
+
alternativeCountsByFactState.set(factStateKey, factStateAlternativeCount + 1);
|
|
107722
|
+
deduplicatedAlternatives.push(alternative);
|
|
107723
|
+
if (deduplicatedAlternatives.length > 64) return null;
|
|
107724
|
+
}
|
|
107725
|
+
return deduplicatedAlternatives;
|
|
107726
|
+
};
|
|
107727
|
+
const addRenderedRootSelectorFact = (alternatives, identity, expressionOutcome, selector) => {
|
|
107728
|
+
const outcome = identity.isInverted ? !expressionOutcome : expressionOutcome;
|
|
107729
|
+
const constrainedAlternatives = [];
|
|
107730
|
+
for (const alternative of alternatives) {
|
|
107731
|
+
const existingFact = alternative.facts.get(identity.key);
|
|
107732
|
+
if (existingFact && existingFact.outcome !== outcome) continue;
|
|
107733
|
+
constrainedAlternatives.push({
|
|
107734
|
+
facts: new Map(alternative.facts).set(identity.key, {
|
|
107735
|
+
outcome,
|
|
107736
|
+
selector
|
|
107737
|
+
}),
|
|
107738
|
+
roots: alternative.roots
|
|
107739
|
+
});
|
|
107740
|
+
}
|
|
107741
|
+
return constrainedAlternatives;
|
|
107742
|
+
};
|
|
107743
|
+
const mergeRenderedRootShapeAlternatives = (existingAlternatives, appendedAlternatives) => {
|
|
107744
|
+
const mergedAlternatives = [];
|
|
107745
|
+
for (const existingAlternative of existingAlternatives) for (const appendedAlternative of appendedAlternatives) {
|
|
107746
|
+
const mergedFacts = new Map(existingAlternative.facts);
|
|
107747
|
+
let hasContradictoryFact = false;
|
|
107748
|
+
for (const [key, appendedFact] of appendedAlternative.facts) {
|
|
107749
|
+
const existingFact = mergedFacts.get(key);
|
|
107750
|
+
if (existingFact && existingFact.outcome !== appendedFact.outcome) {
|
|
107751
|
+
hasContradictoryFact = true;
|
|
107752
|
+
break;
|
|
107753
|
+
}
|
|
107754
|
+
mergedFacts.set(key, appendedFact);
|
|
107755
|
+
}
|
|
107756
|
+
if (hasContradictoryFact) continue;
|
|
107757
|
+
mergedAlternatives.push({
|
|
107758
|
+
facts: mergedFacts,
|
|
107759
|
+
roots: [...existingAlternative.roots, ...appendedAlternative.roots]
|
|
107760
|
+
});
|
|
107761
|
+
}
|
|
107762
|
+
return deduplicateRenderedRootShapeAlternatives(mergedAlternatives);
|
|
107763
|
+
};
|
|
107764
|
+
const combineRenderedRootShapeAlternativeBranches = (branches) => deduplicateRenderedRootShapeAlternatives(branches.flat());
|
|
107765
|
+
const getStaticRenderedRootAlternatives = (node, scopes) => {
|
|
107766
|
+
const renderedNode = getFinalSequenceExpressionValue(node);
|
|
107767
|
+
if (isNodeOfType(renderedNode, "JSXElement") && !isJsxFragmentElement(renderedNode.openingElement, scopes)) {
|
|
107768
|
+
const elementIdentity = getJsxElementIdentity(renderedNode.openingElement.name, scopes);
|
|
107769
|
+
return elementIdentity === null ? null : [{
|
|
107770
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107771
|
+
roots: [elementIdentity]
|
|
107772
|
+
}];
|
|
107773
|
+
}
|
|
107774
|
+
if (isNodeOfType(renderedNode, "JSXElement") || isNodeOfType(renderedNode, "JSXFragment")) {
|
|
107775
|
+
let alternatives = [{
|
|
107776
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107777
|
+
roots: []
|
|
107778
|
+
}];
|
|
107779
|
+
for (const child of renderedNode.children) {
|
|
107780
|
+
const childAlternatives = getStaticRenderedRootAlternatives(child, scopes);
|
|
107781
|
+
if (childAlternatives === null) return null;
|
|
107782
|
+
const mergedAlternatives = mergeRenderedRootShapeAlternatives(alternatives, childAlternatives);
|
|
107783
|
+
if (mergedAlternatives === null) return null;
|
|
107784
|
+
alternatives = mergedAlternatives;
|
|
107785
|
+
}
|
|
107786
|
+
return alternatives;
|
|
107787
|
+
}
|
|
107788
|
+
if (isNodeOfType(renderedNode, "JSXText")) return renderedNode.value?.trim() ? null : [{
|
|
107789
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107790
|
+
roots: []
|
|
107791
|
+
}];
|
|
107792
|
+
if (isNodeOfType(renderedNode, "JSXExpressionContainer")) return getStaticRenderedRootAlternatives(renderedNode.expression, scopes);
|
|
107793
|
+
if (isStaticallyEmptyJsxChild(renderedNode)) return [{
|
|
107794
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107795
|
+
roots: []
|
|
107796
|
+
}];
|
|
107797
|
+
if (isNodeOfType(renderedNode, "ConditionalExpression")) {
|
|
107798
|
+
const staticTestValue = readStaticSelectorTruthiness(renderedNode.test, scopes);
|
|
107799
|
+
if (staticTestValue !== null) return getStaticRenderedRootAlternatives(staticTestValue ? renderedNode.consequent : renderedNode.alternate, scopes);
|
|
107800
|
+
const consequentAlternatives = getStaticRenderedRootAlternatives(renderedNode.consequent, scopes);
|
|
107801
|
+
const alternateAlternatives = getStaticRenderedRootAlternatives(renderedNode.alternate, scopes);
|
|
107802
|
+
if (consequentAlternatives === null || alternateAlternatives === null) return null;
|
|
107803
|
+
const selectorIdentity = getStaticSelectorIdentity(renderedNode.test, scopes);
|
|
107804
|
+
return combineRenderedRootShapeAlternativeBranches([selectorIdentity ? addRenderedRootSelectorFact(consequentAlternatives, selectorIdentity, true, renderedNode.test) : consequentAlternatives, selectorIdentity ? addRenderedRootSelectorFact(alternateAlternatives, selectorIdentity, false, renderedNode.test) : alternateAlternatives]);
|
|
107805
|
+
}
|
|
107806
|
+
if (isNodeOfType(renderedNode, "LogicalExpression")) {
|
|
107807
|
+
const selectorIdentity = (renderedNode.operator === "&&" || renderedNode.operator === "||") && isStaticallyEmptyJsxChild(renderedNode.left) ? getStaticSelectorIdentity(renderedNode.left, scopes) : null;
|
|
107808
|
+
if (selectorIdentity) {
|
|
107809
|
+
const rightAlternatives = getStaticRenderedRootAlternatives(renderedNode.right, scopes);
|
|
107810
|
+
if (rightAlternatives === null) return null;
|
|
107811
|
+
const emptyAlternatives = [{
|
|
107812
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107813
|
+
roots: []
|
|
107814
|
+
}];
|
|
107815
|
+
return combineRenderedRootShapeAlternativeBranches([addRenderedRootSelectorFact(renderedNode.operator === "&&" ? rightAlternatives : emptyAlternatives, selectorIdentity, true, renderedNode.left), addRenderedRootSelectorFact(renderedNode.operator === "&&" ? emptyAlternatives : rightAlternatives, selectorIdentity, false, renderedNode.left)]);
|
|
107816
|
+
}
|
|
107817
|
+
const resultAlternatives = [];
|
|
107818
|
+
for (const resultBranch of getStaticLogicalExpressionResultBranches(renderedNode)) {
|
|
107819
|
+
const branchAlternatives = getStaticRenderedRootAlternatives(resultBranch, scopes);
|
|
107820
|
+
if (branchAlternatives === null) return null;
|
|
107821
|
+
resultAlternatives.push(branchAlternatives);
|
|
107822
|
+
}
|
|
107823
|
+
return combineRenderedRootShapeAlternativeBranches(resultAlternatives);
|
|
107824
|
+
}
|
|
107825
|
+
return null;
|
|
107826
|
+
};
|
|
107827
|
+
const getFlattenedFragmentChildren = (node, scopes) => {
|
|
107828
|
+
const flattenChild = (child) => {
|
|
107829
|
+
const renderedChild = getFinalSequenceExpressionValue(child);
|
|
107830
|
+
if (isNodeOfType(renderedChild, "JSXExpressionContainer")) return flattenChild(renderedChild.expression);
|
|
107831
|
+
if (!isNodeOfType(renderedChild, "JSXFragment") && (!isNodeOfType(renderedChild, "JSXElement") || !isJsxFragmentElement(renderedChild.openingElement, scopes))) return [child];
|
|
107832
|
+
return renderedChild.children.flatMap(flattenChild);
|
|
107833
|
+
};
|
|
107834
|
+
const renderedNode = getFinalSequenceExpressionValue(node);
|
|
107835
|
+
if (!isNodeOfType(renderedNode, "JSXFragment") && (!isNodeOfType(renderedNode, "JSXElement") || !isJsxFragmentElement(renderedNode.openingElement, scopes))) return null;
|
|
107836
|
+
return renderedNode.children.flatMap(flattenChild);
|
|
107837
|
+
};
|
|
107838
|
+
const forgetFinalizedRenderedRootFacts = (alternatives, futureFactKeyCounts) => deduplicateRenderedRootShapeAlternatives(alternatives.map((alternative) => ({
|
|
107839
|
+
facts: new Map([...alternative.facts].filter(([key]) => (futureFactKeyCounts.get(key) ?? 0) > 0)),
|
|
107840
|
+
roots: alternative.roots
|
|
107841
|
+
})));
|
|
107842
|
+
const getStaticRenderedRootShapes = (node, scopes) => {
|
|
107843
|
+
let alternatives = getStaticRenderedRootAlternatives(node, scopes);
|
|
107844
|
+
if (alternatives === null) {
|
|
107845
|
+
const fragmentChildren = getFlattenedFragmentChildren(node, scopes);
|
|
107846
|
+
if (fragmentChildren === null) return null;
|
|
107847
|
+
const childAlternatives = [];
|
|
107848
|
+
for (const child of fragmentChildren) {
|
|
107849
|
+
const staticChildAlternatives = getStaticRenderedRootAlternatives(child, scopes);
|
|
107850
|
+
if (staticChildAlternatives === null) return null;
|
|
107851
|
+
childAlternatives.push(staticChildAlternatives);
|
|
107852
|
+
}
|
|
107853
|
+
const futureFactKeyCounts = /* @__PURE__ */ new Map();
|
|
107854
|
+
for (const staticChildAlternatives of childAlternatives) {
|
|
107855
|
+
const childFactKeys = /* @__PURE__ */ new Set();
|
|
107856
|
+
for (const alternative of staticChildAlternatives) for (const key of alternative.facts.keys()) childFactKeys.add(key);
|
|
107857
|
+
for (const key of childFactKeys) futureFactKeyCounts.set(key, (futureFactKeyCounts.get(key) ?? 0) + 1);
|
|
107858
|
+
}
|
|
107859
|
+
let prefixAlternatives = [{
|
|
107860
|
+
facts: /* @__PURE__ */ new Map(),
|
|
107861
|
+
roots: []
|
|
107862
|
+
}];
|
|
107863
|
+
let witnessAlternatives = null;
|
|
107864
|
+
for (const staticChildAlternatives of childAlternatives) {
|
|
107865
|
+
const currentFactKeys = /* @__PURE__ */ new Set();
|
|
107866
|
+
for (const alternative of staticChildAlternatives) for (const key of alternative.facts.keys()) currentFactKeys.add(key);
|
|
107867
|
+
for (const key of currentFactKeys) futureFactKeyCounts.set(key, (futureFactKeyCounts.get(key) ?? 1) - 1);
|
|
107868
|
+
const mergedAlternatives = mergeRenderedRootShapeAlternatives(prefixAlternatives, staticChildAlternatives);
|
|
107869
|
+
if (mergedAlternatives === null) break;
|
|
107870
|
+
prefixAlternatives = mergedAlternatives;
|
|
107871
|
+
for (let firstAlternativeIndex = 0; firstAlternativeIndex < prefixAlternatives.length; firstAlternativeIndex += 1) {
|
|
107872
|
+
const firstAlternative = prefixAlternatives[firstAlternativeIndex];
|
|
107873
|
+
for (let secondAlternativeIndex = firstAlternativeIndex + 1; secondAlternativeIndex < prefixAlternatives.length; secondAlternativeIndex += 1) {
|
|
107874
|
+
const secondAlternative = prefixAlternatives[secondAlternativeIndex];
|
|
107875
|
+
if (JSON.stringify(firstAlternative.roots) === JSON.stringify(secondAlternative.roots)) continue;
|
|
107876
|
+
if ([...firstAlternative.facts].some(([key, firstFact]) => {
|
|
107877
|
+
const secondFact = secondAlternative.facts.get(key);
|
|
107878
|
+
return futureFactKeyCounts.get(key) === 0 && secondFact !== void 0 && secondFact.outcome !== firstFact.outcome;
|
|
107879
|
+
})) {
|
|
107880
|
+
witnessAlternatives = [firstAlternative, secondAlternative];
|
|
107881
|
+
break;
|
|
107882
|
+
}
|
|
107883
|
+
}
|
|
107884
|
+
if (witnessAlternatives) break;
|
|
107885
|
+
}
|
|
107886
|
+
if (witnessAlternatives) break;
|
|
107887
|
+
const remainingAlternatives = forgetFinalizedRenderedRootFacts(prefixAlternatives, futureFactKeyCounts);
|
|
107888
|
+
if (remainingAlternatives === null) break;
|
|
107889
|
+
prefixAlternatives = remainingAlternatives;
|
|
107890
|
+
}
|
|
107891
|
+
if (witnessAlternatives === null) return null;
|
|
107892
|
+
alternatives = witnessAlternatives;
|
|
107893
|
+
}
|
|
107894
|
+
const rootShapes = [];
|
|
107895
|
+
const rootShapeKeys = /* @__PURE__ */ new Set();
|
|
107896
|
+
for (const alternative of alternatives) {
|
|
107897
|
+
const rootShapeKey = JSON.stringify(alternative.roots);
|
|
107898
|
+
if (rootShapeKeys.has(rootShapeKey)) continue;
|
|
107899
|
+
rootShapeKeys.add(rootShapeKey);
|
|
107900
|
+
rootShapes.push([...alternative.roots]);
|
|
107901
|
+
}
|
|
107902
|
+
return rootShapes;
|
|
107903
|
+
};
|
|
107904
|
+
const getRenderedRootNames = (root, scopes) => {
|
|
107905
|
+
const renderedRootShapes = getStaticRenderedRootShapes(root, scopes);
|
|
107906
|
+
if (renderedRootShapes === null) return null;
|
|
107907
|
+
return renderedRootShapes.map((rootShape) => {
|
|
107908
|
+
const onlyRootName = rootShape[0];
|
|
107909
|
+
return rootShape.length === 1 && onlyRootName ? onlyRootName : `fragment:${JSON.stringify(rootShape)}`;
|
|
107910
|
+
});
|
|
107911
|
+
};
|
|
107912
|
+
const getPatternBindingIdentifier = (pattern) => {
|
|
107913
|
+
const unwrappedPattern = stripParenExpression(pattern);
|
|
107914
|
+
if (isNodeOfType(unwrappedPattern, "Identifier")) return unwrappedPattern;
|
|
107915
|
+
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) return getPatternBindingIdentifier(unwrappedPattern.left);
|
|
107916
|
+
return null;
|
|
107917
|
+
};
|
|
107918
|
+
const getObjectPatternPropertyBinding = (pattern, propertyName) => {
|
|
107919
|
+
const unwrappedPattern = stripParenExpression(pattern);
|
|
107920
|
+
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return null;
|
|
107921
|
+
for (const property of unwrappedPattern.properties) {
|
|
107922
|
+
if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== propertyName) continue;
|
|
107923
|
+
return getPatternBindingIdentifier(property.value);
|
|
107924
|
+
}
|
|
107925
|
+
return null;
|
|
107926
|
+
};
|
|
107927
|
+
const isUnconditionallyTerminalStatement = (statement) => {
|
|
107928
|
+
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
107929
|
+
if (isNodeOfType(statement, "BlockStatement")) return statement.body.some(isUnconditionallyTerminalStatement);
|
|
107930
|
+
if (isNodeOfType(statement, "IfStatement")) return Boolean(statement.alternate && isUnconditionallyTerminalStatement(statement.consequent) && isUnconditionallyTerminalStatement(statement.alternate));
|
|
107931
|
+
if (isNodeOfType(statement, "SwitchStatement")) return statement.cases.some((switchCase) => switchCase.test === null) && statement.cases.every((switchCase) => switchCase.consequent.some(isUnconditionallyTerminalStatement));
|
|
107932
|
+
return false;
|
|
107933
|
+
};
|
|
107934
|
+
const getReachableFunctionReturnStatements = (functionNode, scopes) => collectFunctionReturnStatements(functionNode).filter((returnStatement) => {
|
|
107935
|
+
let descendant = returnStatement;
|
|
107936
|
+
let ancestor = returnStatement.parent;
|
|
107937
|
+
while (ancestor && ancestor !== functionNode) {
|
|
107938
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
107939
|
+
const staticTestValue = readStaticSelectorTruthiness(ancestor.test, scopes);
|
|
107940
|
+
if (staticTestValue !== null && (staticTestValue && ancestor.alternate === descendant || !staticTestValue && ancestor.consequent === descendant)) return false;
|
|
107941
|
+
}
|
|
107942
|
+
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
107943
|
+
const descendantIndex = ancestor.body.findIndex((statement) => statement === descendant);
|
|
107944
|
+
if (descendantIndex > 0 && ancestor.body.slice(0, descendantIndex).some((statement) => {
|
|
107945
|
+
if (isNodeOfType(statement, "IfStatement")) {
|
|
107946
|
+
const staticTestValue = readStaticSelectorTruthiness(statement.test, scopes);
|
|
107947
|
+
if (staticTestValue === true) return isUnconditionallyTerminalStatement(statement.consequent);
|
|
107948
|
+
if (staticTestValue === false) return Boolean(statement.alternate && isUnconditionallyTerminalStatement(statement.alternate));
|
|
107949
|
+
}
|
|
107950
|
+
return isUnconditionallyTerminalStatement(statement);
|
|
107951
|
+
})) return false;
|
|
107952
|
+
}
|
|
107953
|
+
descendant = ancestor;
|
|
107954
|
+
ancestor = ancestor.parent;
|
|
107955
|
+
}
|
|
107956
|
+
return true;
|
|
107957
|
+
});
|
|
107958
|
+
const getRenderItemInputReferences = (functionNode, scopes) => {
|
|
107959
|
+
if (!isFunctionLike$1(functionNode)) return [];
|
|
107960
|
+
const parameter = functionNode.params[0];
|
|
107961
|
+
if (!parameter) return [];
|
|
107962
|
+
const unwrappedParameter = stripParenExpression(parameter);
|
|
107963
|
+
if (isNodeOfType(unwrappedParameter, "Identifier")) {
|
|
107964
|
+
const symbol = scopes.symbolFor(unwrappedParameter);
|
|
107965
|
+
if (!symbol) return [];
|
|
107966
|
+
return [...RENDER_ITEM_INPUT_NAMES].map((inputName) => ({
|
|
107967
|
+
inputName,
|
|
107968
|
+
isStable: isSymbolStable(symbol),
|
|
107969
|
+
propertyName: inputName,
|
|
107970
|
+
symbolId: symbol.id
|
|
107971
|
+
}));
|
|
107972
|
+
}
|
|
107973
|
+
if (!isNodeOfType(unwrappedParameter, "ObjectPattern")) return [];
|
|
107974
|
+
const references = [];
|
|
107975
|
+
for (const inputName of RENDER_ITEM_INPUT_NAMES) {
|
|
107976
|
+
const bindingIdentifier = getObjectPatternPropertyBinding(unwrappedParameter, inputName);
|
|
107977
|
+
const symbol = bindingIdentifier ? scopes.symbolFor(bindingIdentifier) : null;
|
|
107978
|
+
if (symbol) references.push({
|
|
107979
|
+
inputName,
|
|
107980
|
+
isStable: isSymbolStable(symbol),
|
|
107981
|
+
propertyName: null,
|
|
107982
|
+
symbolId: symbol.id
|
|
107983
|
+
});
|
|
107984
|
+
}
|
|
107985
|
+
return references;
|
|
107986
|
+
};
|
|
107987
|
+
const isStaticallyTruthyContainer = (node) => isNodeOfType(node, "ArrayExpression") || isNodeOfType(node, "ObjectExpression") || isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ClassExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment");
|
|
107988
|
+
const expressionReadsInput = (expression, inputReferences, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
107989
|
+
const inputExpression = getFinalSequenceExpressionValue(expression);
|
|
107990
|
+
if (isStaticallyTruthyContainer(inputExpression)) return false;
|
|
107991
|
+
let didReadInput = false;
|
|
107992
|
+
walkAst(inputExpression, (node) => {
|
|
107993
|
+
if (didReadInput) return false;
|
|
107994
|
+
if (node !== inputExpression && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
107995
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
107996
|
+
const reference = scopes.referenceFor(node);
|
|
107997
|
+
if (reference && reference.flag !== "write" && inputReferences.some((inputReference) => inputReference.isStable && inputReference.propertyName === null && inputReference.symbolId === reference.resolvedSymbol?.id)) {
|
|
107998
|
+
didReadInput = true;
|
|
107999
|
+
return false;
|
|
108000
|
+
}
|
|
108001
|
+
const symbol = reference?.resolvedSymbol;
|
|
108002
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && !visitedSymbolIds.has(symbol.id)) {
|
|
108003
|
+
visitedSymbolIds.add(symbol.id);
|
|
108004
|
+
for (const initializer of getConstInitializerExpressions(symbol)) if (expressionReadsInput(initializer, inputReferences, scopes, visitedSymbolIds)) {
|
|
108005
|
+
didReadInput = true;
|
|
108006
|
+
return false;
|
|
108007
|
+
}
|
|
108008
|
+
}
|
|
108009
|
+
}
|
|
108010
|
+
if (!isNodeOfType(node, "MemberExpression")) return;
|
|
108011
|
+
const propertyName = getStaticPropertyName(node);
|
|
108012
|
+
const receiver = stripParenExpression(node.object);
|
|
108013
|
+
if (propertyName === null || !isNodeOfType(receiver, "Identifier")) return;
|
|
108014
|
+
const receiverReference = scopes.referenceFor(receiver);
|
|
108015
|
+
if (receiverReference && receiverReference.flag !== "write" && inputReferences.some((inputReference) => inputReference.isStable && inputReference.propertyName === propertyName && inputReference.symbolId === receiverReference.resolvedSymbol?.id)) {
|
|
108016
|
+
didReadInput = true;
|
|
108017
|
+
return false;
|
|
108018
|
+
}
|
|
108019
|
+
});
|
|
108020
|
+
return didReadInput;
|
|
108021
|
+
};
|
|
108022
|
+
const climbTransparentExpressionWrappers = (node) => {
|
|
108023
|
+
let expression = node;
|
|
108024
|
+
while (expression.parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(expression.parent.type) && "expression" in expression.parent && expression.parent.expression === expression) expression = expression.parent;
|
|
108025
|
+
return expression;
|
|
108026
|
+
};
|
|
108027
|
+
const isProvenStaticCallableReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
108028
|
+
const reference = stripParenExpression(expression);
|
|
108029
|
+
if (isNodeOfType(reference, "MemberExpression")) return getImportedStaticReferenceKey(reference, scopes) !== null;
|
|
108030
|
+
if (!isNodeOfType(reference, "Identifier")) return false;
|
|
108031
|
+
const symbol = scopes.referenceFor(reference)?.resolvedSymbol;
|
|
108032
|
+
if (!symbol) return scopes.isGlobalReference(reference);
|
|
108033
|
+
if (!isSymbolStable(symbol) || visitedSymbolIds.has(symbol.id)) return false;
|
|
108034
|
+
if (symbol.kind === "import" || symbol.kind === "function") return true;
|
|
108035
|
+
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
108036
|
+
visitedSymbolIds.add(symbol.id);
|
|
108037
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
108038
|
+
if (isFunctionLike$1(initializer)) return true;
|
|
108039
|
+
return isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression") ? isProvenStaticCallableReference(initializer, scopes, visitedSymbolIds) : false;
|
|
108040
|
+
};
|
|
108041
|
+
const isProvenStaticCallCallee = (identifier, scopes) => {
|
|
108042
|
+
let callee = climbTransparentExpressionWrappers(identifier);
|
|
108043
|
+
while (isNodeOfType(callee.parent, "MemberExpression") && callee.parent.object === callee) callee = climbTransparentExpressionWrappers(callee.parent);
|
|
108044
|
+
return isNodeOfType(callee.parent, "CallExpression") && callee.parent.callee === callee && isProvenStaticCallableReference(callee, scopes);
|
|
108045
|
+
};
|
|
108046
|
+
const isInsideComparisonOperand = (identifier) => {
|
|
108047
|
+
let descendant = climbTransparentExpressionWrappers(identifier);
|
|
108048
|
+
let ancestor = descendant.parent;
|
|
108049
|
+
while (isNodeOfType(ancestor, "MemberExpression") && (ancestor.object === descendant || ancestor.property === descendant)) {
|
|
108050
|
+
descendant = climbTransparentExpressionWrappers(ancestor);
|
|
108051
|
+
ancestor = descendant.parent;
|
|
108052
|
+
}
|
|
108053
|
+
return isNodeOfType(ancestor, "BinaryExpression") && [
|
|
108054
|
+
"==",
|
|
108055
|
+
"!=",
|
|
108056
|
+
"===",
|
|
108057
|
+
"!==",
|
|
108058
|
+
"<",
|
|
108059
|
+
"<=",
|
|
108060
|
+
">",
|
|
108061
|
+
">=",
|
|
108062
|
+
"in",
|
|
108063
|
+
"instanceof"
|
|
108064
|
+
].includes(ancestor.operator);
|
|
108065
|
+
};
|
|
108066
|
+
const expressionReadsOnlyInput = (expression, inputReferences, scopes) => {
|
|
108067
|
+
if (!expressionReadsInput(expression, inputReferences, scopes)) return false;
|
|
108068
|
+
const selector = getFinalSequenceExpressionValue(expression);
|
|
108069
|
+
let hasUnrelatedRead = false;
|
|
108070
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
108071
|
+
const inspectExpression = (candidate) => {
|
|
108072
|
+
walkAst(candidate, (node) => {
|
|
108073
|
+
if (hasUnrelatedRead) return false;
|
|
108074
|
+
if (node !== candidate && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
108075
|
+
if (!isNodeOfType(node, "Identifier")) return;
|
|
108076
|
+
const parent = node.parent;
|
|
108077
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed || isProvenStaticCallCallee(node, scopes)) return;
|
|
108078
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
108079
|
+
const isDirectInput = inputReferences.some((inputReference) => inputReference.isStable && inputReference.propertyName === null && inputReference.symbolId === symbol?.id);
|
|
108080
|
+
const isInputContainer = isNodeOfType(parent, "MemberExpression") && parent.object === node && inputReferences.some((inputReference) => inputReference.isStable && inputReference.propertyName === getStaticPropertyName(parent) && inputReference.symbolId === symbol?.id);
|
|
108081
|
+
if (isDirectInput || isInputContainer) return;
|
|
108082
|
+
if (symbol?.kind === "import" && isInsideComparisonOperand(node)) return;
|
|
108083
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && visitedSymbolIds.has(symbol.id)) return;
|
|
108084
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && !visitedSymbolIds.has(symbol.id)) {
|
|
108085
|
+
visitedSymbolIds.add(symbol.id);
|
|
108086
|
+
const initializers = getConstInitializerExpressions(symbol);
|
|
108087
|
+
if (!initializers.some((initializer) => expressionReadsInput(initializer, inputReferences, scopes)) && initializers.some((initializer) => readStaticSelectorTruthiness(initializer, scopes) === null)) {
|
|
108088
|
+
hasUnrelatedRead = true;
|
|
108089
|
+
return false;
|
|
108090
|
+
}
|
|
108091
|
+
for (const initializer of initializers) inspectExpression(initializer);
|
|
108092
|
+
return;
|
|
108093
|
+
}
|
|
108094
|
+
hasUnrelatedRead = true;
|
|
108095
|
+
return false;
|
|
108096
|
+
});
|
|
108097
|
+
};
|
|
108098
|
+
inspectExpression(selector);
|
|
108099
|
+
return !hasUnrelatedRead;
|
|
108100
|
+
};
|
|
108101
|
+
const hasInputDependentRenderedRootDifference = (expression, inputReferences, scopes) => {
|
|
108102
|
+
const alternativesHaveInputDependentDifference = (alternatives, eligibleFactKeys) => {
|
|
108103
|
+
for (let firstAlternativeIndex = 0; firstAlternativeIndex < alternatives.length; firstAlternativeIndex += 1) {
|
|
108104
|
+
const firstAlternative = alternatives[firstAlternativeIndex];
|
|
108105
|
+
for (let secondAlternativeIndex = firstAlternativeIndex + 1; secondAlternativeIndex < alternatives.length; secondAlternativeIndex += 1) {
|
|
108106
|
+
const secondAlternative = alternatives[secondAlternativeIndex];
|
|
108107
|
+
if (JSON.stringify(firstAlternative.roots) === JSON.stringify(secondAlternative.roots)) continue;
|
|
108108
|
+
let hasInputDependentDifference = false;
|
|
108109
|
+
let hasAmbientConflict = false;
|
|
108110
|
+
for (const [key, firstFact] of firstAlternative.facts) {
|
|
108111
|
+
const secondFact = secondAlternative.facts.get(key);
|
|
108112
|
+
if (!secondFact || firstFact.outcome === secondFact.outcome) continue;
|
|
108113
|
+
if ((!eligibleFactKeys || eligibleFactKeys.has(key)) && expressionReadsOnlyInput(firstFact.selector, inputReferences, scopes) && expressionReadsOnlyInput(secondFact.selector, inputReferences, scopes)) hasInputDependentDifference = true;
|
|
108114
|
+
else hasAmbientConflict = true;
|
|
108115
|
+
}
|
|
108116
|
+
if (hasInputDependentDifference && !hasAmbientConflict) return true;
|
|
108117
|
+
}
|
|
108118
|
+
}
|
|
108119
|
+
return false;
|
|
108120
|
+
};
|
|
108121
|
+
const renderedExpression = getFinalSequenceExpressionValue(expression);
|
|
108122
|
+
const fragmentChildren = getFlattenedFragmentChildren(renderedExpression, scopes);
|
|
108123
|
+
if (fragmentChildren !== null) {
|
|
108124
|
+
const childAlternatives = [];
|
|
108125
|
+
for (const child of fragmentChildren) {
|
|
108126
|
+
const alternatives = getStaticRenderedRootAlternatives(child, scopes);
|
|
108127
|
+
if (alternatives === null) return false;
|
|
108128
|
+
childAlternatives.push(alternatives);
|
|
108129
|
+
}
|
|
108130
|
+
const futureFactKeyCounts = /* @__PURE__ */ new Map();
|
|
108131
|
+
for (const alternatives of childAlternatives) {
|
|
108132
|
+
const childFactKeys = /* @__PURE__ */ new Set();
|
|
108133
|
+
for (const alternative of alternatives) for (const key of alternative.facts.keys()) childFactKeys.add(key);
|
|
108134
|
+
for (const key of childFactKeys) futureFactKeyCounts.set(key, (futureFactKeyCounts.get(key) ?? 0) + 1);
|
|
108135
|
+
}
|
|
108136
|
+
let prefixAlternatives = [{
|
|
108137
|
+
facts: /* @__PURE__ */ new Map(),
|
|
108138
|
+
roots: []
|
|
108139
|
+
}];
|
|
108140
|
+
for (const alternatives of childAlternatives) {
|
|
108141
|
+
const currentFactKeys = /* @__PURE__ */ new Set();
|
|
108142
|
+
for (const alternative of alternatives) for (const key of alternative.facts.keys()) currentFactKeys.add(key);
|
|
108143
|
+
for (const key of currentFactKeys) futureFactKeyCounts.set(key, (futureFactKeyCounts.get(key) ?? 1) - 1);
|
|
108144
|
+
const mergedAlternatives = mergeRenderedRootShapeAlternatives(prefixAlternatives, alternatives);
|
|
108145
|
+
if (mergedAlternatives === null) return false;
|
|
108146
|
+
prefixAlternatives = mergedAlternatives;
|
|
108147
|
+
const finalFactKeys = /* @__PURE__ */ new Set();
|
|
108148
|
+
for (const alternative of prefixAlternatives) for (const key of alternative.facts.keys()) if (futureFactKeyCounts.get(key) === 0) finalFactKeys.add(key);
|
|
108149
|
+
if (finalFactKeys.size > 0 && alternativesHaveInputDependentDifference(prefixAlternatives, finalFactKeys)) return true;
|
|
108150
|
+
const remainingAlternatives = forgetFinalizedRenderedRootFacts(prefixAlternatives, futureFactKeyCounts);
|
|
108151
|
+
if (remainingAlternatives === null) return false;
|
|
108152
|
+
prefixAlternatives = remainingAlternatives;
|
|
108153
|
+
}
|
|
108154
|
+
return false;
|
|
108155
|
+
}
|
|
108156
|
+
const alternatives = getStaticRenderedRootAlternatives(renderedExpression, scopes);
|
|
108157
|
+
return alternatives !== null && alternativesHaveInputDependentDifference(alternatives);
|
|
108158
|
+
};
|
|
108159
|
+
const getKnownReturnedRootNames = (expression, scopes) => {
|
|
108160
|
+
const returnedExpression = getFinalSequenceExpressionValue(expression);
|
|
108161
|
+
if (isNodeOfType(returnedExpression, "JSXElement") || isNodeOfType(returnedExpression, "JSXFragment")) {
|
|
108162
|
+
const rootNames = getRenderedRootNames(returnedExpression, scopes);
|
|
108163
|
+
return rootNames === null ? null : new Set(rootNames);
|
|
108164
|
+
}
|
|
108165
|
+
if (isStaticallyEmptyJsxChild(returnedExpression)) return new Set([EMPTY_RENDERED_ROOT_NAME]);
|
|
108166
|
+
let branches;
|
|
108167
|
+
if (isNodeOfType(returnedExpression, "ConditionalExpression")) branches = [returnedExpression.consequent, returnedExpression.alternate];
|
|
108168
|
+
else if (isNodeOfType(returnedExpression, "LogicalExpression")) branches = getStaticLogicalExpressionResultBranches(returnedExpression);
|
|
108169
|
+
else return null;
|
|
108170
|
+
const rootNames = /* @__PURE__ */ new Set();
|
|
108171
|
+
for (const branch of branches) {
|
|
108172
|
+
const branchRootNames = getKnownReturnedRootNames(branch, scopes);
|
|
108173
|
+
if (branchRootNames === null) return null;
|
|
108174
|
+
for (const rootName of branchRootNames) rootNames.add(rootName);
|
|
108175
|
+
}
|
|
108176
|
+
return rootNames;
|
|
108177
|
+
};
|
|
108178
|
+
const collectKnownStatementRootNames = (statement, scopes) => {
|
|
108179
|
+
const rootNames = /* @__PURE__ */ new Set();
|
|
108180
|
+
let hasUnknownRoot = false;
|
|
108181
|
+
walkAst(statement, (node) => {
|
|
108182
|
+
if (hasUnknownRoot) return false;
|
|
108183
|
+
if (node !== statement && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
108184
|
+
if (!isNodeOfType(node, "ReturnStatement")) return;
|
|
108185
|
+
const returnedRootNames = node.argument ? getKnownReturnedRootNames(node.argument, scopes) : new Set([EMPTY_RENDERED_ROOT_NAME]);
|
|
108186
|
+
if (returnedRootNames === null) {
|
|
108187
|
+
hasUnknownRoot = true;
|
|
108188
|
+
return false;
|
|
108189
|
+
}
|
|
108190
|
+
for (const rootName of returnedRootNames) rootNames.add(rootName);
|
|
108191
|
+
return false;
|
|
108192
|
+
});
|
|
108193
|
+
return hasUnknownRoot || rootNames.size === 0 ? null : rootNames;
|
|
108194
|
+
};
|
|
108195
|
+
const collectKnownContinuationRootNames = (ifStatement, scopes) => {
|
|
108196
|
+
const parent = ifStatement.parent;
|
|
108197
|
+
if (!parent || !isNodeOfType(parent, "BlockStatement")) return null;
|
|
108198
|
+
const statementIndex = parent.body.findIndex((statement) => statement === ifStatement);
|
|
108199
|
+
if (statementIndex < 0) return null;
|
|
108200
|
+
const rootNames = /* @__PURE__ */ new Set();
|
|
108201
|
+
for (const statement of parent.body.slice(statementIndex + 1)) {
|
|
108202
|
+
const statementRootNames = collectKnownStatementRootNames(statement, scopes);
|
|
108203
|
+
if (statementRootNames) for (const rootName of statementRootNames) rootNames.add(rootName);
|
|
108204
|
+
if (isUnconditionallyTerminalStatement(statement)) break;
|
|
108205
|
+
}
|
|
108206
|
+
return rootNames.size === 0 ? null : rootNames;
|
|
108207
|
+
};
|
|
108208
|
+
const getDirectStatementRootAlternatives = (statement, scopes) => {
|
|
108209
|
+
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? getStaticRenderedRootAlternatives(statement.argument, scopes) : [{
|
|
108210
|
+
facts: /* @__PURE__ */ new Map(),
|
|
108211
|
+
roots: []
|
|
108212
|
+
}];
|
|
108213
|
+
if (isNodeOfType(statement, "BlockStatement") && statement.body.length === 1) return getDirectStatementRootAlternatives(statement.body[0], scopes);
|
|
108214
|
+
return null;
|
|
108215
|
+
};
|
|
108216
|
+
const getContinuationRootAlternatives = (ifStatement, scopes) => {
|
|
108217
|
+
const parent = ifStatement.parent;
|
|
108218
|
+
if (!parent || !isNodeOfType(parent, "BlockStatement")) return null;
|
|
108219
|
+
const statementIndex = parent.body.findIndex((statement) => statement === ifStatement);
|
|
108220
|
+
if (statementIndex < 0) return null;
|
|
108221
|
+
for (const statement of parent.body.slice(statementIndex + 1)) {
|
|
108222
|
+
const alternatives = getDirectStatementRootAlternatives(statement, scopes);
|
|
108223
|
+
if (alternatives !== null) return alternatives;
|
|
108224
|
+
if (isUnconditionallyTerminalStatement(statement)) return null;
|
|
108225
|
+
}
|
|
108226
|
+
return null;
|
|
108227
|
+
};
|
|
108228
|
+
const renderedRootFactsAreCompatible = (firstFacts, secondFacts) => {
|
|
108229
|
+
for (const [key, firstFact] of firstFacts) {
|
|
108230
|
+
const secondFact = secondFacts.get(key);
|
|
108231
|
+
if (secondFact && secondFact.outcome !== firstFact.outcome) return false;
|
|
108232
|
+
}
|
|
108233
|
+
return true;
|
|
108234
|
+
};
|
|
108235
|
+
const hasDistinctKnownIfRootOutcomes = (ifStatement, scopes) => {
|
|
108236
|
+
if (!isUnconditionallyTerminalStatement(ifStatement.consequent)) return false;
|
|
108237
|
+
const consequentAlternatives = getDirectStatementRootAlternatives(ifStatement.consequent, scopes);
|
|
108238
|
+
const alternateAlternatives = ifStatement.alternate ? isUnconditionallyTerminalStatement(ifStatement.alternate) ? getDirectStatementRootAlternatives(ifStatement.alternate, scopes) : null : getContinuationRootAlternatives(ifStatement, scopes);
|
|
108239
|
+
if (consequentAlternatives && alternateAlternatives) return consequentAlternatives.some((consequentAlternative) => alternateAlternatives.some((alternateAlternative) => renderedRootFactsAreCompatible(consequentAlternative.facts, alternateAlternative.facts) && JSON.stringify(consequentAlternative.roots) !== JSON.stringify(alternateAlternative.roots)));
|
|
108240
|
+
const consequentRootNames = collectKnownStatementRootNames(ifStatement.consequent, scopes);
|
|
108241
|
+
const alternateRootNames = ifStatement.alternate ? isUnconditionallyTerminalStatement(ifStatement.alternate) ? collectKnownStatementRootNames(ifStatement.alternate, scopes) : null : collectKnownContinuationRootNames(ifStatement, scopes);
|
|
108242
|
+
if (!consequentRootNames || !alternateRootNames) return false;
|
|
108243
|
+
return [...consequentRootNames].some((rootName) => !alternateRootNames.has(rootName)) || [...alternateRootNames].some((rootName) => !consequentRootNames.has(rootName));
|
|
108244
|
+
};
|
|
108245
|
+
const hasDistinctKnownSwitchRootOutcomes = (switchStatement, scopes) => {
|
|
108246
|
+
if (!switchStatement.cases.some((switchCase) => switchCase.test === null) || !switchStatement.cases.every((switchCase) => switchCase.consequent.some(isUnconditionallyTerminalStatement))) return false;
|
|
108247
|
+
const caseRootNames = [];
|
|
108248
|
+
for (const switchCase of switchStatement.cases) {
|
|
108249
|
+
const rootNames = collectKnownStatementRootNames(switchCase, scopes);
|
|
108250
|
+
if (!rootNames) return false;
|
|
108251
|
+
caseRootNames.push(rootNames);
|
|
108252
|
+
}
|
|
108253
|
+
for (let firstCaseIndex = 0; firstCaseIndex < caseRootNames.length; firstCaseIndex += 1) {
|
|
108254
|
+
const firstRootNames = caseRootNames[firstCaseIndex];
|
|
108255
|
+
for (let secondCaseIndex = firstCaseIndex + 1; secondCaseIndex < caseRootNames.length; secondCaseIndex += 1) {
|
|
108256
|
+
const secondRootNames = caseRootNames[secondCaseIndex];
|
|
108257
|
+
if ([...firstRootNames].every((rootName) => !secondRootNames.has(rootName))) return true;
|
|
108258
|
+
}
|
|
108259
|
+
}
|
|
108260
|
+
return false;
|
|
108261
|
+
};
|
|
108262
|
+
const readStaticSelectorTruthiness = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
108263
|
+
const selector = getFinalSequenceExpressionValue(expression);
|
|
108264
|
+
if (isStaticallyTruthyContainer(selector)) return true;
|
|
108265
|
+
if (isNodeOfType(selector, "Literal")) return Boolean(selector.value);
|
|
108266
|
+
if (isNodeOfType(selector, "Identifier")) {
|
|
108267
|
+
const symbol = scopes.symbolFor(selector);
|
|
108268
|
+
if (symbol && (symbol.kind === "function" || symbol.kind === "class") && isSymbolStable(symbol)) return true;
|
|
108269
|
+
if (symbol?.kind === "const" && symbol.initializer && isSymbolStable(symbol) && getSymbolVariableDeclarator(symbol)?.id === symbol.bindingIdentifier && !visitedSymbolIds.has(symbol.id)) {
|
|
108270
|
+
visitedSymbolIds.add(symbol.id);
|
|
108271
|
+
return readStaticSelectorTruthiness(symbol.initializer, scopes, visitedSymbolIds);
|
|
108272
|
+
}
|
|
108273
|
+
}
|
|
108274
|
+
if (isNodeOfType(selector, "UnaryExpression") && selector.operator === "!") {
|
|
108275
|
+
const argumentTruthiness = readStaticSelectorTruthiness(selector.argument, scopes, visitedSymbolIds);
|
|
108276
|
+
return argumentTruthiness === null ? null : !argumentTruthiness;
|
|
108277
|
+
}
|
|
108278
|
+
if (!isNodeOfType(selector, "LogicalExpression")) return null;
|
|
108279
|
+
const leftTruthiness = readStaticSelectorTruthiness(selector.left, scopes, new Set(visitedSymbolIds));
|
|
108280
|
+
const rightTruthiness = readStaticSelectorTruthiness(selector.right, scopes, new Set(visitedSymbolIds));
|
|
108281
|
+
if (selector.operator === "&&") {
|
|
108282
|
+
if (leftTruthiness === false || rightTruthiness === false) return false;
|
|
108283
|
+
return leftTruthiness === true ? rightTruthiness : null;
|
|
108284
|
+
}
|
|
108285
|
+
if (selector.operator === "||") {
|
|
108286
|
+
if (leftTruthiness === true || rightTruthiness === true) return true;
|
|
108287
|
+
return leftTruthiness === false ? rightTruthiness : null;
|
|
108288
|
+
}
|
|
108289
|
+
return leftTruthiness;
|
|
108290
|
+
};
|
|
108291
|
+
const analyzeReturnedExpressionSelections = (expression, inputReferences, scopes) => {
|
|
108292
|
+
const returnedExpression = getFinalSequenceExpressionValue(expression);
|
|
108293
|
+
if (isNodeOfType(returnedExpression, "JSXExpressionContainer")) return analyzeReturnedExpressionSelections(returnedExpression.expression, inputReferences, scopes);
|
|
108294
|
+
if (isNodeOfType(returnedExpression, "JSXFragment") || isNodeOfType(returnedExpression, "JSXElement") && isJsxFragmentElement(returnedExpression.openingElement, scopes)) {
|
|
108295
|
+
let hasInputDependentSelection = false;
|
|
108296
|
+
let hasUnrelatedSelection = false;
|
|
108297
|
+
for (const child of returnedExpression.children) {
|
|
108298
|
+
const childAnalysis = analyzeReturnedExpressionSelections(child, inputReferences, scopes);
|
|
108299
|
+
hasInputDependentSelection ||= childAnalysis.hasInputDependentSelection;
|
|
108300
|
+
hasUnrelatedSelection ||= childAnalysis.hasUnrelatedSelection;
|
|
108301
|
+
}
|
|
108302
|
+
return {
|
|
108303
|
+
hasInputDependentSelection,
|
|
108304
|
+
hasProvenInputDependentRootSelection: hasInputDependentRenderedRootDifference(returnedExpression, inputReferences, scopes),
|
|
108305
|
+
hasUnrelatedSelection
|
|
108306
|
+
};
|
|
108307
|
+
}
|
|
108308
|
+
if (isNodeOfType(returnedExpression, "ConditionalExpression")) {
|
|
108309
|
+
const staticTestValue = readStaticSelectorTruthiness(returnedExpression.test, scopes);
|
|
108310
|
+
if (staticTestValue !== null) return analyzeReturnedExpressionSelections(staticTestValue ? returnedExpression.consequent : returnedExpression.alternate, inputReferences, scopes);
|
|
108311
|
+
const consequentAnalysis = analyzeReturnedExpressionSelections(returnedExpression.consequent, inputReferences, scopes);
|
|
108312
|
+
const alternateAnalysis = analyzeReturnedExpressionSelections(returnedExpression.alternate, inputReferences, scopes);
|
|
108313
|
+
const selectorReadsInput = expressionReadsOnlyInput(returnedExpression.test, inputReferences, scopes);
|
|
108314
|
+
const selectedRootNames = getKnownReturnedRootNames(returnedExpression, scopes);
|
|
108315
|
+
return {
|
|
108316
|
+
hasInputDependentSelection: selectorReadsInput || consequentAnalysis.hasInputDependentSelection || alternateAnalysis.hasInputDependentSelection,
|
|
108317
|
+
hasProvenInputDependentRootSelection: selectorReadsInput && selectedRootNames !== null && selectedRootNames.size > 1 || consequentAnalysis.hasProvenInputDependentRootSelection || alternateAnalysis.hasProvenInputDependentRootSelection,
|
|
108318
|
+
hasUnrelatedSelection: !selectorReadsInput && (selectedRootNames === null || selectedRootNames.size > 1) || consequentAnalysis.hasUnrelatedSelection || alternateAnalysis.hasUnrelatedSelection
|
|
108319
|
+
};
|
|
108320
|
+
}
|
|
108321
|
+
if (isNodeOfType(returnedExpression, "LogicalExpression")) {
|
|
108322
|
+
const resultBranches = getStaticLogicalExpressionResultBranches(returnedExpression);
|
|
108323
|
+
if (resultBranches.length < 2) {
|
|
108324
|
+
const onlyResult = resultBranches[0];
|
|
108325
|
+
return onlyResult ? analyzeReturnedExpressionSelections(onlyResult, inputReferences, scopes) : {
|
|
108326
|
+
hasInputDependentSelection: false,
|
|
108327
|
+
hasProvenInputDependentRootSelection: false,
|
|
108328
|
+
hasUnrelatedSelection: false
|
|
108329
|
+
};
|
|
108330
|
+
}
|
|
108331
|
+
const leftAnalysis = analyzeReturnedExpressionSelections(returnedExpression.left, inputReferences, scopes);
|
|
108332
|
+
const rightAnalysis = analyzeReturnedExpressionSelections(returnedExpression.right, inputReferences, scopes);
|
|
108333
|
+
const selectorReadsInput = expressionReadsOnlyInput(returnedExpression.left, inputReferences, scopes);
|
|
108334
|
+
const selectedRootNames = getKnownReturnedRootNames(returnedExpression, scopes);
|
|
108335
|
+
return {
|
|
108336
|
+
hasInputDependentSelection: selectorReadsInput || leftAnalysis.hasInputDependentSelection || rightAnalysis.hasInputDependentSelection,
|
|
108337
|
+
hasProvenInputDependentRootSelection: selectorReadsInput && selectedRootNames !== null && selectedRootNames.size > 1 || leftAnalysis.hasProvenInputDependentRootSelection || rightAnalysis.hasProvenInputDependentRootSelection,
|
|
108338
|
+
hasUnrelatedSelection: !selectorReadsInput && (selectedRootNames === null || selectedRootNames.size > 1) || leftAnalysis.hasUnrelatedSelection || rightAnalysis.hasUnrelatedSelection
|
|
108339
|
+
};
|
|
108340
|
+
}
|
|
108341
|
+
if (isNodeOfType(returnedExpression, "CallExpression") && isReactApiCall(returnedExpression, "createElement", scopes, {
|
|
108342
|
+
allowGlobalReactNamespace: true,
|
|
108343
|
+
resolveNamedAliases: true
|
|
108344
|
+
})) {
|
|
108345
|
+
const componentArgument = returnedExpression.arguments[0];
|
|
108346
|
+
if (componentArgument && !isNodeOfType(componentArgument, "SpreadElement")) return analyzeReturnedExpressionSelections(componentArgument, inputReferences, scopes);
|
|
108347
|
+
}
|
|
108348
|
+
return {
|
|
108349
|
+
hasInputDependentSelection: false,
|
|
108350
|
+
hasProvenInputDependentRootSelection: false,
|
|
108351
|
+
hasUnrelatedSelection: false
|
|
108352
|
+
};
|
|
108353
|
+
};
|
|
108354
|
+
const analyzeFunctionInputSelections = (functionNode, inputReferences, scopes) => {
|
|
108355
|
+
if (!isFunctionLike$1(functionNode)) return {
|
|
108356
|
+
hasInputDependentSelection: false,
|
|
108357
|
+
hasProvenInputDependentRootSelection: false,
|
|
108358
|
+
hasUnrelatedSelection: false
|
|
108359
|
+
};
|
|
108360
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return analyzeReturnedExpressionSelections(functionNode.body, inputReferences, scopes);
|
|
108361
|
+
let hasInputDependentSelection = false;
|
|
108362
|
+
let hasProvenInputDependentRootSelection = false;
|
|
108363
|
+
let hasUnrelatedSelection = false;
|
|
108364
|
+
const analyzedAncestors = /* @__PURE__ */ new Set();
|
|
108365
|
+
for (const returnStatement of getReachableFunctionReturnStatements(functionNode, scopes)) {
|
|
108366
|
+
const returnedRootNames = returnStatement.argument ? getKnownReturnedRootNames(returnStatement.argument, scopes) : /* @__PURE__ */ new Set();
|
|
108367
|
+
if (returnStatement.argument) {
|
|
108368
|
+
const returnAnalysis = analyzeReturnedExpressionSelections(returnStatement.argument, inputReferences, scopes);
|
|
108369
|
+
hasInputDependentSelection ||= returnAnalysis.hasInputDependentSelection;
|
|
108370
|
+
hasProvenInputDependentRootSelection ||= returnAnalysis.hasProvenInputDependentRootSelection;
|
|
108371
|
+
hasUnrelatedSelection ||= returnAnalysis.hasUnrelatedSelection;
|
|
108372
|
+
}
|
|
108373
|
+
if (returnedRootNames && [...returnedRootNames].every((rootName) => rootName === EMPTY_RENDERED_ROOT_NAME)) continue;
|
|
108374
|
+
let ancestor = returnStatement.parent;
|
|
108375
|
+
while (ancestor && ancestor !== functionNode) {
|
|
108376
|
+
if (analyzedAncestors.has(ancestor)) break;
|
|
108377
|
+
analyzedAncestors.add(ancestor);
|
|
108378
|
+
let selector = null;
|
|
108379
|
+
if (isNodeOfType(ancestor, "IfStatement")) selector = ancestor.test;
|
|
108380
|
+
else if (isNodeOfType(ancestor, "SwitchStatement")) selector = ancestor.discriminant;
|
|
108381
|
+
else if (isNodeOfType(ancestor, "TryStatement") || isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "ForInStatement") || isNodeOfType(ancestor, "ForOfStatement") || isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) hasUnrelatedSelection = true;
|
|
108382
|
+
if (selector) if (expressionReadsOnlyInput(selector, inputReferences, scopes)) {
|
|
108383
|
+
hasInputDependentSelection = true;
|
|
108384
|
+
if (isNodeOfType(ancestor, "IfStatement") && hasDistinctKnownIfRootOutcomes(ancestor, scopes) || isNodeOfType(ancestor, "SwitchStatement") && hasDistinctKnownSwitchRootOutcomes(ancestor, scopes)) hasProvenInputDependentRootSelection = true;
|
|
108385
|
+
} else hasUnrelatedSelection = true;
|
|
108386
|
+
ancestor = ancestor.parent;
|
|
108387
|
+
}
|
|
108388
|
+
}
|
|
108389
|
+
return {
|
|
108390
|
+
hasInputDependentSelection,
|
|
108391
|
+
hasProvenInputDependentRootSelection,
|
|
108392
|
+
hasUnrelatedSelection
|
|
108393
|
+
};
|
|
108394
|
+
};
|
|
108395
|
+
const expressionHasOnlyInputDependentSelections = (expression, inputReferences, scopes) => {
|
|
108396
|
+
const selectionAnalysis = analyzeReturnedExpressionSelections(expression, inputReferences, scopes);
|
|
108397
|
+
return selectionAnalysis.hasInputDependentSelection && !selectionAnalysis.hasUnrelatedSelection;
|
|
108398
|
+
};
|
|
108399
|
+
const functionHasOnlyInputDependentSelections = (functionNode, inputReferences, scopes) => {
|
|
108400
|
+
const selectionAnalysis = analyzeFunctionInputSelections(functionNode, inputReferences, scopes);
|
|
108401
|
+
return selectionAnalysis.hasInputDependentSelection && !selectionAnalysis.hasUnrelatedSelection;
|
|
108402
|
+
};
|
|
108403
|
+
const getComponentExpressionIdentity = (expression, scopes) => {
|
|
108404
|
+
const componentExpression = stripParenExpression(expression);
|
|
108405
|
+
if (isNodeOfType(componentExpression, "Literal")) return typeof componentExpression.value === "string" ? `intrinsic:${componentExpression.value}` : null;
|
|
108406
|
+
if (isNodeOfType(componentExpression, "Identifier")) return getComponentReferenceIdentity(componentExpression, scopes);
|
|
108407
|
+
if (!isNodeOfType(componentExpression, "MemberExpression")) return null;
|
|
108408
|
+
return getComponentReferenceIdentity(componentExpression, scopes);
|
|
108409
|
+
};
|
|
108410
|
+
const collectStaticComponentIdentities = (expression, identities, scopes) => {
|
|
108411
|
+
const componentExpression = getFinalSequenceExpressionValue(expression);
|
|
108412
|
+
if (isNodeOfType(componentExpression, "ConditionalExpression")) return collectStaticComponentIdentities(componentExpression.consequent, identities, scopes) && collectStaticComponentIdentities(componentExpression.alternate, identities, scopes);
|
|
108413
|
+
if (isNodeOfType(componentExpression, "LogicalExpression")) return getStaticLogicalExpressionResultBranches(componentExpression).every((resultBranch) => collectStaticComponentIdentities(resultBranch, identities, scopes));
|
|
108414
|
+
const identity = getComponentExpressionIdentity(componentExpression, scopes);
|
|
108415
|
+
if (identity === null) return false;
|
|
108416
|
+
identities.add(identity);
|
|
108417
|
+
return true;
|
|
108418
|
+
};
|
|
108419
|
+
const isReactFragmentReference = (expression, scopes) => {
|
|
108420
|
+
const fragmentExpression = stripParenExpression(expression);
|
|
108421
|
+
const componentIdentity = getComponentReferenceIdentity(fragmentExpression, scopes);
|
|
108422
|
+
if (componentIdentity === "import:react:Fragment" || componentIdentity === "import:react:default.Fragment") return true;
|
|
108423
|
+
if (isNodeOfType(fragmentExpression, "Identifier")) {
|
|
108424
|
+
const symbol = resolveConstIdentifierAlias(fragmentExpression, scopes);
|
|
108425
|
+
return Boolean(symbol && isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment");
|
|
108426
|
+
}
|
|
108427
|
+
if (!isNodeOfType(fragmentExpression, "MemberExpression") || getStaticPropertyName(fragmentExpression) !== "Fragment") return false;
|
|
108428
|
+
const receiver = stripParenExpression(fragmentExpression.object);
|
|
108429
|
+
return Boolean(isNodeOfType(receiver, "Identifier") && (isReactNamespaceImport(receiver, scopes) || receiver.name === "React" && scopes.isGlobalReference(receiver)));
|
|
108430
|
+
};
|
|
108431
|
+
const getForwardedInput = (expression, inputReferences, scopes) => {
|
|
108432
|
+
const forwardedExpression = stripParenExpression(expression);
|
|
108433
|
+
if (isNodeOfType(forwardedExpression, "Identifier")) {
|
|
108434
|
+
const reference = scopes.referenceFor(forwardedExpression);
|
|
108435
|
+
if (!reference?.resolvedSymbol) return null;
|
|
108436
|
+
const directInput = inputReferences.find((inputReference) => inputReference.isStable && inputReference.propertyName === null && inputReference.symbolId === reference.resolvedSymbol?.id);
|
|
108437
|
+
if (directInput) return {
|
|
108438
|
+
inputNames: new Set([directInput.inputName]),
|
|
108439
|
+
isWholeContainer: false
|
|
108440
|
+
};
|
|
108441
|
+
const containedInputNames = /* @__PURE__ */ new Set();
|
|
108442
|
+
for (const inputReference of inputReferences) if (inputReference.isStable && inputReference.propertyName !== null && inputReference.symbolId === reference.resolvedSymbol.id) containedInputNames.add(inputReference.inputName);
|
|
108443
|
+
return containedInputNames.size > 0 ? {
|
|
108444
|
+
inputNames: containedInputNames,
|
|
108445
|
+
isWholeContainer: true
|
|
108446
|
+
} : null;
|
|
108447
|
+
}
|
|
108448
|
+
if (!isNodeOfType(forwardedExpression, "MemberExpression")) return null;
|
|
108449
|
+
const propertyName = getStaticPropertyName(forwardedExpression);
|
|
108450
|
+
const receiver = stripParenExpression(forwardedExpression.object);
|
|
108451
|
+
if (propertyName === null || !isNodeOfType(receiver, "Identifier")) return null;
|
|
108452
|
+
const receiverReference = scopes.referenceFor(receiver);
|
|
108453
|
+
const matchedInput = inputReferences.find((inputReference) => inputReference.isStable && inputReference.propertyName === propertyName && inputReference.symbolId === receiverReference?.resolvedSymbol?.id);
|
|
108454
|
+
return matchedInput ? {
|
|
108455
|
+
inputNames: new Set([matchedInput.inputName]),
|
|
108456
|
+
isWholeContainer: false
|
|
108457
|
+
} : null;
|
|
108458
|
+
};
|
|
108459
|
+
const getParameterInputReferences = (parameter, forwardedInput, scopes) => {
|
|
108460
|
+
const unwrappedParameter = stripParenExpression(parameter);
|
|
108461
|
+
if (isNodeOfType(unwrappedParameter, "Identifier")) {
|
|
108462
|
+
if (forwardedInput.isWholeContainer) return [];
|
|
108463
|
+
const symbol = scopes.symbolFor(unwrappedParameter);
|
|
108464
|
+
const inputName = [...forwardedInput.inputNames][0];
|
|
108465
|
+
return symbol && inputName ? [{
|
|
108466
|
+
inputName,
|
|
108467
|
+
isStable: isSymbolStable(symbol),
|
|
108468
|
+
propertyName: null,
|
|
108469
|
+
symbolId: symbol.id
|
|
108470
|
+
}] : [];
|
|
108471
|
+
}
|
|
108472
|
+
if (!isNodeOfType(unwrappedParameter, "ObjectPattern")) return [];
|
|
108473
|
+
const references = [];
|
|
108474
|
+
for (const property of unwrappedParameter.properties) {
|
|
108475
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
108476
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
108477
|
+
if (propertyName === null || forwardedInput.isWholeContainer && !forwardedInput.inputNames.has(propertyName)) continue;
|
|
108478
|
+
const bindingIdentifier = getPatternBindingIdentifier(property.value);
|
|
108479
|
+
const symbol = bindingIdentifier ? scopes.symbolFor(bindingIdentifier) : null;
|
|
108480
|
+
if (symbol) references.push({
|
|
108481
|
+
inputName: propertyName,
|
|
108482
|
+
isStable: isSymbolStable(symbol),
|
|
108483
|
+
propertyName: null,
|
|
108484
|
+
symbolId: symbol.id
|
|
108485
|
+
});
|
|
108486
|
+
}
|
|
108487
|
+
return references;
|
|
108488
|
+
};
|
|
108489
|
+
const getComponentPropInputReferences = (openingElement, componentFunction, inputReferences, scopes) => {
|
|
108490
|
+
if (!isFunctionLike$1(componentFunction)) return [];
|
|
108491
|
+
const parameter = componentFunction.params[0];
|
|
108492
|
+
if (!parameter) return [];
|
|
108493
|
+
const references = [];
|
|
108494
|
+
for (const attribute of openingElement.attributes) {
|
|
108495
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier") || !isNodeOfType(attribute.value, "JSXExpressionContainer")) continue;
|
|
108496
|
+
const attributeName = attribute.name.name;
|
|
108497
|
+
if (getAuthoritativeJsxAttribute(openingElement.attributes, attributeName) !== attribute) continue;
|
|
108498
|
+
const forwardedInput = getForwardedInput(attribute.value.expression, inputReferences, scopes);
|
|
108499
|
+
if (!forwardedInput || forwardedInput.isWholeContainer) continue;
|
|
108500
|
+
const unwrappedParameter = stripParenExpression(parameter);
|
|
108501
|
+
if (isNodeOfType(unwrappedParameter, "Identifier")) {
|
|
108502
|
+
const symbol = scopes.symbolFor(unwrappedParameter);
|
|
108503
|
+
if (symbol) references.push({
|
|
108504
|
+
inputName: attributeName,
|
|
108505
|
+
isStable: isSymbolStable(symbol),
|
|
108506
|
+
propertyName: attributeName,
|
|
108507
|
+
symbolId: symbol.id
|
|
108508
|
+
});
|
|
108509
|
+
continue;
|
|
108510
|
+
}
|
|
108511
|
+
const bindingIdentifier = getObjectPatternPropertyBinding(unwrappedParameter, attributeName);
|
|
108512
|
+
const symbol = bindingIdentifier ? scopes.symbolFor(bindingIdentifier) : null;
|
|
108513
|
+
if (symbol) references.push({
|
|
108514
|
+
inputName: attributeName,
|
|
108515
|
+
isStable: isSymbolStable(symbol),
|
|
108516
|
+
propertyName: null,
|
|
108517
|
+
symbolId: symbol.id
|
|
108518
|
+
});
|
|
108519
|
+
}
|
|
108520
|
+
return references;
|
|
108521
|
+
};
|
|
108522
|
+
const resolveLocalComponentFunction = (openingElement, scopes) => {
|
|
108523
|
+
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return null;
|
|
108524
|
+
const symbol = resolveConstIdentifierAlias(openingElement.name, scopes);
|
|
108525
|
+
if (!symbol || symbol.kind === "import" || !symbol.initializer || !isSymbolStable(symbol) || hasSymbolWriteBefore(symbol, openingElement.name, scopes)) return null;
|
|
108526
|
+
return unwrapProvenReactHocFunction(symbol.initializer, scopes);
|
|
108527
|
+
};
|
|
108528
|
+
const collectFunctionRenderedRootNames = (functionNode, names, scopes, analysis) => {
|
|
108529
|
+
if (!isFunctionLike$1(functionNode) || analysis.visitedFunctionNodes.has(functionNode)) return;
|
|
108530
|
+
analysis.visitedFunctionNodes.add(functionNode);
|
|
108531
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) {
|
|
108532
|
+
collectReturnedJsxRootNames(functionNode.body, names, scopes, analysis);
|
|
108533
|
+
return;
|
|
108534
|
+
}
|
|
108535
|
+
for (const returnStatement of getReachableFunctionReturnStatements(functionNode, scopes)) if (returnStatement.argument) collectReturnedJsxRootNames(returnStatement.argument, names, scopes, analysis);
|
|
108536
|
+
};
|
|
108537
|
+
const collectLocalComponentRenderedRootNames = (element, names, scopes, analysis) => {
|
|
108538
|
+
if (!analysis.canFollowLocalRenderer) return false;
|
|
108539
|
+
const componentFunction = resolveLocalComponentFunction(element.openingElement, scopes);
|
|
108540
|
+
if (!componentFunction) return false;
|
|
108541
|
+
if (analysis.visitedFunctionNodes.has(componentFunction)) return true;
|
|
108542
|
+
const componentInputReferences = getComponentPropInputReferences(element.openingElement, componentFunction, analysis.inputReferences, scopes);
|
|
108543
|
+
if (componentInputReferences.length === 0 || !functionHasOnlyInputDependentSelections(componentFunction, componentInputReferences, scopes)) return false;
|
|
108544
|
+
const componentRootNames = /* @__PURE__ */ new Set();
|
|
108545
|
+
collectFunctionRenderedRootNames(componentFunction, componentRootNames, scopes, {
|
|
108546
|
+
canFollowLocalRenderer: false,
|
|
108547
|
+
inputReferences: componentInputReferences,
|
|
108548
|
+
visitedFunctionNodes: analysis.visitedFunctionNodes,
|
|
108549
|
+
visitedSymbolIds: analysis.visitedSymbolIds
|
|
108550
|
+
});
|
|
108551
|
+
if (componentRootNames.size === 0) return false;
|
|
108552
|
+
for (const componentRootName of componentRootNames) names.add(componentRootName);
|
|
108553
|
+
return true;
|
|
108554
|
+
};
|
|
108555
|
+
const collectItemSelectedComponentRootNames = (element, names, scopes, analysis) => {
|
|
108556
|
+
const componentName = element.openingElement.name;
|
|
108557
|
+
if (!isNodeOfType(componentName, "JSXIdentifier")) return false;
|
|
108558
|
+
const symbol = resolveConstIdentifierAlias(componentName, scopes);
|
|
108559
|
+
if (symbol?.kind !== "const" || !symbol.initializer || analysis.visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, componentName, scopes) || !expressionHasOnlyInputDependentSelections(symbol.initializer, analysis.inputReferences, scopes)) return false;
|
|
108560
|
+
analysis.visitedSymbolIds.add(symbol.id);
|
|
108561
|
+
const componentIdentities = /* @__PURE__ */ new Set();
|
|
108562
|
+
const didResolveEveryComponent = collectStaticComponentIdentities(symbol.initializer, componentIdentities, scopes);
|
|
108563
|
+
analysis.visitedSymbolIds.delete(symbol.id);
|
|
108564
|
+
if (!didResolveEveryComponent || componentIdentities.size === 0) return false;
|
|
108565
|
+
for (const componentIdentity of componentIdentities) names.add(componentIdentity);
|
|
108566
|
+
return true;
|
|
108567
|
+
};
|
|
108568
|
+
const collectReactCreateElementRootNames = (callExpression, names, scopes, analysis) => {
|
|
108569
|
+
if (!isReactApiCall(callExpression, "createElement", scopes, {
|
|
108570
|
+
allowGlobalReactNamespace: true,
|
|
108571
|
+
resolveNamedAliases: true
|
|
108572
|
+
})) return false;
|
|
108573
|
+
const componentArgument = callExpression.arguments[0];
|
|
108574
|
+
if (!componentArgument || isNodeOfType(componentArgument, "SpreadElement") || isReactFragmentReference(componentArgument, scopes)) return false;
|
|
108575
|
+
const componentIdentities = /* @__PURE__ */ new Set();
|
|
108576
|
+
if (!collectStaticComponentIdentities(componentArgument, componentIdentities, scopes)) return false;
|
|
108577
|
+
if (componentIdentities.size > 1 && !expressionHasOnlyInputDependentSelections(componentArgument, analysis.inputReferences, scopes)) return false;
|
|
108578
|
+
for (const componentIdentity of componentIdentities) names.add(componentIdentity);
|
|
108579
|
+
return componentIdentities.size > 0;
|
|
108580
|
+
};
|
|
108581
|
+
const collectLocalHelperRenderedRootNames = (callExpression, names, scopes, analysis) => {
|
|
108582
|
+
if (!analysis.canFollowLocalRenderer) return false;
|
|
108583
|
+
const helperCallee = stripParenExpression(callExpression.callee);
|
|
108584
|
+
if (!isNodeOfType(helperCallee, "Identifier")) return false;
|
|
108585
|
+
const helperFunction = resolveStaticLocalCallFunction(callExpression, scopes);
|
|
108586
|
+
if (!helperFunction || !isFunctionLike$1(helperFunction) || helperFunction.async || helperFunction.generator || analysis.visitedFunctionNodes.has(helperFunction)) return false;
|
|
108587
|
+
const helperSymbol = scopes.symbolFor(helperCallee);
|
|
108588
|
+
const functionSymbol = isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.id ? scopes.symbolFor(helperFunction.id) : null;
|
|
108589
|
+
if (helperSymbol && !isSymbolStable(helperSymbol) || functionSymbol && !isSymbolStable(functionSymbol)) return false;
|
|
108590
|
+
const helperInputReferences = [];
|
|
108591
|
+
for (let argumentIndex = 0; argumentIndex < callExpression.arguments.length; argumentIndex += 1) {
|
|
108592
|
+
const argument = callExpression.arguments[argumentIndex];
|
|
108593
|
+
const parameter = helperFunction.params[argumentIndex];
|
|
108594
|
+
if (!argument || isNodeOfType(argument, "SpreadElement") || !parameter) continue;
|
|
108595
|
+
const forwardedInput = getForwardedInput(argument, analysis.inputReferences, scopes);
|
|
108596
|
+
if (!forwardedInput) continue;
|
|
108597
|
+
helperInputReferences.push(...getParameterInputReferences(parameter, forwardedInput, scopes));
|
|
108598
|
+
}
|
|
108599
|
+
if (helperInputReferences.length === 0 || !functionHasOnlyInputDependentSelections(helperFunction, helperInputReferences, scopes)) return false;
|
|
108600
|
+
const helperRootNames = /* @__PURE__ */ new Set();
|
|
108601
|
+
collectFunctionRenderedRootNames(helperFunction, helperRootNames, scopes, {
|
|
108602
|
+
canFollowLocalRenderer: false,
|
|
108603
|
+
inputReferences: helperInputReferences,
|
|
108604
|
+
visitedFunctionNodes: analysis.visitedFunctionNodes,
|
|
108605
|
+
visitedSymbolIds: analysis.visitedSymbolIds
|
|
108606
|
+
});
|
|
108607
|
+
if (helperRootNames.size === 0) return false;
|
|
108608
|
+
for (const helperRootName of helperRootNames) names.add(helperRootName);
|
|
108609
|
+
return true;
|
|
108610
|
+
};
|
|
108611
|
+
const collectReturnedJsxRootNames = (expression, names, scopes, analysis) => {
|
|
108612
|
+
const unwrappedExpression = getFinalSequenceExpressionValue(expression);
|
|
108613
|
+
if (isNodeOfType(unwrappedExpression, "JSXElement")) {
|
|
108614
|
+
if (collectItemSelectedComponentRootNames(unwrappedExpression, names, scopes, analysis) || collectLocalComponentRenderedRootNames(unwrappedExpression, names, scopes, analysis)) return;
|
|
108615
|
+
const rootNames = getRenderedRootNames(unwrappedExpression, scopes);
|
|
108616
|
+
if (rootNames) for (const rootName of rootNames) names.add(rootName);
|
|
108617
|
+
return;
|
|
108618
|
+
}
|
|
108619
|
+
if (isNodeOfType(unwrappedExpression, "JSXFragment")) {
|
|
108620
|
+
const rootNames = getRenderedRootNames(unwrappedExpression, scopes);
|
|
108621
|
+
if (rootNames) for (const rootName of rootNames) names.add(rootName);
|
|
108622
|
+
return;
|
|
108623
|
+
}
|
|
108624
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
108625
|
+
if (collectReactCreateElementRootNames(unwrappedExpression, names, scopes, analysis) || collectLocalHelperRenderedRootNames(unwrappedExpression, names, scopes, analysis)) return;
|
|
108626
|
+
}
|
|
108627
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
108628
|
+
const symbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
|
|
108629
|
+
if (symbol?.kind === "const" && symbol.initializer && !analysis.visitedSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, unwrappedExpression, scopes) && expressionHasOnlyInputDependentSelections(symbol.initializer, analysis.inputReferences, scopes)) {
|
|
108630
|
+
analysis.visitedSymbolIds.add(symbol.id);
|
|
108631
|
+
collectReturnedJsxRootNames(symbol.initializer, names, scopes, analysis);
|
|
108632
|
+
analysis.visitedSymbolIds.delete(symbol.id);
|
|
108633
|
+
return;
|
|
108634
|
+
}
|
|
108635
|
+
}
|
|
108636
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
108637
|
+
const staticTestValue = readStaticSelectorTruthiness(unwrappedExpression.test, scopes);
|
|
108638
|
+
if (staticTestValue !== null) {
|
|
108639
|
+
collectReturnedJsxRootNames(staticTestValue ? unwrappedExpression.consequent : unwrappedExpression.alternate, names, scopes, analysis);
|
|
108640
|
+
return;
|
|
108641
|
+
}
|
|
108642
|
+
collectReturnedJsxRootNames(unwrappedExpression.consequent, names, scopes, analysis);
|
|
108643
|
+
collectReturnedJsxRootNames(unwrappedExpression.alternate, names, scopes, analysis);
|
|
108644
|
+
return;
|
|
108645
|
+
}
|
|
108646
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
108647
|
+
for (const resultBranch of getStaticLogicalExpressionResultBranches(unwrappedExpression)) collectReturnedJsxRootNames(resultBranch, names, scopes, analysis);
|
|
108648
|
+
return;
|
|
108649
|
+
}
|
|
108650
|
+
};
|
|
108651
|
+
const resolveFunctionFromInitializer = (initializer, resultSymbol, scopes) => {
|
|
108652
|
+
const expression = stripParenExpression(initializer);
|
|
108653
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
|
|
108654
|
+
const callbackArgument = getTransparentReactCallbackWrapperArgument(expression, resultSymbol, scopes);
|
|
108655
|
+
if (callbackArgument && (isNodeOfType(callbackArgument, "ArrowFunctionExpression") || isNodeOfType(callbackArgument, "FunctionExpression"))) return callbackArgument;
|
|
108656
|
+
return null;
|
|
108657
|
+
};
|
|
108658
|
+
const resolveRenderItemFunction = (attribute, scopes) => {
|
|
108659
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
108660
|
+
const expression = stripParenExpression(attribute.value.expression);
|
|
108661
|
+
const directFunction = resolveFunctionFromInitializer(expression, null, scopes);
|
|
108662
|
+
if (directFunction) return directFunction;
|
|
108663
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
108664
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
108665
|
+
if (localFunction) return localFunction;
|
|
108666
|
+
const symbol = scopes.symbolFor(expression);
|
|
108667
|
+
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
108668
|
+
return resolveFunctionFromInitializer(symbol.initializer, symbol, scopes);
|
|
108669
|
+
};
|
|
108670
|
+
const renderItemHasHeterogeneousRootTypes = (attribute, scopes, resultCache) => {
|
|
108671
|
+
const renderItemFunction = resolveRenderItemFunction(attribute, scopes);
|
|
108672
|
+
if (!renderItemFunction || !isNodeOfType(renderItemFunction, "ArrowFunctionExpression") && !isNodeOfType(renderItemFunction, "FunctionExpression") && !isNodeOfType(renderItemFunction, "FunctionDeclaration")) return false;
|
|
108673
|
+
const cachedResult = resultCache.get(renderItemFunction);
|
|
108674
|
+
if (cachedResult !== void 0) return cachedResult;
|
|
108675
|
+
const returnedRootNames = /* @__PURE__ */ new Set();
|
|
108676
|
+
const inputReferences = getRenderItemInputReferences(renderItemFunction, scopes);
|
|
108677
|
+
const selectionAnalysis = analyzeFunctionInputSelections(renderItemFunction, inputReferences, scopes);
|
|
108678
|
+
const returnStatements = isNodeOfType(renderItemFunction.body, "BlockStatement") ? getReachableFunctionReturnStatements(renderItemFunction, scopes) : [];
|
|
108679
|
+
if (selectionAnalysis.hasUnrelatedSelection && !selectionAnalysis.hasProvenInputDependentRootSelection || returnStatements.length > 1 && !selectionAnalysis.hasInputDependentSelection) {
|
|
108680
|
+
resultCache.set(renderItemFunction, false);
|
|
108681
|
+
return false;
|
|
108682
|
+
}
|
|
108683
|
+
collectFunctionRenderedRootNames(renderItemFunction, returnedRootNames, scopes, {
|
|
108684
|
+
canFollowLocalRenderer: true,
|
|
108685
|
+
inputReferences,
|
|
108686
|
+
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
108687
|
+
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
108688
|
+
});
|
|
108689
|
+
const hasHeterogeneousRootTypes = returnedRootNames.size > 1;
|
|
108690
|
+
resultCache.set(renderItemFunction, hasHeterogeneousRootTypes);
|
|
108691
|
+
return hasHeterogeneousRootTypes;
|
|
108692
|
+
};
|
|
107054
108693
|
const rnListRecyclableWithoutTypes = defineRule({
|
|
107055
108694
|
id: "rn-list-recyclable-without-types",
|
|
107056
108695
|
title: "Recyclable list missing getItemType",
|
|
107057
108696
|
tags: ["test-noise"],
|
|
107058
108697
|
requires: ["react-native"],
|
|
107059
108698
|
severity: "warn",
|
|
107060
|
-
recommendation: "When rows have different shapes, reused cells can show the wrong layout. Add `getItemType
|
|
108699
|
+
recommendation: "When rows have different shapes, reused cells can show the wrong layout. Add `getItemType` that returns a stable type for each row shape so FlashList keeps separate recycling pools.",
|
|
107061
108700
|
create: (context) => {
|
|
107062
108701
|
let fileImportsRecycler = false;
|
|
108702
|
+
const renderItemResultCache = /* @__PURE__ */ new WeakMap();
|
|
107063
108703
|
return {
|
|
107064
108704
|
Program(node) {
|
|
107065
108705
|
fileImportsRecycler = hasImportFromModules(node, RECYCLABLE_LIST_PACKAGE_SOURCES);
|
|
@@ -107068,20 +108708,20 @@ const rnListRecyclableWithoutTypes = defineRule({
|
|
|
107068
108708
|
if (!fileImportsRecycler) return;
|
|
107069
108709
|
const elementName = resolveJsxElementName(node);
|
|
107070
108710
|
if (!elementName) return;
|
|
107071
|
-
|
|
107072
|
-
|
|
107073
|
-
let
|
|
107074
|
-
|
|
107075
|
-
|
|
107076
|
-
|
|
107077
|
-
|
|
107078
|
-
|
|
107079
|
-
|
|
107080
|
-
|
|
107081
|
-
|
|
107082
|
-
if (hasRecycleItemsEnabled && !hasGetItemType) context.report({
|
|
108711
|
+
const canonicalRecyclerName = resolveImportedRecyclerName(node, context.scopes, { allowNamespaceMemberAccess: true });
|
|
108712
|
+
if (canonicalRecyclerName === null) return;
|
|
108713
|
+
let hasRecycleItemsEnabled = SHOPIFY_FLASH_LIST_COMPONENTS.has(canonicalRecyclerName) && isFlashListV2OrNewer(context);
|
|
108714
|
+
const recycleItemsAttribute = getAuthoritativeJsxAttribute(node.attributes, "recycleItems");
|
|
108715
|
+
if (recycleItemsAttribute) if (!recycleItemsAttribute.value) hasRecycleItemsEnabled = true;
|
|
108716
|
+
else if (isNodeOfType(recycleItemsAttribute.value, "JSXExpressionContainer") && isNodeOfType(recycleItemsAttribute.value.expression, "Literal")) hasRecycleItemsEnabled = recycleItemsAttribute.value.expression.value === true;
|
|
108717
|
+
else hasRecycleItemsEnabled = true;
|
|
108718
|
+
else if (node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute") && canExpressionOverrideJsxAttribute(attribute.argument, "recycleItems", true, context.scopes))) hasRecycleItemsEnabled = false;
|
|
108719
|
+
const hasPossibleSpreadGetItemType = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute") && canExpressionOverrideJsxAttribute(attribute.argument, "getItemType", true, context.scopes));
|
|
108720
|
+
const hasGetItemType = getAuthoritativeJsxAttribute(node.attributes, "getItemType") !== null || hasPossibleSpreadGetItemType;
|
|
108721
|
+
const renderItemAttribute = getAuthoritativeJsxAttribute(node.attributes, "renderItem");
|
|
108722
|
+
if (hasRecycleItemsEnabled && !hasGetItemType && renderItemAttribute && renderItemHasHeterogeneousRootTypes(renderItemAttribute, context.scopes, renderItemResultCache)) context.report({
|
|
107083
108723
|
node,
|
|
107084
|
-
message: `Your users see rows of different shapes reuse the wrong cells when <${elementName}
|
|
108724
|
+
message: `Your users see rows of different shapes reuse the wrong cells when <${elementName}> recycles them without \`getItemType\`.`
|
|
107085
108725
|
});
|
|
107086
108726
|
}
|
|
107087
108727
|
};
|
|
@@ -107728,7 +109368,7 @@ const rnNoNonNativeNavigator = defineRule({
|
|
|
107728
109368
|
tags: ["test-noise"],
|
|
107729
109369
|
requires: ["react-native"],
|
|
107730
109370
|
severity: "warn",
|
|
107731
|
-
recommendation: "Use `@react-navigation/native-stack`
|
|
109371
|
+
recommendation: "Use `@react-navigation/native-stack` for stack navigation. Treat drawers separately because standalone React Navigation has no native drawer replacement.",
|
|
107732
109372
|
create: (context) => ({ ImportDeclaration(node) {
|
|
107733
109373
|
const source = node.source?.value;
|
|
107734
109374
|
if (typeof source !== "string") return;
|
|
@@ -108302,6 +109942,28 @@ const rnNoSingleElementStyleArray = defineRule({
|
|
|
108302
109942
|
} })
|
|
108303
109943
|
});
|
|
108304
109944
|
//#endregion
|
|
109945
|
+
//#region src/plugin/rules/react-native/rn-platform-shaking-use-direct-import.ts
|
|
109946
|
+
const REACT_NATIVE_MODULE_SOURCE = "react-native";
|
|
109947
|
+
const rnPlatformShakingUseDirectImport = defineRule({
|
|
109948
|
+
id: "rn-platform-shaking-use-direct-import",
|
|
109949
|
+
title: "Platform reached through React Native namespace",
|
|
109950
|
+
tags: ["test-noise"],
|
|
109951
|
+
requires: ["expo:54"],
|
|
109952
|
+
severity: "warn",
|
|
109953
|
+
recommendation: "Import `Platform` directly with `import { Platform } from \"react-native\"` so Expo can remove code for the other platform.",
|
|
109954
|
+
create: (context) => ({ MemberExpression(node) {
|
|
109955
|
+
if (node.computed) return;
|
|
109956
|
+
if (!isNodeOfType(node.object, "Identifier")) return;
|
|
109957
|
+
if (!isNodeOfType(node.property, "Identifier") || node.property.name !== "Platform") return;
|
|
109958
|
+
if (context.scopes.symbolFor(node.object)?.kind !== "import") return;
|
|
109959
|
+
if (!isNamespaceImportFromModule$1(node, node.object.name, REACT_NATIVE_MODULE_SOURCE)) return;
|
|
109960
|
+
context.report({
|
|
109961
|
+
node,
|
|
109962
|
+
message: "Expo cannot tree-shake platform branches reached through the React Native namespace, so both platform paths stay in the bundle."
|
|
109963
|
+
});
|
|
109964
|
+
} })
|
|
109965
|
+
});
|
|
109966
|
+
//#endregion
|
|
108305
109967
|
//#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
|
|
108306
109968
|
const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
108307
109969
|
id: "rn-prefer-content-inset-adjustment",
|
|
@@ -108668,6 +110330,97 @@ const rnPressableSharedValueMutation = defineRule({
|
|
|
108668
110330
|
}
|
|
108669
110331
|
});
|
|
108670
110332
|
//#endregion
|
|
110333
|
+
//#region src/plugin/rules/react-native/utils/resolve-reanimated-api-name.ts
|
|
110334
|
+
const REANIMATED_MODULE_SOURCE = "react-native-reanimated";
|
|
110335
|
+
const resolveReanimatedApiName = (callExpression, scopes, supportedApiNames) => {
|
|
110336
|
+
const reference = resolveImportedApiReference(callExpression.callee, scopes);
|
|
110337
|
+
if (reference?.source !== REANIMATED_MODULE_SOURCE || reference.importedName === null || !supportedApiNames.has(reference.importedName)) return null;
|
|
110338
|
+
return reference.importedName;
|
|
110339
|
+
};
|
|
110340
|
+
//#endregion
|
|
110341
|
+
//#region src/plugin/rules/react-native/rn-reanimated-4-no-legacy-spring-thresholds.ts
|
|
110342
|
+
const WITH_SPRING_API_NAMES = new Set(["withSpring"]);
|
|
110343
|
+
const LEGACY_SPRING_THRESHOLD_NAMES = new Set(["restDisplacementThreshold", "restSpeedThreshold"]);
|
|
110344
|
+
const rnReanimated4NoLegacySpringThresholds = defineRule({
|
|
110345
|
+
id: "rn-reanimated-4-no-legacy-spring-thresholds",
|
|
110346
|
+
title: "Legacy Reanimated spring threshold",
|
|
110347
|
+
tags: ["migration-hint"],
|
|
110348
|
+
requires: ["reanimated:4"],
|
|
110349
|
+
severity: "warn",
|
|
110350
|
+
recommendation: "Replace Reanimated 3 rest thresholds with Reanimated 4's `energyThreshold` spring option.",
|
|
110351
|
+
create: (context) => ({ CallExpression(node) {
|
|
110352
|
+
if (!resolveReanimatedApiName(node, context.scopes, WITH_SPRING_API_NAMES)) return;
|
|
110353
|
+
const configArgument = node.arguments[1];
|
|
110354
|
+
if (!configArgument || isNodeOfType(configArgument, "SpreadElement")) return;
|
|
110355
|
+
const unwrappedConfig = stripParenExpression(configArgument);
|
|
110356
|
+
if (!isNodeOfType(unwrappedConfig, "ObjectExpression")) return;
|
|
110357
|
+
for (const property of unwrappedConfig.properties) {
|
|
110358
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
110359
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
110360
|
+
if (!propertyName || !LEGACY_SPRING_THRESHOLD_NAMES.has(propertyName)) continue;
|
|
110361
|
+
context.report({
|
|
110362
|
+
node: property,
|
|
110363
|
+
message: `Reanimated 4 removed \`${propertyName}\`; use the \`energyThreshold\` spring option instead.`
|
|
110364
|
+
});
|
|
110365
|
+
}
|
|
110366
|
+
} })
|
|
110367
|
+
});
|
|
110368
|
+
//#endregion
|
|
110369
|
+
//#region src/plugin/rules/react-native/rn-reanimated-4-no-removed-api.ts
|
|
110370
|
+
const REMOVED_API_MESSAGE_BY_NAME = new Map([
|
|
110371
|
+
["useAnimatedGestureHandler", "Reanimated 4 removed `useAnimatedGestureHandler`; migrate this gesture to the Gesture API."],
|
|
110372
|
+
["useWorkletCallback", "Reanimated 4 removed `useWorkletCallback`; use React's `useCallback` with a `worklet` directive instead."],
|
|
110373
|
+
["combineTransition", "Reanimated 4 removed `combineTransition`; compose the transition with `EntryExitTransition` instead."],
|
|
110374
|
+
["addWhitelistedNativeProps", "Reanimated 4 removed `addWhitelistedNativeProps` because prop whitelisting is no longer needed."],
|
|
110375
|
+
["addWhitelistedUIProps", "Reanimated 4 removed `addWhitelistedUIProps` because prop whitelisting is no longer needed."]
|
|
110376
|
+
]);
|
|
110377
|
+
const REMOVED_API_NAMES = new Set(REMOVED_API_MESSAGE_BY_NAME.keys());
|
|
110378
|
+
const rnReanimated4NoRemovedApi = defineRule({
|
|
110379
|
+
id: "rn-reanimated-4-no-removed-api",
|
|
110380
|
+
title: "API removed in Reanimated 4",
|
|
110381
|
+
tags: ["migration-hint"],
|
|
110382
|
+
requires: ["reanimated:4"],
|
|
110383
|
+
severity: "warn",
|
|
110384
|
+
recommendation: "Migrate removed Reanimated APIs to their Reanimated 4 or Gesture API replacements before upgrading.",
|
|
110385
|
+
create: (context) => ({ CallExpression(node) {
|
|
110386
|
+
const apiName = resolveReanimatedApiName(node, context.scopes, REMOVED_API_NAMES);
|
|
110387
|
+
if (!apiName) return;
|
|
110388
|
+
const message = REMOVED_API_MESSAGE_BY_NAME.get(apiName);
|
|
110389
|
+
if (!message) return;
|
|
110390
|
+
context.report({
|
|
110391
|
+
node,
|
|
110392
|
+
message
|
|
110393
|
+
});
|
|
110394
|
+
} })
|
|
110395
|
+
});
|
|
110396
|
+
//#endregion
|
|
110397
|
+
//#region src/plugin/rules/react-native/rn-reanimated-4-use-worklets-scheduler.ts
|
|
110398
|
+
const WORKLETS_MIGRATION_BY_REANIMATED_API = new Map([
|
|
110399
|
+
["runOnUI", "replace `runOnUI(fn)(...args)` with `scheduleOnUI(fn, ...args)`"],
|
|
110400
|
+
["runOnJS", "replace `runOnJS(fn)(...args)` with `scheduleOnRN(fn, ...args)`"],
|
|
110401
|
+
["executeOnUIRuntimeSync", "replace `executeOnUIRuntimeSync(fn)(...args)` with `runOnUISync(fn, ...args)`"],
|
|
110402
|
+
["runOnRuntime", "replace `runOnRuntime(runtime, fn)(...args)` with `scheduleOnRuntime(runtime, fn, ...args)`"]
|
|
110403
|
+
]);
|
|
110404
|
+
const REANIMATED_SCHEDULER_API_NAMES = new Set(WORKLETS_MIGRATION_BY_REANIMATED_API.keys());
|
|
110405
|
+
const rnReanimated4UseWorkletsScheduler = defineRule({
|
|
110406
|
+
id: "rn-reanimated-4-use-worklets-scheduler",
|
|
110407
|
+
title: "Scheduler moved to Worklets",
|
|
110408
|
+
tags: ["migration-hint"],
|
|
110409
|
+
requires: ["reanimated:4"],
|
|
110410
|
+
severity: "warn",
|
|
110411
|
+
recommendation: "Import the corresponding scheduler from `react-native-worklets` when migrating to Reanimated 4.",
|
|
110412
|
+
create: (context) => ({ CallExpression(node) {
|
|
110413
|
+
const apiName = resolveReanimatedApiName(node, context.scopes, REANIMATED_SCHEDULER_API_NAMES);
|
|
110414
|
+
if (!apiName) return;
|
|
110415
|
+
const migration = WORKLETS_MIGRATION_BY_REANIMATED_API.get(apiName);
|
|
110416
|
+
if (!migration) return;
|
|
110417
|
+
context.report({
|
|
110418
|
+
node,
|
|
110419
|
+
message: `For Reanimated 4, ${migration} from \`react-native-worklets\`.`
|
|
110420
|
+
});
|
|
110421
|
+
} })
|
|
110422
|
+
});
|
|
110423
|
+
//#endregion
|
|
108671
110424
|
//#region src/plugin/rules/react-native/utils/scrollview_names.ts
|
|
108672
110425
|
const SCROLLVIEW_NAMES = new Set([
|
|
108673
110426
|
"ScrollView",
|
|
@@ -108931,7 +110684,7 @@ const rnStylePreferBoxShadow = defineRule({
|
|
|
108931
110684
|
tags: ["test-noise"],
|
|
108932
110685
|
requires: ["react-native"],
|
|
108933
110686
|
severity: "warn",
|
|
108934
|
-
recommendation: "These shadow keys only work on one platform. On
|
|
110687
|
+
recommendation: "These shadow keys only work on one platform. On the New Architecture, use a CSS `boxShadow` string like `boxShadow: \"0 2px 8px rgba(0,0,0,0.1)\"`, which works on both.",
|
|
108935
110688
|
create: (context) => {
|
|
108936
110689
|
if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$3;
|
|
108937
110690
|
return {
|
|
@@ -114800,7 +116553,7 @@ const getMutableConstInitializer = (init, scopes) => {
|
|
|
114800
116553
|
allowsPropertyDeletion: true
|
|
114801
116554
|
};
|
|
114802
116555
|
};
|
|
114803
|
-
const getMemberPropertyName$
|
|
116556
|
+
const getMemberPropertyName$2 = (memberExpression) => {
|
|
114804
116557
|
if (!memberExpression.computed && isNodeOfType(memberExpression.property, "Identifier")) return memberExpression.property.name;
|
|
114805
116558
|
if (memberExpression.computed && isNodeOfType(memberExpression.property, "Literal") && typeof memberExpression.property.value === "string") return memberExpression.property.value;
|
|
114806
116559
|
return null;
|
|
@@ -114829,7 +116582,7 @@ const isDirectContentsMutation = (referenceIdentifier) => {
|
|
|
114829
116582
|
if (isNodeOfType(chainTipParent, "UpdateExpression") && chainTipParent.argument === chainTip) return true;
|
|
114830
116583
|
if (isNodeOfType(chainTipParent, "UnaryExpression") && chainTipParent.operator === "delete" && chainTipParent.argument === chainTip) return true;
|
|
114831
116584
|
if (isNodeOfType(chainTipParent, "CallExpression") && chainTipParent.callee === chainTip) {
|
|
114832
|
-
const methodName = getMemberPropertyName$
|
|
116585
|
+
const methodName = getMemberPropertyName$2(chainTip);
|
|
114833
116586
|
return methodName !== null && MUTATING_METHODS.has(methodName);
|
|
114834
116587
|
}
|
|
114835
116588
|
return false;
|
|
@@ -114839,7 +116592,7 @@ const getRootPropertyName = (referenceIdentifier) => {
|
|
|
114839
116592
|
while (receiver.parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(receiver.parent.type) && "expression" in receiver.parent && receiver.parent.expression === receiver) receiver = receiver.parent;
|
|
114840
116593
|
if (!receiver.parent || !isNodeOfType(receiver.parent, "MemberExpression")) return null;
|
|
114841
116594
|
if (receiver.parent.object !== receiver) return null;
|
|
114842
|
-
return getMemberPropertyName$
|
|
116595
|
+
return getMemberPropertyName$2(receiver.parent);
|
|
114843
116596
|
};
|
|
114844
116597
|
const isDeleteContentsMutation = (referenceIdentifier) => {
|
|
114845
116598
|
const chainTip = ascendMemberChain(referenceIdentifier);
|
|
@@ -114855,7 +116608,7 @@ const isSupportedNestedMethodMutation = (referenceIdentifier, nestedPropertyKind
|
|
|
114855
116608
|
const chainTip = ascendMemberChain(referenceIdentifier);
|
|
114856
116609
|
const chainTipParent = chainTip.parent;
|
|
114857
116610
|
if (!isNodeOfType(chainTip, "MemberExpression") || !chainTipParent || !isNodeOfType(chainTipParent, "CallExpression") || chainTipParent.callee !== chainTip) return true;
|
|
114858
|
-
const methodName = getMemberPropertyName$
|
|
116611
|
+
const methodName = getMemberPropertyName$2(chainTip);
|
|
114859
116612
|
if (!methodName) return false;
|
|
114860
116613
|
return Boolean(NESTED_MUTATING_METHODS[nestedPropertyKind]?.has(methodName));
|
|
114861
116614
|
};
|
|
@@ -114888,7 +116641,7 @@ const isMutatedThroughCallArgument = (referenceIdentifier, scopes, mayFollowCall
|
|
|
114888
116641
|
if (referenceArgumentIndex === -1) return false;
|
|
114889
116642
|
const callee = callExpression.callee;
|
|
114890
116643
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
114891
|
-
const methodName = getMemberPropertyName$
|
|
116644
|
+
const methodName = getMemberPropertyName$2(callee);
|
|
114892
116645
|
const calleeReceiver = stripParenExpression(callee.object);
|
|
114893
116646
|
return Boolean(callSiteRunsPerRequest && isNodeOfType(calleeReceiver, "Identifier") && calleeReceiver.name === "Object" && scopes.isGlobalReference(calleeReceiver) && methodName !== null && OBJECT_MUTATING_METHODS.has(methodName) && referenceArgumentIndex === 0 && (!initializer?.writablePropertyNames || isKnownPropertyObjectMutation(callExpression, methodName, initializer.writablePropertyNames)));
|
|
114894
116647
|
}
|
|
@@ -116441,7 +118194,7 @@ const tanstackStartLoaderParallelFetch = defineRule({
|
|
|
116441
118194
|
});
|
|
116442
118195
|
//#endregion
|
|
116443
118196
|
//#region src/plugin/rules/tanstack-start/tanstack-start-missing-head-content.ts
|
|
116444
|
-
const TANSTACK_ROUTER_PACKAGE = "@tanstack/react-router";
|
|
118197
|
+
const TANSTACK_ROUTER_PACKAGE$1 = "@tanstack/react-router";
|
|
116445
118198
|
const HEAD_CONTENT_COMPONENT_NAME = "HeadContent";
|
|
116446
118199
|
const DOCUMENT_HEAD_ELEMENT_NAME = "head";
|
|
116447
118200
|
const getJsxMemberRootName = (node) => {
|
|
@@ -116449,7 +118202,7 @@ const getJsxMemberRootName = (node) => {
|
|
|
116449
118202
|
if (isNodeOfType(node.object, "JSXMemberExpression")) return getJsxMemberRootName(node.object);
|
|
116450
118203
|
return null;
|
|
116451
118204
|
};
|
|
116452
|
-
const getJsxMemberPropertyName = (node) => {
|
|
118205
|
+
const getJsxMemberPropertyName$1 = (node) => {
|
|
116453
118206
|
if (isNodeOfType(node.property, "JSXIdentifier")) return node.property.name;
|
|
116454
118207
|
return null;
|
|
116455
118208
|
};
|
|
@@ -116458,7 +118211,7 @@ const getMemberRootName = (node) => {
|
|
|
116458
118211
|
if (isNodeOfType(node.object, "MemberExpression")) return getMemberRootName(node.object);
|
|
116459
118212
|
return null;
|
|
116460
118213
|
};
|
|
116461
|
-
const getMemberPropertyName = (node) => {
|
|
118214
|
+
const getMemberPropertyName$1 = (node) => {
|
|
116462
118215
|
if (isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
116463
118216
|
return null;
|
|
116464
118217
|
};
|
|
@@ -116498,7 +118251,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
116498
118251
|
const tanstackRouterNamespaceNames = /* @__PURE__ */ new Set();
|
|
116499
118252
|
const collectImportBindings = (node) => {
|
|
116500
118253
|
if (!isNodeOfType(node, "ImportDeclaration")) return;
|
|
116501
|
-
const isTanstackRouterImport = node.source.value === TANSTACK_ROUTER_PACKAGE;
|
|
118254
|
+
const isTanstackRouterImport = node.source.value === TANSTACK_ROUTER_PACKAGE$1;
|
|
116502
118255
|
const specifiers = node.specifiers ?? [];
|
|
116503
118256
|
for (const specifier of specifiers) {
|
|
116504
118257
|
if (isTanstackRouterImport && isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
|
|
@@ -116522,7 +118275,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
116522
118275
|
}
|
|
116523
118276
|
if (!isNodeOfType(initializer, "MemberExpression")) return;
|
|
116524
118277
|
const rootName = getMemberRootName(initializer);
|
|
116525
|
-
const propertyName = getMemberPropertyName(initializer);
|
|
118278
|
+
const propertyName = getMemberPropertyName$1(initializer);
|
|
116526
118279
|
if (rootName && tanstackRouterNamespaceNames.has(rootName) && propertyName === HEAD_CONTENT_COMPONENT_NAME) headContentComponentNames.add(node.id.name);
|
|
116527
118280
|
};
|
|
116528
118281
|
return {
|
|
@@ -116549,7 +118302,7 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
116549
118302
|
}
|
|
116550
118303
|
if (!isNodeOfType(node.name, "JSXMemberExpression")) return;
|
|
116551
118304
|
const rootName = getJsxMemberRootName(node.name);
|
|
116552
|
-
const propertyName = getJsxMemberPropertyName(node.name);
|
|
118305
|
+
const propertyName = getJsxMemberPropertyName$1(node.name);
|
|
116553
118306
|
if (rootName && tanstackRouterNamespaceNames.has(rootName) && propertyName === HEAD_CONTENT_COMPONENT_NAME) hasHeadContentElement = true;
|
|
116554
118307
|
if (isInsideDocumentHeadElement(node) && isCustomJsxElementName(node.name)) hasCustomHeadChildElement = true;
|
|
116555
118308
|
},
|
|
@@ -116563,6 +118316,275 @@ const tanstackStartMissingHeadContent = defineRule({
|
|
|
116563
118316
|
}
|
|
116564
118317
|
});
|
|
116565
118318
|
//#endregion
|
|
118319
|
+
//#region src/plugin/rules/tanstack-start/tanstack-start-missing-scripts.ts
|
|
118320
|
+
const TANSTACK_ROUTER_PACKAGE = "@tanstack/react-router";
|
|
118321
|
+
const TANSTACK_ROOT_ROUTE_FACTORY_NAMES = new Set(["createRootRoute", "createRootRouteWithContext"]);
|
|
118322
|
+
const SCRIPTS_COMPONENT_NAME = "Scripts";
|
|
118323
|
+
const DOCUMENT_BODY_ELEMENT_NAME = "body";
|
|
118324
|
+
const CLASS_RENDER_METHOD_NAME = "render";
|
|
118325
|
+
const ROOT_DOCUMENT_COMPONENT_PROPERTY_NAMES = ["component", "shellComponent"];
|
|
118326
|
+
const getJsxMemberRootIdentifier = (node) => {
|
|
118327
|
+
if (isNodeOfType(node.object, "JSXIdentifier")) return node.object;
|
|
118328
|
+
if (isNodeOfType(node.object, "JSXMemberExpression")) return getJsxMemberRootIdentifier(node.object);
|
|
118329
|
+
return null;
|
|
118330
|
+
};
|
|
118331
|
+
const getJsxMemberPropertyName = (node) => isNodeOfType(node.property, "JSXIdentifier") ? node.property.name : null;
|
|
118332
|
+
const getMemberRootIdentifier = (node) => {
|
|
118333
|
+
if (isNodeOfType(node.object, "Identifier")) return node.object;
|
|
118334
|
+
if (isNodeOfType(node.object, "MemberExpression")) return getMemberRootIdentifier(node.object);
|
|
118335
|
+
return null;
|
|
118336
|
+
};
|
|
118337
|
+
const getMemberPropertyName = (node) => isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
118338
|
+
const isDocumentBodyElement = (node) => isNodeOfType(node, "JSXElement") && isNodeOfType(node.openingElement.name, "JSXIdentifier") && node.openingElement.name.name === DOCUMENT_BODY_ELEMENT_NAME;
|
|
118339
|
+
const getEnclosingDocumentBodyElement = (node) => {
|
|
118340
|
+
let currentNode = node.parent;
|
|
118341
|
+
while (currentNode) {
|
|
118342
|
+
if (isDocumentBodyElement(currentNode)) return currentNode;
|
|
118343
|
+
currentNode = currentNode.parent;
|
|
118344
|
+
}
|
|
118345
|
+
return null;
|
|
118346
|
+
};
|
|
118347
|
+
const getClassComponentDeclaration = (node, context) => getClassBindingSymbol(node, context.scopes)?.declarationNode ?? node;
|
|
118348
|
+
const getEnclosingComponentDeclaration = (node, context) => {
|
|
118349
|
+
let currentNode = node.parent;
|
|
118350
|
+
while (currentNode) {
|
|
118351
|
+
if (isNodeOfType(currentNode, "FunctionDeclaration")) return currentNode;
|
|
118352
|
+
if (isNodeOfType(currentNode, "ArrowFunctionExpression") || isNodeOfType(currentNode, "FunctionExpression")) {
|
|
118353
|
+
const parentNode = findTransparentExpressionRoot(currentNode).parent;
|
|
118354
|
+
if (parentNode && (isNodeOfType(parentNode, "MethodDefinition") || isNodeOfType(parentNode, "PropertyDefinition")) && getStaticPropertyKeyName(parentNode, { allowComputedString: true }) === CLASS_RENDER_METHOD_NAME) {
|
|
118355
|
+
const classNode = findEnclosingClass(parentNode);
|
|
118356
|
+
if (classNode) return getClassComponentDeclaration(classNode, context);
|
|
118357
|
+
}
|
|
118358
|
+
if (parentNode && isNodeOfType(parentNode, "VariableDeclarator") && isNodeOfType(parentNode.id, "Identifier")) return context.scopes.symbolFor(parentNode.id)?.declarationNode ?? null;
|
|
118359
|
+
return currentNode;
|
|
118360
|
+
}
|
|
118361
|
+
currentNode = currentNode.parent;
|
|
118362
|
+
}
|
|
118363
|
+
return null;
|
|
118364
|
+
};
|
|
118365
|
+
const getEnclosingVariableDeclaration = (node) => {
|
|
118366
|
+
let currentNode = node.parent;
|
|
118367
|
+
while (currentNode) {
|
|
118368
|
+
if (isNodeOfType(currentNode, "VariableDeclarator") && isNodeOfType(currentNode.id, "Identifier")) return currentNode;
|
|
118369
|
+
if (isFunctionLike$1(currentNode)) return null;
|
|
118370
|
+
currentNode = currentNode.parent;
|
|
118371
|
+
}
|
|
118372
|
+
return null;
|
|
118373
|
+
};
|
|
118374
|
+
const getBindingDeclaration = (node, context) => context.scopes.symbolFor(node)?.declarationNode ?? null;
|
|
118375
|
+
const tanstackStartMissingScripts = defineRule({
|
|
118376
|
+
id: "tanstack-start-missing-scripts",
|
|
118377
|
+
title: "Root route missing Scripts",
|
|
118378
|
+
tags: ["test-noise"],
|
|
118379
|
+
requires: ["tanstack-start"],
|
|
118380
|
+
severity: "warn",
|
|
118381
|
+
recommendation: "Render `<Scripts />` near the end of `<body>` in your __root route so TanStack Start can load client-side JavaScript.",
|
|
118382
|
+
create: (context) => {
|
|
118383
|
+
if (!TANSTACK_ROOT_ROUTE_FILE_PATTERN.test(context.filename ?? "")) return {};
|
|
118384
|
+
const scriptsComponentDeclarations = /* @__PURE__ */ new Set();
|
|
118385
|
+
const tanstackRouterNamespaceDeclarations = /* @__PURE__ */ new Set();
|
|
118386
|
+
const rootRouteFactoryDeclarations = /* @__PURE__ */ new Set();
|
|
118387
|
+
const configuredRootComponentDeclarations = /* @__PURE__ */ new Set();
|
|
118388
|
+
const documentBodyElements = /* @__PURE__ */ new Set();
|
|
118389
|
+
const scriptsInsideBodyElements = /* @__PURE__ */ new Set();
|
|
118390
|
+
const scriptsValueDeclarations = /* @__PURE__ */ new Set();
|
|
118391
|
+
const scriptsWrapperComponentDeclarations = /* @__PURE__ */ new Set();
|
|
118392
|
+
const componentDependencyDeclarations = /* @__PURE__ */ new Map();
|
|
118393
|
+
const bodyChildComponentDeclarations = /* @__PURE__ */ new Map();
|
|
118394
|
+
const bodyExpressionDeclarations = /* @__PURE__ */ new Map();
|
|
118395
|
+
const collectImportBindings = (node) => {
|
|
118396
|
+
if (!isNodeOfType(node, "ImportDeclaration")) return;
|
|
118397
|
+
const isTanstackRouterImport = node.source.value === TANSTACK_ROUTER_PACKAGE;
|
|
118398
|
+
for (const specifier of node.specifiers ?? []) {
|
|
118399
|
+
const bindingDeclaration = getBindingDeclaration(specifier.local, context);
|
|
118400
|
+
if (!bindingDeclaration) continue;
|
|
118401
|
+
if (isNodeOfType(specifier, "ImportDefaultSpecifier") && specifier.local.name === SCRIPTS_COMPONENT_NAME) {
|
|
118402
|
+
scriptsComponentDeclarations.add(bindingDeclaration);
|
|
118403
|
+
continue;
|
|
118404
|
+
}
|
|
118405
|
+
if (isTanstackRouterImport && isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
|
|
118406
|
+
tanstackRouterNamespaceDeclarations.add(bindingDeclaration);
|
|
118407
|
+
continue;
|
|
118408
|
+
}
|
|
118409
|
+
if (!isNodeOfType(specifier, "ImportSpecifier") || !isNodeOfType(specifier.imported, "Identifier")) continue;
|
|
118410
|
+
if (specifier.imported.name === SCRIPTS_COMPONENT_NAME) scriptsComponentDeclarations.add(bindingDeclaration);
|
|
118411
|
+
if (isTanstackRouterImport && TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(specifier.imported.name)) rootRouteFactoryDeclarations.add(bindingDeclaration);
|
|
118412
|
+
}
|
|
118413
|
+
};
|
|
118414
|
+
const collectVariableAlias = (node) => {
|
|
118415
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !isNodeOfType(node.id, "Identifier") || !node.init) return false;
|
|
118416
|
+
const aliasDeclaration = getBindingDeclaration(node.id, context);
|
|
118417
|
+
if (!aliasDeclaration) return false;
|
|
118418
|
+
if (isNodeOfType(node.init, "Identifier")) {
|
|
118419
|
+
const initializerDeclaration = getBindingDeclaration(node.init, context);
|
|
118420
|
+
let didCollectAlias = false;
|
|
118421
|
+
if (initializerDeclaration && scriptsComponentDeclarations.has(initializerDeclaration)) {
|
|
118422
|
+
const previousSize = scriptsComponentDeclarations.size;
|
|
118423
|
+
scriptsComponentDeclarations.add(aliasDeclaration);
|
|
118424
|
+
didCollectAlias = scriptsComponentDeclarations.size !== previousSize;
|
|
118425
|
+
}
|
|
118426
|
+
if (initializerDeclaration && tanstackRouterNamespaceDeclarations.has(initializerDeclaration)) {
|
|
118427
|
+
const previousSize = tanstackRouterNamespaceDeclarations.size;
|
|
118428
|
+
tanstackRouterNamespaceDeclarations.add(aliasDeclaration);
|
|
118429
|
+
didCollectAlias = tanstackRouterNamespaceDeclarations.size !== previousSize || didCollectAlias;
|
|
118430
|
+
}
|
|
118431
|
+
if (initializerDeclaration && rootRouteFactoryDeclarations.has(initializerDeclaration)) {
|
|
118432
|
+
const previousSize = rootRouteFactoryDeclarations.size;
|
|
118433
|
+
rootRouteFactoryDeclarations.add(aliasDeclaration);
|
|
118434
|
+
didCollectAlias = rootRouteFactoryDeclarations.size !== previousSize || didCollectAlias;
|
|
118435
|
+
}
|
|
118436
|
+
return didCollectAlias;
|
|
118437
|
+
}
|
|
118438
|
+
if (!isNodeOfType(node.init, "MemberExpression")) return false;
|
|
118439
|
+
const rootIdentifier = getMemberRootIdentifier(node.init);
|
|
118440
|
+
const rootDeclaration = rootIdentifier ? getBindingDeclaration(rootIdentifier, context) : null;
|
|
118441
|
+
const propertyName = getMemberPropertyName(node.init);
|
|
118442
|
+
if (!rootDeclaration || !tanstackRouterNamespaceDeclarations.has(rootDeclaration)) return false;
|
|
118443
|
+
if (propertyName === SCRIPTS_COMPONENT_NAME) {
|
|
118444
|
+
const previousSize = scriptsComponentDeclarations.size;
|
|
118445
|
+
scriptsComponentDeclarations.add(aliasDeclaration);
|
|
118446
|
+
return scriptsComponentDeclarations.size !== previousSize;
|
|
118447
|
+
}
|
|
118448
|
+
if (propertyName && TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(propertyName)) {
|
|
118449
|
+
const previousSize = rootRouteFactoryDeclarations.size;
|
|
118450
|
+
rootRouteFactoryDeclarations.add(aliasDeclaration);
|
|
118451
|
+
return rootRouteFactoryDeclarations.size !== previousSize;
|
|
118452
|
+
}
|
|
118453
|
+
return false;
|
|
118454
|
+
};
|
|
118455
|
+
const isScriptsElementName = (name) => {
|
|
118456
|
+
if (isNodeOfType(name, "JSXIdentifier")) {
|
|
118457
|
+
const componentDeclaration = getBindingDeclaration(name, context);
|
|
118458
|
+
return componentDeclaration ? scriptsComponentDeclarations.has(componentDeclaration) : name.name === SCRIPTS_COMPONENT_NAME;
|
|
118459
|
+
}
|
|
118460
|
+
if (!isNodeOfType(name, "JSXMemberExpression")) return false;
|
|
118461
|
+
const rootIdentifier = getJsxMemberRootIdentifier(name);
|
|
118462
|
+
const rootDeclaration = rootIdentifier ? getBindingDeclaration(rootIdentifier, context) : null;
|
|
118463
|
+
return Boolean(rootDeclaration && tanstackRouterNamespaceDeclarations.has(rootDeclaration) && getJsxMemberPropertyName(name) === SCRIPTS_COMPONENT_NAME);
|
|
118464
|
+
};
|
|
118465
|
+
const isRootRouteFactoryCall = (node) => {
|
|
118466
|
+
let callee = stripParenExpression(node.callee);
|
|
118467
|
+
while (isNodeOfType(callee, "CallExpression")) callee = stripParenExpression(callee.callee);
|
|
118468
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
118469
|
+
const factoryDeclaration = getBindingDeclaration(callee, context);
|
|
118470
|
+
return factoryDeclaration ? rootRouteFactoryDeclarations.has(factoryDeclaration) : TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(callee.name);
|
|
118471
|
+
}
|
|
118472
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
118473
|
+
const rootIdentifier = getMemberRootIdentifier(callee);
|
|
118474
|
+
const rootDeclaration = rootIdentifier ? getBindingDeclaration(rootIdentifier, context) : null;
|
|
118475
|
+
const propertyName = getMemberPropertyName(callee);
|
|
118476
|
+
return Boolean(rootDeclaration && tanstackRouterNamespaceDeclarations.has(rootDeclaration) && propertyName && TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(propertyName));
|
|
118477
|
+
};
|
|
118478
|
+
const collectConfiguredRootComponent = (componentValue) => {
|
|
118479
|
+
const unwrappedComponentValue = stripParenExpression(componentValue);
|
|
118480
|
+
if (isFunctionLike$1(unwrappedComponentValue)) {
|
|
118481
|
+
configuredRootComponentDeclarations.add(unwrappedComponentValue);
|
|
118482
|
+
return;
|
|
118483
|
+
}
|
|
118484
|
+
if (isNodeOfType(unwrappedComponentValue, "ClassExpression")) {
|
|
118485
|
+
configuredRootComponentDeclarations.add(getClassComponentDeclaration(unwrappedComponentValue, context));
|
|
118486
|
+
return;
|
|
118487
|
+
}
|
|
118488
|
+
if (!isNodeOfType(unwrappedComponentValue, "Identifier")) return;
|
|
118489
|
+
const componentDeclaration = getBindingDeclaration(unwrappedComponentValue, context);
|
|
118490
|
+
if (componentDeclaration) configuredRootComponentDeclarations.add(componentDeclaration);
|
|
118491
|
+
};
|
|
118492
|
+
return {
|
|
118493
|
+
Program(node) {
|
|
118494
|
+
for (const statement of node.body ?? []) collectImportBindings(statement);
|
|
118495
|
+
const variableDeclarators = (node.body ?? []).filter((statement) => isNodeOfType(statement, "VariableDeclaration")).flatMap((statement) => statement.declarations ?? []);
|
|
118496
|
+
let didCollectAlias = false;
|
|
118497
|
+
do {
|
|
118498
|
+
didCollectAlias = false;
|
|
118499
|
+
for (const variableDeclarator of variableDeclarators) didCollectAlias = collectVariableAlias(variableDeclarator) || didCollectAlias;
|
|
118500
|
+
} while (didCollectAlias);
|
|
118501
|
+
},
|
|
118502
|
+
ImportDeclaration(node) {
|
|
118503
|
+
collectImportBindings(node);
|
|
118504
|
+
},
|
|
118505
|
+
VariableDeclarator(node) {
|
|
118506
|
+
collectVariableAlias(node);
|
|
118507
|
+
},
|
|
118508
|
+
CallExpression(node) {
|
|
118509
|
+
if (!isRootRouteFactoryCall(node)) return;
|
|
118510
|
+
const optionsArgument = node.arguments[0];
|
|
118511
|
+
if (!optionsArgument || isNodeOfType(optionsArgument, "SpreadElement")) return;
|
|
118512
|
+
const optionsObject = resolveStableOptionsObject(optionsArgument, ROOT_DOCUMENT_COMPONENT_PROPERTY_NAMES, context.scopes, node);
|
|
118513
|
+
if (!optionsObject) return;
|
|
118514
|
+
for (const property of optionsObject.properties) {
|
|
118515
|
+
if (!isNodeOfType(property, "Property") || !ROOT_DOCUMENT_COMPONENT_PROPERTY_NAMES.includes(getStaticPropertyKeyName(property, { allowComputedString: true }) ?? "")) continue;
|
|
118516
|
+
collectConfiguredRootComponent(property.value);
|
|
118517
|
+
}
|
|
118518
|
+
},
|
|
118519
|
+
JSXOpeningElement(node) {
|
|
118520
|
+
const enclosingBodyElement = getEnclosingDocumentBodyElement(node);
|
|
118521
|
+
if (isNodeOfType(node.name, "JSXIdentifier") && node.name.name === DOCUMENT_BODY_ELEMENT_NAME) {
|
|
118522
|
+
if (enclosingBodyElement) documentBodyElements.add(enclosingBodyElement);
|
|
118523
|
+
return;
|
|
118524
|
+
}
|
|
118525
|
+
if (isScriptsElementName(node.name)) {
|
|
118526
|
+
const scriptsValueDeclaration = getEnclosingVariableDeclaration(node);
|
|
118527
|
+
if (scriptsValueDeclaration) scriptsValueDeclarations.add(scriptsValueDeclaration);
|
|
118528
|
+
if (enclosingBodyElement) {
|
|
118529
|
+
scriptsInsideBodyElements.add(enclosingBodyElement);
|
|
118530
|
+
return;
|
|
118531
|
+
}
|
|
118532
|
+
const wrapperComponentDeclaration = getEnclosingComponentDeclaration(node, context);
|
|
118533
|
+
if (wrapperComponentDeclaration) scriptsWrapperComponentDeclarations.add(wrapperComponentDeclaration);
|
|
118534
|
+
return;
|
|
118535
|
+
}
|
|
118536
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
118537
|
+
const childComponentDeclaration = getBindingDeclaration(node.name, context);
|
|
118538
|
+
if (!childComponentDeclaration) return;
|
|
118539
|
+
if (enclosingBodyElement) {
|
|
118540
|
+
const bodyChildDeclarations = bodyChildComponentDeclarations.get(enclosingBodyElement) ?? /* @__PURE__ */ new Set();
|
|
118541
|
+
bodyChildDeclarations.add(childComponentDeclaration);
|
|
118542
|
+
bodyChildComponentDeclarations.set(enclosingBodyElement, bodyChildDeclarations);
|
|
118543
|
+
}
|
|
118544
|
+
const enclosingComponentDeclaration = getEnclosingComponentDeclaration(node, context);
|
|
118545
|
+
if (!enclosingComponentDeclaration) return;
|
|
118546
|
+
const dependencyDeclarations = componentDependencyDeclarations.get(enclosingComponentDeclaration) ?? /* @__PURE__ */ new Set();
|
|
118547
|
+
dependencyDeclarations.add(childComponentDeclaration);
|
|
118548
|
+
componentDependencyDeclarations.set(enclosingComponentDeclaration, dependencyDeclarations);
|
|
118549
|
+
},
|
|
118550
|
+
JSXExpressionContainer(node) {
|
|
118551
|
+
const enclosingBodyElement = getEnclosingDocumentBodyElement(node);
|
|
118552
|
+
if (!enclosingBodyElement || !isNodeOfType(node.expression, "Identifier")) return;
|
|
118553
|
+
const expressionDeclaration = getBindingDeclaration(node.expression, context);
|
|
118554
|
+
if (!expressionDeclaration) return;
|
|
118555
|
+
const expressionDeclarations = bodyExpressionDeclarations.get(enclosingBodyElement) ?? /* @__PURE__ */ new Set();
|
|
118556
|
+
expressionDeclarations.add(expressionDeclaration);
|
|
118557
|
+
bodyExpressionDeclarations.set(enclosingBodyElement, expressionDeclarations);
|
|
118558
|
+
},
|
|
118559
|
+
"Program:exit"(programNode) {
|
|
118560
|
+
const reachableRootComponentDeclarations = new Set(configuredRootComponentDeclarations);
|
|
118561
|
+
for (const componentDeclaration of reachableRootComponentDeclarations) for (const dependencyDeclaration of componentDependencyDeclarations.get(componentDeclaration) ?? []) reachableRootComponentDeclarations.add(dependencyDeclaration);
|
|
118562
|
+
for (const documentBodyElement of documentBodyElements) {
|
|
118563
|
+
const bodyOwnerDeclaration = getEnclosingComponentDeclaration(documentBodyElement, context);
|
|
118564
|
+
if (!bodyOwnerDeclaration || !reachableRootComponentDeclarations.has(bodyOwnerDeclaration)) continue;
|
|
118565
|
+
if (scriptsInsideBodyElements.has(documentBodyElement)) continue;
|
|
118566
|
+
if ([...bodyExpressionDeclarations.get(documentBodyElement) ?? []].some((declaration) => scriptsValueDeclarations.has(declaration))) continue;
|
|
118567
|
+
const reachableBodyChildDeclarations = new Set(bodyChildComponentDeclarations.get(documentBodyElement) ?? []);
|
|
118568
|
+
let hasScriptsWrapperInsideBody = false;
|
|
118569
|
+
for (const componentDeclaration of reachableBodyChildDeclarations) {
|
|
118570
|
+
if (scriptsWrapperComponentDeclarations.has(componentDeclaration)) {
|
|
118571
|
+
hasScriptsWrapperInsideBody = true;
|
|
118572
|
+
break;
|
|
118573
|
+
}
|
|
118574
|
+
for (const dependencyDeclaration of componentDependencyDeclarations.get(componentDeclaration) ?? []) reachableBodyChildDeclarations.add(dependencyDeclaration);
|
|
118575
|
+
}
|
|
118576
|
+
if (hasScriptsWrapperInsideBody) continue;
|
|
118577
|
+
context.report({
|
|
118578
|
+
node: programNode,
|
|
118579
|
+
message: "Without <Scripts /> inside <body>, the __root route does not load TanStack Start's client-side JavaScript."
|
|
118580
|
+
});
|
|
118581
|
+
return;
|
|
118582
|
+
}
|
|
118583
|
+
}
|
|
118584
|
+
};
|
|
118585
|
+
}
|
|
118586
|
+
});
|
|
118587
|
+
//#endregion
|
|
116566
118588
|
//#region src/plugin/rules/tanstack-start/tanstack-start-no-anchor-element.ts
|
|
116567
118589
|
const getAttributeStringValue = (attribute) => {
|
|
116568
118590
|
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return null;
|
|
@@ -126720,6 +128742,17 @@ const reactDoctorRules = [
|
|
|
126720
128742
|
requires: [...new Set(["react", ...noPassLiveStateToParent.requires ?? []])]
|
|
126721
128743
|
}
|
|
126722
128744
|
},
|
|
128745
|
+
{
|
|
128746
|
+
key: "react-doctor/no-path-prefix-containment",
|
|
128747
|
+
id: "no-path-prefix-containment",
|
|
128748
|
+
source: "react-doctor",
|
|
128749
|
+
originallyExternal: false,
|
|
128750
|
+
rule: {
|
|
128751
|
+
...noPathPrefixContainment,
|
|
128752
|
+
framework: "global",
|
|
128753
|
+
category: "Security"
|
|
128754
|
+
}
|
|
128755
|
+
},
|
|
126723
128756
|
{
|
|
126724
128757
|
key: "react-doctor/no-permanent-will-change",
|
|
126725
128758
|
id: "no-permanent-will-change",
|
|
@@ -130122,6 +132155,30 @@ const reactDoctorRules = [
|
|
|
130122
132155
|
tags: [...new Set(["react-native", ...rnAnimationReactionAsDerived.tags ?? []])]
|
|
130123
132156
|
}
|
|
130124
132157
|
},
|
|
132158
|
+
{
|
|
132159
|
+
key: "react-doctor/rn-bottom-sheet-no-ignored-scroll-prop",
|
|
132160
|
+
id: "rn-bottom-sheet-no-ignored-scroll-prop",
|
|
132161
|
+
source: "react-doctor",
|
|
132162
|
+
originallyExternal: false,
|
|
132163
|
+
rule: {
|
|
132164
|
+
...rnBottomSheetNoIgnoredScrollProp,
|
|
132165
|
+
framework: "react-native",
|
|
132166
|
+
category: "Bugs",
|
|
132167
|
+
tags: [...new Set(["react-native", ...rnBottomSheetNoIgnoredScrollProp.tags ?? []])]
|
|
132168
|
+
}
|
|
132169
|
+
},
|
|
132170
|
+
{
|
|
132171
|
+
key: "react-doctor/rn-bottom-sheet-no-state-in-on-animate",
|
|
132172
|
+
id: "rn-bottom-sheet-no-state-in-on-animate",
|
|
132173
|
+
source: "react-doctor",
|
|
132174
|
+
originallyExternal: false,
|
|
132175
|
+
rule: {
|
|
132176
|
+
...rnBottomSheetNoStateInOnAnimate,
|
|
132177
|
+
framework: "react-native",
|
|
132178
|
+
category: "Bugs",
|
|
132179
|
+
tags: [...new Set(["react-native", ...rnBottomSheetNoStateInOnAnimate.tags ?? []])]
|
|
132180
|
+
}
|
|
132181
|
+
},
|
|
130125
132182
|
{
|
|
130126
132183
|
key: "react-doctor/rn-bottom-sheet-prefer-native",
|
|
130127
132184
|
id: "rn-bottom-sheet-prefer-native",
|
|
@@ -130134,6 +132191,18 @@ const reactDoctorRules = [
|
|
|
130134
132191
|
tags: [...new Set(["react-native", ...rnBottomSheetPreferNative.tags ?? []])]
|
|
130135
132192
|
}
|
|
130136
132193
|
},
|
|
132194
|
+
{
|
|
132195
|
+
key: "react-doctor/rn-bottom-sheet-use-integrated-scrollable",
|
|
132196
|
+
id: "rn-bottom-sheet-use-integrated-scrollable",
|
|
132197
|
+
source: "react-doctor",
|
|
132198
|
+
originallyExternal: false,
|
|
132199
|
+
rule: {
|
|
132200
|
+
...rnBottomSheetUseIntegratedScrollable,
|
|
132201
|
+
framework: "react-native",
|
|
132202
|
+
category: "Bugs",
|
|
132203
|
+
tags: [...new Set(["react-native", ...rnBottomSheetUseIntegratedScrollable.tags ?? []])]
|
|
132204
|
+
}
|
|
132205
|
+
},
|
|
130137
132206
|
{
|
|
130138
132207
|
key: "react-doctor/rn-detox-missing-await",
|
|
130139
132208
|
id: "rn-detox-missing-await",
|
|
@@ -130398,6 +132467,18 @@ const reactDoctorRules = [
|
|
|
130398
132467
|
tags: [...new Set(["react-native", ...rnNoSingleElementStyleArray.tags ?? []])]
|
|
130399
132468
|
}
|
|
130400
132469
|
},
|
|
132470
|
+
{
|
|
132471
|
+
key: "react-doctor/rn-platform-shaking-use-direct-import",
|
|
132472
|
+
id: "rn-platform-shaking-use-direct-import",
|
|
132473
|
+
source: "react-doctor",
|
|
132474
|
+
originallyExternal: false,
|
|
132475
|
+
rule: {
|
|
132476
|
+
...rnPlatformShakingUseDirectImport,
|
|
132477
|
+
framework: "react-native",
|
|
132478
|
+
category: "Bugs",
|
|
132479
|
+
tags: [...new Set(["react-native", ...rnPlatformShakingUseDirectImport.tags ?? []])]
|
|
132480
|
+
}
|
|
132481
|
+
},
|
|
130401
132482
|
{
|
|
130402
132483
|
key: "react-doctor/rn-prefer-content-inset-adjustment",
|
|
130403
132484
|
id: "rn-prefer-content-inset-adjustment",
|
|
@@ -130470,6 +132551,42 @@ const reactDoctorRules = [
|
|
|
130470
132551
|
tags: [...new Set(["react-native", ...rnPressableSharedValueMutation.tags ?? []])]
|
|
130471
132552
|
}
|
|
130472
132553
|
},
|
|
132554
|
+
{
|
|
132555
|
+
key: "react-doctor/rn-reanimated-4-no-legacy-spring-thresholds",
|
|
132556
|
+
id: "rn-reanimated-4-no-legacy-spring-thresholds",
|
|
132557
|
+
source: "react-doctor",
|
|
132558
|
+
originallyExternal: false,
|
|
132559
|
+
rule: {
|
|
132560
|
+
...rnReanimated4NoLegacySpringThresholds,
|
|
132561
|
+
framework: "react-native",
|
|
132562
|
+
category: "Bugs",
|
|
132563
|
+
tags: [...new Set(["react-native", ...rnReanimated4NoLegacySpringThresholds.tags ?? []])]
|
|
132564
|
+
}
|
|
132565
|
+
},
|
|
132566
|
+
{
|
|
132567
|
+
key: "react-doctor/rn-reanimated-4-no-removed-api",
|
|
132568
|
+
id: "rn-reanimated-4-no-removed-api",
|
|
132569
|
+
source: "react-doctor",
|
|
132570
|
+
originallyExternal: false,
|
|
132571
|
+
rule: {
|
|
132572
|
+
...rnReanimated4NoRemovedApi,
|
|
132573
|
+
framework: "react-native",
|
|
132574
|
+
category: "Bugs",
|
|
132575
|
+
tags: [...new Set(["react-native", ...rnReanimated4NoRemovedApi.tags ?? []])]
|
|
132576
|
+
}
|
|
132577
|
+
},
|
|
132578
|
+
{
|
|
132579
|
+
key: "react-doctor/rn-reanimated-4-use-worklets-scheduler",
|
|
132580
|
+
id: "rn-reanimated-4-use-worklets-scheduler",
|
|
132581
|
+
source: "react-doctor",
|
|
132582
|
+
originallyExternal: false,
|
|
132583
|
+
rule: {
|
|
132584
|
+
...rnReanimated4UseWorkletsScheduler,
|
|
132585
|
+
framework: "react-native",
|
|
132586
|
+
category: "Bugs",
|
|
132587
|
+
tags: [...new Set(["react-native", ...rnReanimated4UseWorkletsScheduler.tags ?? []])]
|
|
132588
|
+
}
|
|
132589
|
+
},
|
|
130473
132590
|
{
|
|
130474
132591
|
key: "react-doctor/rn-scrollview-dynamic-padding",
|
|
130475
132592
|
id: "rn-scrollview-dynamic-padding",
|
|
@@ -130838,6 +132955,17 @@ const reactDoctorRules = [
|
|
|
130838
132955
|
category: "Bugs"
|
|
130839
132956
|
}
|
|
130840
132957
|
},
|
|
132958
|
+
{
|
|
132959
|
+
key: "react-doctor/tanstack-start-missing-scripts",
|
|
132960
|
+
id: "tanstack-start-missing-scripts",
|
|
132961
|
+
source: "react-doctor",
|
|
132962
|
+
originallyExternal: false,
|
|
132963
|
+
rule: {
|
|
132964
|
+
...tanstackStartMissingScripts,
|
|
132965
|
+
framework: "tanstack-start",
|
|
132966
|
+
category: "Bugs"
|
|
132967
|
+
}
|
|
132968
|
+
},
|
|
130841
132969
|
{
|
|
130842
132970
|
key: "react-doctor/tanstack-start-no-anchor-element",
|
|
130843
132971
|
id: "tanstack-start-no-anchor-element",
|