oxlint-plugin-react-doctor 0.8.1-dev.82c4a12 → 0.8.1-dev.88114a2
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 +0 -46
- package/dist/index.js +2015 -597
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -762,7 +762,7 @@ const EXTERNAL_SYNC_MEMBER_METHOD_NAMES = new Set([
|
|
|
762
762
|
"put",
|
|
763
763
|
"patch"
|
|
764
764
|
]);
|
|
765
|
-
|
|
765
|
+
new Set([
|
|
766
766
|
...FETCH_MEMBER_OBJECTS,
|
|
767
767
|
"api",
|
|
768
768
|
"client",
|
|
@@ -2622,7 +2622,7 @@ const PREFER_ALT = "Screen readers skip a decorative image more reliably with `a
|
|
|
2622
2622
|
const MESSAGE_OBJECT = "Screen reader users cannot use this `<object>` because assistive tech cannot describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
|
|
2623
2623
|
const MESSAGE_AREA = "Blind users can't use this `<area>` of the image map because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
2624
2624
|
const MESSAGE_INPUT_IMAGE = "Blind users can't use this image button because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
2625
|
-
const resolveSettings$
|
|
2625
|
+
const resolveSettings$52 = (settings) => {
|
|
2626
2626
|
const reactDoctor = settings?.["react-doctor"];
|
|
2627
2627
|
return typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.altText ?? {} : {};
|
|
2628
2628
|
};
|
|
@@ -2745,7 +2745,7 @@ const altText = defineRule({
|
|
|
2745
2745
|
category: "Accessibility",
|
|
2746
2746
|
create: (context) => {
|
|
2747
2747
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
2748
|
-
const settings = resolveSettings$
|
|
2748
|
+
const settings = resolveSettings$52(context.settings);
|
|
2749
2749
|
const checkImg = !settings.elements || settings.elements.includes("img");
|
|
2750
2750
|
const checkObject = !settings.elements || settings.elements.includes("object");
|
|
2751
2751
|
const checkArea = !settings.elements || settings.elements.includes("area");
|
|
@@ -2807,7 +2807,7 @@ const DEFAULT_AMBIGUOUS = [
|
|
|
2807
2807
|
"a link",
|
|
2808
2808
|
"learn more"
|
|
2809
2809
|
];
|
|
2810
|
-
const resolveSettings$
|
|
2810
|
+
const resolveSettings$51 = (settings) => {
|
|
2811
2811
|
const reactDoctor = settings?.["react-doctor"];
|
|
2812
2812
|
return { words: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorAmbiguousText ?? {} : {}).words ?? DEFAULT_AMBIGUOUS };
|
|
2813
2813
|
};
|
|
@@ -2857,7 +2857,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2857
2857
|
category: "Accessibility",
|
|
2858
2858
|
create: (context) => {
|
|
2859
2859
|
if (isTestlikeFilename(context.filename)) return {};
|
|
2860
|
-
const settings = resolveSettings$
|
|
2860
|
+
const settings = resolveSettings$51(context.settings);
|
|
2861
2861
|
const ambiguousSet = new Set(settings.words.map((word) => word.toLowerCase()));
|
|
2862
2862
|
return { JSXElement(node) {
|
|
2863
2863
|
if (getElementType(node.openingElement, context.settings) !== "a") return;
|
|
@@ -3225,7 +3225,7 @@ const anchorHasContent = defineRule({
|
|
|
3225
3225
|
const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
|
|
3226
3226
|
const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
|
|
3227
3227
|
const MESSAGE_CANT_BE_ANCHOR = "Keyboard users can't trigger this link because it's a click handler with no real `href`, so use `<button>` instead.";
|
|
3228
|
-
const resolveSettings$
|
|
3228
|
+
const resolveSettings$50 = (settings) => {
|
|
3229
3229
|
const reactDoctor = settings?.["react-doctor"];
|
|
3230
3230
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorIsValid ?? {} : {};
|
|
3231
3231
|
const jsxA11y = settings?.["jsx-a11y"];
|
|
@@ -3268,7 +3268,7 @@ const anchorIsValid = defineRule({
|
|
|
3268
3268
|
recommendation: "Give links a real destination. Use `<button>` for in-page actions.",
|
|
3269
3269
|
category: "Accessibility",
|
|
3270
3270
|
create: (context) => {
|
|
3271
|
-
const settings = resolveSettings$
|
|
3271
|
+
const settings = resolveSettings$50(context.settings);
|
|
3272
3272
|
const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
|
|
3273
3273
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3274
3274
|
return { JSXOpeningElement(node) {
|
|
@@ -4181,7 +4181,7 @@ const ariaProptypes = defineRule({
|
|
|
4181
4181
|
//#endregion
|
|
4182
4182
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
4183
4183
|
const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
|
|
4184
|
-
const resolveSettings$
|
|
4184
|
+
const resolveSettings$49 = (settings) => {
|
|
4185
4185
|
const reactDoctor = settings?.["react-doctor"];
|
|
4186
4186
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
|
|
4187
4187
|
return {
|
|
@@ -4197,7 +4197,7 @@ const ariaRole = defineRule({
|
|
|
4197
4197
|
recommendation: "Use a real, non-abstract ARIA role so assistive tech can expose the element correctly.",
|
|
4198
4198
|
category: "Accessibility",
|
|
4199
4199
|
create: (context) => {
|
|
4200
|
-
const settings = resolveSettings$
|
|
4200
|
+
const settings = resolveSettings$49(context.settings);
|
|
4201
4201
|
return { JSXOpeningElement(node) {
|
|
4202
4202
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
4203
4203
|
if (!roleAttribute) return;
|
|
@@ -7115,7 +7115,7 @@ const FORM_CONTROL_TAGS = new Set([
|
|
|
7115
7115
|
"select",
|
|
7116
7116
|
"form"
|
|
7117
7117
|
]);
|
|
7118
|
-
const resolveSettings$
|
|
7118
|
+
const resolveSettings$48 = (settings) => {
|
|
7119
7119
|
const reactDoctor = settings?.["react-doctor"];
|
|
7120
7120
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {};
|
|
7121
7121
|
return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
|
|
@@ -7128,7 +7128,7 @@ const autocompleteValid = defineRule({
|
|
|
7128
7128
|
recommendation: "Use a valid autofill token in `autoComplete` so browsers can fill the right field reliably.",
|
|
7129
7129
|
category: "Accessibility",
|
|
7130
7130
|
create: skipNonProductionFiles((context) => {
|
|
7131
|
-
const settings = resolveSettings$
|
|
7131
|
+
const settings = resolveSettings$48(context.settings);
|
|
7132
7132
|
return { JSXOpeningElement: (node) => {
|
|
7133
7133
|
const tag = getElementType(node, context.settings);
|
|
7134
7134
|
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.has(tag)) return;
|
|
@@ -7247,7 +7247,7 @@ const isCreateElementCall = (node) => {
|
|
|
7247
7247
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
7248
7248
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
7249
7249
|
const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
|
|
7250
|
-
const resolveSettings$
|
|
7250
|
+
const resolveSettings$47 = (settings) => {
|
|
7251
7251
|
const reactDoctor = settings?.["react-doctor"];
|
|
7252
7252
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
|
|
7253
7253
|
return {
|
|
@@ -7485,7 +7485,7 @@ const buttonHasType = defineRule({
|
|
|
7485
7485
|
severity: "warn",
|
|
7486
7486
|
recommendation: "Set an explicit button `type` so plain buttons do not submit forms by accident: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
|
|
7487
7487
|
create: (context) => {
|
|
7488
|
-
const settings = resolveSettings$
|
|
7488
|
+
const settings = resolveSettings$47(context.settings);
|
|
7489
7489
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
7490
7490
|
return {
|
|
7491
7491
|
JSXOpeningElement(node) {
|
|
@@ -7566,7 +7566,7 @@ const buttonHasType = defineRule({
|
|
|
7566
7566
|
//#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
|
|
7567
7567
|
const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
|
|
7568
7568
|
const EXCLUSIVE_MESSAGE = "This input mixes `checked` with `defaultChecked`, so React can't tell whether it is controlled or uncontrolled.";
|
|
7569
|
-
const resolveSettings$
|
|
7569
|
+
const resolveSettings$46 = (settings) => {
|
|
7570
7570
|
const reactDoctor = settings?.["react-doctor"];
|
|
7571
7571
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
|
|
7572
7572
|
return {
|
|
@@ -7667,7 +7667,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
7667
7667
|
recommendation: "Add `onChange`, `readOnly`, or `defaultChecked` so React knows whether the checkbox is editable, display-only, or uncontrolled.",
|
|
7668
7668
|
category: "Correctness",
|
|
7669
7669
|
create: (context) => {
|
|
7670
|
-
const settings = resolveSettings$
|
|
7670
|
+
const settings = resolveSettings$46(context.settings);
|
|
7671
7671
|
const reportFromPresence = (presence) => {
|
|
7672
7672
|
if (presence.checkedNode && presence.defaultCheckedNode && !presence.bothCheckedAndDefaultForwarded && !settings.ignoreExclusiveCheckedAttribute) context.report({
|
|
7673
7673
|
node: presence.checkedNode,
|
|
@@ -10380,7 +10380,7 @@ const WRAPPER_LABEL_PROP = "label";
|
|
|
10380
10380
|
const SELECT_ELEMENT = "select";
|
|
10381
10381
|
const DEFAULT_DEPTH = 5;
|
|
10382
10382
|
const MAX_DEPTH = 25;
|
|
10383
|
-
const resolveSettings$
|
|
10383
|
+
const resolveSettings$45 = (settings) => {
|
|
10384
10384
|
const reactDoctor = settings?.["react-doctor"];
|
|
10385
10385
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
|
|
10386
10386
|
return {
|
|
@@ -10713,7 +10713,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10713
10713
|
recommendation: "Give every interactive control a label screen readers can read.",
|
|
10714
10714
|
category: "Accessibility",
|
|
10715
10715
|
create: (context) => {
|
|
10716
|
-
const settings = resolveSettings$
|
|
10716
|
+
const settings = resolveSettings$45(context.settings);
|
|
10717
10717
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10718
10718
|
const iconComponentNames = /* @__PURE__ */ new Set();
|
|
10719
10719
|
const iconNamespaceNames = /* @__PURE__ */ new Set();
|
|
@@ -12665,7 +12665,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
|
|
|
12665
12665
|
"lazy",
|
|
12666
12666
|
"withTracking"
|
|
12667
12667
|
];
|
|
12668
|
-
const resolveSettings$
|
|
12668
|
+
const resolveSettings$44 = (settings) => {
|
|
12669
12669
|
const reactDoctor = settings?.["react-doctor"];
|
|
12670
12670
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
|
|
12671
12671
|
const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
|
|
@@ -12847,7 +12847,7 @@ const displayName = defineRule({
|
|
|
12847
12847
|
recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
|
|
12848
12848
|
category: "Architecture",
|
|
12849
12849
|
create: (context) => {
|
|
12850
|
-
const settings = resolveSettings$
|
|
12850
|
+
const settings = resolveSettings$44(context.settings);
|
|
12851
12851
|
const ignoreNamed = !settings.ignoreTranspilerName;
|
|
12852
12852
|
const reportAt = (node) => {
|
|
12853
12853
|
context.report({
|
|
@@ -13827,6 +13827,116 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
13827
13827
|
return null;
|
|
13828
13828
|
};
|
|
13829
13829
|
//#endregion
|
|
13830
|
+
//#region src/plugin/utils/get-final-sequence-expression-value.ts
|
|
13831
|
+
const getFinalSequenceExpressionValue = (expression) => {
|
|
13832
|
+
let finalExpression = stripParenExpression(expression);
|
|
13833
|
+
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
13834
|
+
const sequenceResult = finalExpression.expressions.at(-1);
|
|
13835
|
+
if (!sequenceResult) break;
|
|
13836
|
+
finalExpression = stripParenExpression(sequenceResult);
|
|
13837
|
+
}
|
|
13838
|
+
return finalExpression;
|
|
13839
|
+
};
|
|
13840
|
+
//#endregion
|
|
13841
|
+
//#region src/plugin/utils/collect-expression-path-coverage-nodes.ts
|
|
13842
|
+
const collectExpressionPathCoverageNodes = (owner, matchingNodes, context, expressionBoundary = owner) => {
|
|
13843
|
+
const coverageNodes = /* @__PURE__ */ new Set();
|
|
13844
|
+
const pendingNodes = matchingNodes.filter((matchingNode) => {
|
|
13845
|
+
if (context.cfg.enclosingFunction(matchingNode) !== owner) return false;
|
|
13846
|
+
let currentNode = matchingNode;
|
|
13847
|
+
while (currentNode && currentNode !== expressionBoundary) currentNode = currentNode.parent ?? null;
|
|
13848
|
+
return currentNode === expressionBoundary;
|
|
13849
|
+
});
|
|
13850
|
+
const visitedNodes = new Set(pendingNodes);
|
|
13851
|
+
const coveredBranchesByConditional = /* @__PURE__ */ new Map();
|
|
13852
|
+
while (pendingNodes.length > 0) {
|
|
13853
|
+
const coverageCandidate = pendingNodes.pop();
|
|
13854
|
+
if (!coverageCandidate) break;
|
|
13855
|
+
if (coverageCandidate === expressionBoundary) {
|
|
13856
|
+
coverageNodes.add(coverageCandidate);
|
|
13857
|
+
continue;
|
|
13858
|
+
}
|
|
13859
|
+
let currentChild = coverageCandidate;
|
|
13860
|
+
let currentParent = currentChild.parent ?? null;
|
|
13861
|
+
let conditionalExpression = null;
|
|
13862
|
+
let conditionalBranch = null;
|
|
13863
|
+
let isBlockedByNonExhaustiveExpression = false;
|
|
13864
|
+
while (currentParent && currentParent !== expressionBoundary) {
|
|
13865
|
+
if (isNodeOfType(currentParent, "ConditionalExpression")) {
|
|
13866
|
+
const isConsequent = currentParent.consequent === currentChild;
|
|
13867
|
+
const isAlternate = currentParent.alternate === currentChild;
|
|
13868
|
+
if (isConsequent || isAlternate) {
|
|
13869
|
+
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
13870
|
+
if (staticTestValue !== null) {
|
|
13871
|
+
if (staticTestValue !== isConsequent) {
|
|
13872
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13873
|
+
break;
|
|
13874
|
+
}
|
|
13875
|
+
currentChild = currentParent;
|
|
13876
|
+
currentParent = currentChild.parent ?? null;
|
|
13877
|
+
continue;
|
|
13878
|
+
}
|
|
13879
|
+
conditionalExpression = currentParent;
|
|
13880
|
+
conditionalBranch = isConsequent ? "consequent" : "alternate";
|
|
13881
|
+
break;
|
|
13882
|
+
}
|
|
13883
|
+
}
|
|
13884
|
+
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild) {
|
|
13885
|
+
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
13886
|
+
if (!(currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false)) {
|
|
13887
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13888
|
+
break;
|
|
13889
|
+
}
|
|
13890
|
+
}
|
|
13891
|
+
if (isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) {
|
|
13892
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13893
|
+
break;
|
|
13894
|
+
}
|
|
13895
|
+
currentChild = currentParent;
|
|
13896
|
+
currentParent = currentChild.parent ?? null;
|
|
13897
|
+
}
|
|
13898
|
+
if (isBlockedByNonExhaustiveExpression) continue;
|
|
13899
|
+
if (!conditionalExpression || !conditionalBranch) {
|
|
13900
|
+
coverageNodes.add(coverageCandidate);
|
|
13901
|
+
continue;
|
|
13902
|
+
}
|
|
13903
|
+
const coveredBranches = coveredBranchesByConditional.get(conditionalExpression) ?? /* @__PURE__ */ new Set();
|
|
13904
|
+
coveredBranches.add(conditionalBranch);
|
|
13905
|
+
coveredBranchesByConditional.set(conditionalExpression, coveredBranches);
|
|
13906
|
+
if (coveredBranches.has("alternate") && coveredBranches.has("consequent") && !visitedNodes.has(conditionalExpression)) {
|
|
13907
|
+
visitedNodes.add(conditionalExpression);
|
|
13908
|
+
pendingNodes.push(conditionalExpression);
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
return coverageNodes;
|
|
13912
|
+
};
|
|
13913
|
+
//#endregion
|
|
13914
|
+
//#region src/plugin/utils/do-nodes-cover-every-path-from-function-entry.ts
|
|
13915
|
+
const doNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context, options = {}) => {
|
|
13916
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
13917
|
+
if (!functionCfg) return false;
|
|
13918
|
+
const matchingBlocks = new Set([...collectExpressionPathCoverageNodes(owner, matchingNodes, context)].flatMap((matchingNode) => {
|
|
13919
|
+
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
13920
|
+
return matchingBlock ? [matchingBlock] : [];
|
|
13921
|
+
}));
|
|
13922
|
+
if (matchingBlocks.size === 0) return false;
|
|
13923
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
13924
|
+
const pendingBlocks = [functionCfg.entry];
|
|
13925
|
+
while (pendingBlocks.length > 0) {
|
|
13926
|
+
const currentBlock = pendingBlocks.pop();
|
|
13927
|
+
if (!currentBlock) break;
|
|
13928
|
+
if (matchingBlocks.has(currentBlock)) continue;
|
|
13929
|
+
for (const edge of currentBlock.successors) {
|
|
13930
|
+
if (options.ignoreThrowEdges && edge.kind === "throw") continue;
|
|
13931
|
+
if (edge.to === functionCfg.exit) return false;
|
|
13932
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
13933
|
+
visitedBlocks.add(edge.to);
|
|
13934
|
+
pendingBlocks.push(edge.to);
|
|
13935
|
+
}
|
|
13936
|
+
}
|
|
13937
|
+
return true;
|
|
13938
|
+
};
|
|
13939
|
+
//#endregion
|
|
13830
13940
|
//#region src/plugin/utils/get-function-binding-name.ts
|
|
13831
13941
|
const getFunctionBindingIdentifier$1 = (functionNode) => {
|
|
13832
13942
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
@@ -13864,14 +13974,19 @@ const isAstDescendant = (inner, outer) => {
|
|
|
13864
13974
|
};
|
|
13865
13975
|
//#endregion
|
|
13866
13976
|
//#region src/plugin/utils/react-ref-origin.ts
|
|
13867
|
-
const
|
|
13977
|
+
const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
|
|
13978
|
+
const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
|
|
13868
13979
|
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
13869
13980
|
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
13870
13981
|
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
13871
13982
|
if (!symbol?.initializer) return null;
|
|
13872
13983
|
const initializer = stripParenExpression(symbol.initializer);
|
|
13873
13984
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
13874
|
-
return isReactApiCall(initializer, "useRef", scopes, {
|
|
13985
|
+
return isReactApiCall(initializer, options.includeCreateRef ? REACT_REF_API_NAMES : "useRef", scopes, {
|
|
13986
|
+
allowGlobalReactNamespace: true,
|
|
13987
|
+
allowUnboundBareCalls: options.allowUnboundBareCalls,
|
|
13988
|
+
resolveNamedAliases: options.resolveNamedAliases
|
|
13989
|
+
}) ? symbol : null;
|
|
13875
13990
|
};
|
|
13876
13991
|
const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13877
13992
|
const expression = stripParenExpression(node);
|
|
@@ -13970,6 +14085,20 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
13970
14085
|
return false;
|
|
13971
14086
|
};
|
|
13972
14087
|
//#endregion
|
|
14088
|
+
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
14089
|
+
const isWithinAssignmentTarget = (identifier) => {
|
|
14090
|
+
let currentNode = identifier;
|
|
14091
|
+
let parentNode = currentNode.parent;
|
|
14092
|
+
while (parentNode) {
|
|
14093
|
+
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14094
|
+
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14095
|
+
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14096
|
+
currentNode = parentNode;
|
|
14097
|
+
parentNode = currentNode.parent;
|
|
14098
|
+
}
|
|
14099
|
+
return false;
|
|
14100
|
+
};
|
|
14101
|
+
//#endregion
|
|
13973
14102
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
13974
14103
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
13975
14104
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -14018,7 +14147,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
|
|
|
14018
14147
|
return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
|
|
14019
14148
|
}
|
|
14020
14149
|
if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
|
|
14021
|
-
if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
|
|
14150
|
+
if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number" || typeof unwrappedExpression.value === "boolean")) return `literal:${String(unwrappedExpression.value)}`;
|
|
14022
14151
|
if (isFunctionLike$1(unwrappedExpression)) {
|
|
14023
14152
|
const rangeStart = getRangeStart(unwrappedExpression);
|
|
14024
14153
|
return rangeStart === null ? null : `function:${rangeStart}`;
|
|
@@ -14047,9 +14176,11 @@ const resolveForEachProjection = (expression, context) => {
|
|
|
14047
14176
|
const collectionKey = resolveExpressionKey(forEachCallee.object, context);
|
|
14048
14177
|
if (!collectionKey) return null;
|
|
14049
14178
|
const firstParameter = callbackNode.params[0];
|
|
14050
|
-
const
|
|
14051
|
-
const propertyName = isNodeOfType(firstParameter, "ObjectPattern")
|
|
14052
|
-
const
|
|
14179
|
+
const assignmentPattern = isNodeOfType(symbol.bindingIdentifier.parent, "AssignmentPattern") && symbol.bindingIdentifier.parent.left === symbol.bindingIdentifier ? symbol.bindingIdentifier.parent : null;
|
|
14180
|
+
const propertyName = isNodeOfType(firstParameter, "ObjectPattern") ? getDestructuredBindingPropertyName(symbol.bindingIdentifier) : null;
|
|
14181
|
+
const defaultValueKey = assignmentPattern ? resolveExpressionKey(assignmentPattern.right, context) : null;
|
|
14182
|
+
if (assignmentPattern && !defaultValueKey) return null;
|
|
14183
|
+
const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
|
|
14053
14184
|
if (!parameterProjection) return null;
|
|
14054
14185
|
return {
|
|
14055
14186
|
collectionKey,
|
|
@@ -14061,13 +14192,23 @@ const resolveForEachProjectionKey = (expression, context) => {
|
|
|
14061
14192
|
return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
|
|
14062
14193
|
};
|
|
14063
14194
|
const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
|
|
14195
|
+
const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
14196
|
+
if (!expression) return null;
|
|
14197
|
+
const directIdentityKey = resolveResourceIdentityKey(expression, context);
|
|
14198
|
+
if (directIdentityKey) return directIdentityKey;
|
|
14199
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
14200
|
+
if (!isNodeOfType(unwrappedExpression, "BinaryExpression") && !isNodeOfType(unwrappedExpression, "LogicalExpression")) return null;
|
|
14201
|
+
const leftIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.left, context);
|
|
14202
|
+
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
14203
|
+
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
14204
|
+
};
|
|
14064
14205
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
14065
14206
|
const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
|
|
14066
14207
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
14067
14208
|
if (!optionsNode) return null;
|
|
14068
14209
|
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
14069
14210
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
14070
|
-
const optionsKey = allowOpaqueOptionsIdentity ?
|
|
14211
|
+
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
14071
14212
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
14072
14213
|
}
|
|
14073
14214
|
let captureKey = "capture:false";
|
|
@@ -14082,7 +14223,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
|
|
|
14082
14223
|
continue;
|
|
14083
14224
|
}
|
|
14084
14225
|
if (propertyName === "capture") {
|
|
14085
|
-
const propertyValueKey =
|
|
14226
|
+
const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
|
|
14086
14227
|
captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
|
|
14087
14228
|
}
|
|
14088
14229
|
}
|
|
@@ -14264,30 +14405,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
14264
14405
|
}
|
|
14265
14406
|
return matchingBlocks.size > 0;
|
|
14266
14407
|
};
|
|
14267
|
-
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
14268
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
14269
|
-
if (!functionCfg) return false;
|
|
14270
|
-
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
14271
|
-
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
14272
|
-
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
14273
|
-
return matchingBlock ? [matchingBlock] : [];
|
|
14274
|
-
}));
|
|
14275
|
-
if (matchingBlocks.size === 0) return false;
|
|
14276
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
14277
|
-
const pendingBlocks = [functionCfg.entry];
|
|
14278
|
-
while (pendingBlocks.length > 0) {
|
|
14279
|
-
const currentBlock = pendingBlocks.pop();
|
|
14280
|
-
if (!currentBlock) break;
|
|
14281
|
-
if (matchingBlocks.has(currentBlock)) continue;
|
|
14282
|
-
for (const edge of currentBlock.successors) {
|
|
14283
|
-
if (edge.to === functionCfg.exit) return false;
|
|
14284
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
14285
|
-
visitedBlocks.add(edge.to);
|
|
14286
|
-
pendingBlocks.push(edge.to);
|
|
14287
|
-
}
|
|
14288
|
-
}
|
|
14289
|
-
return true;
|
|
14290
|
-
};
|
|
14291
14408
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
14292
14409
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
14293
14410
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -14530,18 +14647,6 @@ const findPushedResourceCollectionKey = (usage, context) => {
|
|
|
14530
14647
|
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14531
14648
|
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14532
14649
|
};
|
|
14533
|
-
const isWithinAssignmentTarget = (identifier) => {
|
|
14534
|
-
let currentNode = identifier;
|
|
14535
|
-
let parentNode = currentNode.parent;
|
|
14536
|
-
while (parentNode) {
|
|
14537
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14538
|
-
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14539
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14540
|
-
currentNode = parentNode;
|
|
14541
|
-
parentNode = currentNode.parent;
|
|
14542
|
-
}
|
|
14543
|
-
return false;
|
|
14544
|
-
};
|
|
14545
14650
|
const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14546
14651
|
if (!expression) return null;
|
|
14547
14652
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14665,7 +14770,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14665
14770
|
const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
|
|
14666
14771
|
if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
|
|
14667
14772
|
});
|
|
14668
|
-
return didCleanupFunctionMatch ||
|
|
14773
|
+
return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
|
|
14669
14774
|
};
|
|
14670
14775
|
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
14671
14776
|
const callExpression = stripParenExpression(expression);
|
|
@@ -14694,7 +14799,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14694
14799
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
14695
14800
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
14696
14801
|
});
|
|
14697
|
-
return
|
|
14802
|
+
return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14698
14803
|
};
|
|
14699
14804
|
const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
|
|
14700
14805
|
if (resolveExpressionKey(test, context) === expressionKey) return true;
|
|
@@ -14718,7 +14823,24 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
|
|
|
14718
14823
|
return null;
|
|
14719
14824
|
};
|
|
14720
14825
|
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
|
|
14721
|
-
const
|
|
14826
|
+
const hasExecutionBoundaryNotSharedWithUsage = (node, usageNode, owner) => {
|
|
14827
|
+
const usageAncestors = /* @__PURE__ */ new Set();
|
|
14828
|
+
let usageAncestor = usageNode;
|
|
14829
|
+
while (usageAncestor && usageAncestor !== owner) {
|
|
14830
|
+
usageAncestors.add(usageAncestor);
|
|
14831
|
+
usageAncestor = usageAncestor.parent ?? null;
|
|
14832
|
+
}
|
|
14833
|
+
let descendant = node;
|
|
14834
|
+
let ancestor = descendant.parent ?? null;
|
|
14835
|
+
while (ancestor && ancestor !== owner) {
|
|
14836
|
+
const guardedSubtree = isNodeOfType(ancestor, "IfStatement") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "ConditionalExpression") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === descendant || isNodeOfType(ancestor, "AssignmentPattern") && ancestor.right === descendant || (isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "ForInStatement") || isNodeOfType(ancestor, "ForOfStatement") || isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === descendant ? descendant : isNodeOfType(ancestor, "SwitchCase") ? ancestor : null;
|
|
14837
|
+
if (guardedSubtree && !usageAncestors.has(guardedSubtree)) return true;
|
|
14838
|
+
descendant = ancestor;
|
|
14839
|
+
ancestor = descendant.parent ?? null;
|
|
14840
|
+
}
|
|
14841
|
+
return false;
|
|
14842
|
+
};
|
|
14843
|
+
const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPathsWithoutUsage = false) => {
|
|
14722
14844
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
14723
14845
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
14724
14846
|
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
@@ -14729,7 +14851,8 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
14729
14851
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
14730
14852
|
const releaseStart = getRangeStart(child);
|
|
14731
14853
|
const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
|
|
14732
|
-
|
|
14854
|
+
const releaseBlock = functionCfg.blockOf(child);
|
|
14855
|
+
if (releaseStart === null || releaseStart >= usageStart || releaseBlock !== usageBlock && !handleGuard || !handleGuard && hasExecutionBoundaryNotSharedWithUsage(child, usage.node, callback)) return;
|
|
14733
14856
|
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
14734
14857
|
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14735
14858
|
return;
|
|
@@ -14737,7 +14860,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
14737
14860
|
const helperFunction = resolveStableValue(child.callee, context);
|
|
14738
14861
|
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14739
14862
|
});
|
|
14740
|
-
return
|
|
14863
|
+
return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
14741
14864
|
};
|
|
14742
14865
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
14743
14866
|
const componentFunction = findEnclosingFunction$1(callback);
|
|
@@ -14757,7 +14880,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
14757
14880
|
});
|
|
14758
14881
|
return didFindUnmountCleanup;
|
|
14759
14882
|
};
|
|
14760
|
-
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null &&
|
|
14883
|
+
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasStableUnmountCleanupForUsage(callback, usage, context) && hasRerunReleaseBeforeUsage(callback, usage, context, usage.registrationVerbName === "setTimeout");
|
|
14761
14884
|
const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
|
|
14762
14885
|
const unwrappedExpression = stripParenExpression(expression);
|
|
14763
14886
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
|
|
@@ -14988,7 +15111,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
|
|
|
14988
15111
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14989
15112
|
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14990
15113
|
const usageAssignment = usageExpression.parent;
|
|
14991
|
-
if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !
|
|
15114
|
+
if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
14992
15115
|
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14993
15116
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14994
15117
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
@@ -15036,7 +15159,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
15036
15159
|
});
|
|
15037
15160
|
}
|
|
15038
15161
|
});
|
|
15039
|
-
if (!
|
|
15162
|
+
if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
15040
15163
|
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
15041
15164
|
}
|
|
15042
15165
|
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
@@ -15249,7 +15372,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
15249
15372
|
});
|
|
15250
15373
|
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15251
15374
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15252
|
-
return
|
|
15375
|
+
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15253
15376
|
};
|
|
15254
15377
|
const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
|
|
15255
15378
|
let currentNode = findTransparentExpressionRoot(releaseNode);
|
|
@@ -15262,7 +15385,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
|
|
|
15262
15385
|
const isDirectConciseBody = ownerFunction.body === currentNode;
|
|
15263
15386
|
const statementNode = currentNode.parent;
|
|
15264
15387
|
const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
|
|
15265
|
-
if (!isDirectConciseBody && !isDirectBlockStatement || !
|
|
15388
|
+
if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
|
|
15266
15389
|
const forEachCall = findEnclosingForEachCall(ownerFunction);
|
|
15267
15390
|
if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
|
|
15268
15391
|
const forEachCallee = stripParenExpression(forEachCall.callee);
|
|
@@ -15350,7 +15473,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
|
|
|
15350
15473
|
const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
|
|
15351
15474
|
const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
|
|
15352
15475
|
const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
|
|
15353
|
-
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) &&
|
|
15476
|
+
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
|
|
15354
15477
|
const projections = [
|
|
15355
15478
|
registrationCallee.object,
|
|
15356
15479
|
registrationCall.arguments?.[0],
|
|
@@ -15549,7 +15672,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
|
15549
15672
|
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15550
15673
|
matchingReturns.push(child);
|
|
15551
15674
|
});
|
|
15552
|
-
return
|
|
15675
|
+
return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15553
15676
|
};
|
|
15554
15677
|
let didFindInvocation = false;
|
|
15555
15678
|
walkAst(componentFunction.body, (child) => {
|
|
@@ -15601,7 +15724,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
|
15601
15724
|
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15602
15725
|
};
|
|
15603
15726
|
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15604
|
-
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !
|
|
15727
|
+
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15605
15728
|
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15606
15729
|
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15607
15730
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
|
|
@@ -15829,7 +15952,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
15829
15952
|
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15830
15953
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
|
|
15831
15954
|
});
|
|
15832
|
-
return
|
|
15955
|
+
return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15833
15956
|
};
|
|
15834
15957
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
15835
15958
|
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
@@ -15872,15 +15995,6 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
|
|
|
15872
15995
|
const returnStatement = expressionRoot.parent;
|
|
15873
15996
|
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
|
|
15874
15997
|
};
|
|
15875
|
-
const getFinalSequenceExpressionValue = (expression) => {
|
|
15876
|
-
let finalExpression = stripParenExpression(expression);
|
|
15877
|
-
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
15878
|
-
const sequenceResult = finalExpression.expressions.at(-1);
|
|
15879
|
-
if (!sequenceResult) break;
|
|
15880
|
-
finalExpression = stripParenExpression(sequenceResult);
|
|
15881
|
-
}
|
|
15882
|
-
return finalExpression;
|
|
15883
|
-
};
|
|
15884
15998
|
const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
|
|
15885
15999
|
if (!allowReturnedResourceEscape) return false;
|
|
15886
16000
|
let currentNode = resourceNode;
|
|
@@ -16129,7 +16243,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
|
|
|
16129
16243
|
if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
16130
16244
|
if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
|
|
16131
16245
|
});
|
|
16132
|
-
return
|
|
16246
|
+
return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
|
|
16133
16247
|
};
|
|
16134
16248
|
const matchingReturns = [];
|
|
16135
16249
|
walkInsideStatementBlocks(retainedFunction.body, (child) => {
|
|
@@ -18932,7 +19046,7 @@ const compileGlob = (pattern) => {
|
|
|
18932
19046
|
};
|
|
18933
19047
|
//#endregion
|
|
18934
19048
|
//#region src/plugin/rules/react-builtins/forbid-component-props.ts
|
|
18935
|
-
const resolveSettings$
|
|
19049
|
+
const resolveSettings$43 = (settings) => {
|
|
18936
19050
|
const reactDoctor = settings?.["react-doctor"];
|
|
18937
19051
|
return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
|
|
18938
19052
|
};
|
|
@@ -18984,7 +19098,7 @@ const forbidComponentProps = defineRule({
|
|
|
18984
19098
|
recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
|
|
18985
19099
|
category: "Architecture",
|
|
18986
19100
|
create: (context) => {
|
|
18987
|
-
const entries = resolveSettings$
|
|
19101
|
+
const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
|
|
18988
19102
|
return { JSXOpeningElement(node) {
|
|
18989
19103
|
if (entries.length === 0) return;
|
|
18990
19104
|
if (!isSupportedJsxName(node.name)) return;
|
|
@@ -19013,7 +19127,7 @@ const forbidComponentProps = defineRule({
|
|
|
19013
19127
|
//#endregion
|
|
19014
19128
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
19015
19129
|
const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
|
|
19016
|
-
const resolveSettings$
|
|
19130
|
+
const resolveSettings$42 = (settings) => {
|
|
19017
19131
|
const reactDoctor = settings?.["react-doctor"];
|
|
19018
19132
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
19019
19133
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19035,7 +19149,7 @@ const forbidDomProps = defineRule({
|
|
|
19035
19149
|
recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
|
|
19036
19150
|
category: "Architecture",
|
|
19037
19151
|
create: (context) => {
|
|
19038
|
-
const forbidMap = resolveSettings$
|
|
19152
|
+
const forbidMap = resolveSettings$42(context.settings);
|
|
19039
19153
|
return { JSXOpeningElement(node) {
|
|
19040
19154
|
if (forbidMap.size === 0) return;
|
|
19041
19155
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -19098,7 +19212,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
19098
19212
|
//#endregion
|
|
19099
19213
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
19100
19214
|
const buildMessage$23 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here, so code stays on the approved UI surface.`;
|
|
19101
|
-
const resolveSettings$
|
|
19215
|
+
const resolveSettings$41 = (settings) => {
|
|
19102
19216
|
const reactDoctor = settings?.["react-doctor"];
|
|
19103
19217
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
19104
19218
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19114,7 +19228,7 @@ const forbidElements = defineRule({
|
|
|
19114
19228
|
recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
|
|
19115
19229
|
category: "Architecture",
|
|
19116
19230
|
create: (context) => {
|
|
19117
|
-
const forbidMap = resolveSettings$
|
|
19231
|
+
const forbidMap = resolveSettings$41(context.settings);
|
|
19118
19232
|
return {
|
|
19119
19233
|
JSXOpeningElement(node) {
|
|
19120
19234
|
if (forbidMap.size === 0) return;
|
|
@@ -19223,7 +19337,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
19223
19337
|
"h5",
|
|
19224
19338
|
"h6"
|
|
19225
19339
|
];
|
|
19226
|
-
const resolveSettings$
|
|
19340
|
+
const resolveSettings$40 = (settings) => {
|
|
19227
19341
|
const reactDoctor = settings?.["react-doctor"];
|
|
19228
19342
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
19229
19343
|
return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
|
|
@@ -19236,7 +19350,7 @@ const headingHasContent = defineRule({
|
|
|
19236
19350
|
recommendation: "Put readable text in every heading.",
|
|
19237
19351
|
category: "Accessibility",
|
|
19238
19352
|
create: (context) => {
|
|
19239
|
-
const settings = resolveSettings$
|
|
19353
|
+
const settings = resolveSettings$40(context.settings);
|
|
19240
19354
|
return { JSXOpeningElement(node) {
|
|
19241
19355
|
const elementType = getElementType(node, context.settings);
|
|
19242
19356
|
if (!settings.headingTags.has(elementType)) return;
|
|
@@ -19257,7 +19371,7 @@ const headingHasContent = defineRule({
|
|
|
19257
19371
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
19258
19372
|
const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
|
|
19259
19373
|
const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
|
|
19260
|
-
const resolveSettings$
|
|
19374
|
+
const resolveSettings$39 = (settings) => {
|
|
19261
19375
|
const reactDoctor = settings?.["react-doctor"];
|
|
19262
19376
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
19263
19377
|
};
|
|
@@ -19284,7 +19398,7 @@ const hookUseState = defineRule({
|
|
|
19284
19398
|
recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
|
|
19285
19399
|
category: "Architecture",
|
|
19286
19400
|
create: (context) => {
|
|
19287
|
-
const { allowDestructuredState } = resolveSettings$
|
|
19401
|
+
const { allowDestructuredState } = resolveSettings$39(context.settings);
|
|
19288
19402
|
return { CallExpression(node) {
|
|
19289
19403
|
const callExpressionNode = node;
|
|
19290
19404
|
if (!isReactFunctionCall(callExpressionNode, "useState")) return;
|
|
@@ -19417,7 +19531,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19417
19531
|
//#endregion
|
|
19418
19532
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19419
19533
|
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19420
|
-
const resolveSettings$
|
|
19534
|
+
const resolveSettings$38 = (settings) => {
|
|
19421
19535
|
const reactDoctor = settings?.["react-doctor"];
|
|
19422
19536
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
19423
19537
|
};
|
|
@@ -19454,7 +19568,7 @@ const htmlHasLang = defineRule({
|
|
|
19454
19568
|
recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
|
|
19455
19569
|
category: "Accessibility",
|
|
19456
19570
|
create: (context) => {
|
|
19457
|
-
const settings = resolveSettings$
|
|
19571
|
+
const settings = resolveSettings$38(context.settings);
|
|
19458
19572
|
const tagSet = new Set(settings.htmlTags);
|
|
19459
19573
|
return { JSXOpeningElement(node) {
|
|
19460
19574
|
const tag = getElementType(node, context.settings);
|
|
@@ -19970,7 +20084,7 @@ const DEFAULT_REDUNDANT_WORDS = [
|
|
|
19970
20084
|
"photo",
|
|
19971
20085
|
"picture"
|
|
19972
20086
|
];
|
|
19973
|
-
const resolveSettings$
|
|
20087
|
+
const resolveSettings$37 = (settings) => {
|
|
19974
20088
|
const reactDoctor = settings?.["react-doctor"];
|
|
19975
20089
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
|
|
19976
20090
|
return {
|
|
@@ -20018,7 +20132,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20018
20132
|
category: "Accessibility",
|
|
20019
20133
|
create: (context) => {
|
|
20020
20134
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
20021
|
-
const settings = resolveSettings$
|
|
20135
|
+
const settings = resolveSettings$37(context.settings);
|
|
20022
20136
|
return { JSXOpeningElement(node) {
|
|
20023
20137
|
const tag = getElementType(node, context.settings);
|
|
20024
20138
|
if (!settings.components.includes(tag)) return;
|
|
@@ -20326,7 +20440,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
|
|
|
20326
20440
|
"row",
|
|
20327
20441
|
"gridcell"
|
|
20328
20442
|
]);
|
|
20329
|
-
const resolveSettings$
|
|
20443
|
+
const resolveSettings$36 = (settings) => {
|
|
20330
20444
|
const reactDoctor = settings?.["react-doctor"];
|
|
20331
20445
|
return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
|
|
20332
20446
|
};
|
|
@@ -20338,7 +20452,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
20338
20452
|
recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
|
|
20339
20453
|
category: "Accessibility",
|
|
20340
20454
|
create: (context) => {
|
|
20341
|
-
const settings = resolveSettings$
|
|
20455
|
+
const settings = resolveSettings$36(context.settings);
|
|
20342
20456
|
const tabbableSet = new Set(settings.tabbable);
|
|
20343
20457
|
return { JSXOpeningElement(node) {
|
|
20344
20458
|
if (isLocalTestScaffoldJsx(node, context)) return;
|
|
@@ -22479,25 +22593,49 @@ const jsLengthCheckFirst = defineRule({
|
|
|
22479
22593
|
});
|
|
22480
22594
|
//#endregion
|
|
22481
22595
|
//#region src/plugin/utils/is-proven-global-namespace-reference.ts
|
|
22596
|
+
const GLOBAL_SELF_PROPERTY_NAMES = new Set([
|
|
22597
|
+
"global",
|
|
22598
|
+
"globalThis",
|
|
22599
|
+
"self",
|
|
22600
|
+
"window"
|
|
22601
|
+
]);
|
|
22482
22602
|
const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22603
|
+
let currentExpression = expression;
|
|
22604
|
+
while (true) {
|
|
22605
|
+
const strippedExpression = stripParenExpression(currentExpression);
|
|
22606
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
22607
|
+
if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22608
|
+
const symbol = scopes.symbolFor(strippedExpression);
|
|
22609
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
22610
|
+
const declaration = symbol.declarationNode;
|
|
22611
|
+
const initializer = declaration.init;
|
|
22612
|
+
if (!initializer) return false;
|
|
22613
|
+
const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
|
|
22614
|
+
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
22615
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
22616
|
+
const isDirectGlobalSelfAlias = isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === symbol.bindingIdentifier && bindingProperty.parent === declaration.id && destructuredPropertyName !== null && GLOBAL_SELF_PROPERTY_NAMES.has(destructuredPropertyName);
|
|
22617
|
+
if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
|
|
22618
|
+
visitedSymbolIds.add(symbol.id);
|
|
22619
|
+
currentExpression = initializer;
|
|
22620
|
+
}
|
|
22490
22621
|
};
|
|
22491
22622
|
const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22492
|
-
|
|
22493
|
-
|
|
22623
|
+
let currentExpression = expression;
|
|
22624
|
+
while (true) {
|
|
22625
|
+
const strippedExpression = stripParenExpression(currentExpression);
|
|
22626
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22494
22627
|
if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22495
22628
|
const symbol = scopes.symbolFor(strippedExpression);
|
|
22496
|
-
if (!symbol
|
|
22629
|
+
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22497
22630
|
visitedSymbolIds.add(symbol.id);
|
|
22498
|
-
|
|
22631
|
+
const declaration = symbol.declarationNode;
|
|
22632
|
+
const bindingIdentifier = symbol.bindingIdentifier;
|
|
22633
|
+
const bindingProperty = bindingIdentifier.parent;
|
|
22634
|
+
if (getDestructuredBindingPropertyName(bindingIdentifier) === namespaceName && isNodeOfType(declaration, "VariableDeclarator") && isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === bindingIdentifier && bindingProperty.parent === declaration.id && declaration.init) return isProvenGlobalObjectReference(declaration.init, scopes);
|
|
22635
|
+
const directInitializer = getDirectConstInitializer(symbol);
|
|
22636
|
+
if (!directInitializer) return false;
|
|
22637
|
+
currentExpression = directInitializer;
|
|
22499
22638
|
}
|
|
22500
|
-
return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22501
22639
|
};
|
|
22502
22640
|
//#endregion
|
|
22503
22641
|
//#region src/plugin/rules/js-performance/js-min-max-loop.ts
|
|
@@ -23610,7 +23748,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
23610
23748
|
const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23611
23749
|
const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23612
23750
|
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
|
|
23613
|
-
const resolveSettings$
|
|
23751
|
+
const resolveSettings$35 = (settings) => {
|
|
23614
23752
|
const reactDoctor = settings?.["react-doctor"];
|
|
23615
23753
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
23616
23754
|
return {
|
|
@@ -23628,7 +23766,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23628
23766
|
recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
|
|
23629
23767
|
category: "Architecture",
|
|
23630
23768
|
create: (context) => {
|
|
23631
|
-
const settings = resolveSettings$
|
|
23769
|
+
const settings = resolveSettings$35(context.settings);
|
|
23632
23770
|
const alwaysSet = new Set(settings.always);
|
|
23633
23771
|
const neverSet = new Set(settings.never);
|
|
23634
23772
|
return { JSXAttribute(node) {
|
|
@@ -23682,7 +23820,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23682
23820
|
const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
|
|
23683
23821
|
const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
|
|
23684
23822
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
23685
|
-
const resolveSettings$
|
|
23823
|
+
const resolveSettings$34 = (settings) => {
|
|
23686
23824
|
const reactDoctor = settings?.["react-doctor"];
|
|
23687
23825
|
const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
|
|
23688
23826
|
if (isAllowedMode(ruleSettingsRaw)) return {
|
|
@@ -23778,7 +23916,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
23778
23916
|
recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
|
|
23779
23917
|
category: "Architecture",
|
|
23780
23918
|
create: (context) => {
|
|
23781
|
-
const settings = resolveSettings$
|
|
23919
|
+
const settings = resolveSettings$34(context.settings);
|
|
23782
23920
|
return {
|
|
23783
23921
|
JSXAttribute(node) {
|
|
23784
23922
|
const value = node.value;
|
|
@@ -23861,7 +23999,7 @@ const containsJsxElement = (root) => {
|
|
|
23861
23999
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
23862
24000
|
const JSX_NOT_ALLOWED = (extension) => `This file contains JSX but uses a \`${extension}\` name, so the filename no longer signals JSX to readers or tooling conventions.`;
|
|
23863
24001
|
const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are reserved for JSX here, so using that extension without JSX makes file-type conventions less useful.`;
|
|
23864
|
-
const resolveSettings$
|
|
24002
|
+
const resolveSettings$33 = (settings) => {
|
|
23865
24003
|
const reactDoctor = settings?.["react-doctor"];
|
|
23866
24004
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
23867
24005
|
return {
|
|
@@ -23883,7 +24021,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
23883
24021
|
recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
|
|
23884
24022
|
category: "Architecture",
|
|
23885
24023
|
create: (context) => {
|
|
23886
|
-
const settings = resolveSettings$
|
|
24024
|
+
const settings = resolveSettings$33(context.settings);
|
|
23887
24025
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
23888
24026
|
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
23889
24027
|
const extensionOnly = path.extname(filename).slice(1);
|
|
@@ -23924,7 +24062,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
23924
24062
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
23925
24063
|
const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
|
|
23926
24064
|
const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
|
|
23927
|
-
const resolveSettings$
|
|
24065
|
+
const resolveSettings$32 = (settings) => {
|
|
23928
24066
|
const reactDoctor = settings?.["react-doctor"];
|
|
23929
24067
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
23930
24068
|
};
|
|
@@ -23936,7 +24074,7 @@ const jsxFragments = defineRule({
|
|
|
23936
24074
|
recommendation: "Use one fragment style so identical wrappers do not look different across files.",
|
|
23937
24075
|
category: "Architecture",
|
|
23938
24076
|
create: (context) => {
|
|
23939
|
-
const { mode } = resolveSettings$
|
|
24077
|
+
const { mode } = resolveSettings$32(context.settings);
|
|
23940
24078
|
return {
|
|
23941
24079
|
JSXElement(node) {
|
|
23942
24080
|
if (mode !== "syntax") return;
|
|
@@ -23980,7 +24118,7 @@ const buildPropRegex = (handlerPropPrefix) => {
|
|
|
23980
24118
|
const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
23981
24119
|
return new RegExp(`^(${escaped})[A-Z].*$`);
|
|
23982
24120
|
};
|
|
23983
|
-
const resolveSettings$
|
|
24121
|
+
const resolveSettings$31 = (settings) => {
|
|
23984
24122
|
const reactDoctor = settings?.["react-doctor"];
|
|
23985
24123
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
|
|
23986
24124
|
let handlerPrefix = DEFAULT_HANDLER_PREFIX;
|
|
@@ -24056,7 +24194,7 @@ const jsxHandlerNames = defineRule({
|
|
|
24056
24194
|
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
|
|
24057
24195
|
category: "Architecture",
|
|
24058
24196
|
create: (context) => {
|
|
24059
|
-
const settings = resolveSettings$
|
|
24197
|
+
const settings = resolveSettings$31(context.settings);
|
|
24060
24198
|
return { JSXAttribute(node) {
|
|
24061
24199
|
if (settings.ignoreComponentNames.length > 0) {
|
|
24062
24200
|
const opening = node.parent;
|
|
@@ -24131,6 +24269,19 @@ const hasJsxKeyAttribute = (openingElement) => {
|
|
|
24131
24269
|
return false;
|
|
24132
24270
|
};
|
|
24133
24271
|
//#endregion
|
|
24272
|
+
//#region src/plugin/utils/is-component-function.ts
|
|
24273
|
+
const isFunctionAssignedToComponent = (functionNode) => {
|
|
24274
|
+
let cursor = functionNode.parent ?? null;
|
|
24275
|
+
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
24276
|
+
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
24277
|
+
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
24278
|
+
};
|
|
24279
|
+
const isComponentFunction$1 = (node) => {
|
|
24280
|
+
if (!isFunctionLike$1(node)) return false;
|
|
24281
|
+
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
24282
|
+
return isFunctionAssignedToComponent(node);
|
|
24283
|
+
};
|
|
24284
|
+
//#endregion
|
|
24134
24285
|
//#region src/plugin/utils/is-const-declared-binding.ts
|
|
24135
24286
|
const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
|
|
24136
24287
|
"VariableDeclarator",
|
|
@@ -24175,11 +24326,17 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
|
|
|
24175
24326
|
"flatMap",
|
|
24176
24327
|
"from"
|
|
24177
24328
|
]);
|
|
24329
|
+
const RENDERING_CALL_NAMES = new Set([
|
|
24330
|
+
"createPortal",
|
|
24331
|
+
"hydrate",
|
|
24332
|
+
"hydrateRoot",
|
|
24333
|
+
"render"
|
|
24334
|
+
]);
|
|
24178
24335
|
const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
|
|
24179
24336
|
const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
|
|
24180
|
-
const
|
|
24337
|
+
const KEY_BEFORE_SPREAD = "Place this `key` after the `{...spread}` so the spread cannot override it.";
|
|
24181
24338
|
const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
|
|
24182
|
-
const resolveSettings$
|
|
24339
|
+
const resolveSettings$30 = (settings) => {
|
|
24183
24340
|
const reactDoctor = settings?.["react-doctor"];
|
|
24184
24341
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
24185
24342
|
return {
|
|
@@ -24193,6 +24350,33 @@ const findArrayVariableDeclarator = (arrayExpression) => {
|
|
|
24193
24350
|
if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
|
|
24194
24351
|
return null;
|
|
24195
24352
|
};
|
|
24353
|
+
const isArrayNestedInObjectProperty = (arrayExpression) => {
|
|
24354
|
+
let current = findTransparentExpressionRoot(arrayExpression);
|
|
24355
|
+
while (current.parent) {
|
|
24356
|
+
const parent = current.parent;
|
|
24357
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
|
|
24358
|
+
current = findTransparentExpressionRoot(parent);
|
|
24359
|
+
continue;
|
|
24360
|
+
}
|
|
24361
|
+
return isNodeOfType(parent, "Property") && parent.value === current;
|
|
24362
|
+
}
|
|
24363
|
+
return false;
|
|
24364
|
+
};
|
|
24365
|
+
const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
|
|
24366
|
+
const transparentArray = findTransparentExpressionRoot(arrayExpression);
|
|
24367
|
+
const callExpression = transparentArray.parent;
|
|
24368
|
+
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
|
|
24369
|
+
const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
|
|
24370
|
+
if (isReactApiCall(callExpression, "createElement", scopes, {
|
|
24371
|
+
allowGlobalReactNamespace: true,
|
|
24372
|
+
allowUnboundBareCalls: true,
|
|
24373
|
+
resolveNamedAliases: true
|
|
24374
|
+
})) return argumentIndex < 2;
|
|
24375
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
24376
|
+
if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
|
|
24377
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
|
|
24378
|
+
return true;
|
|
24379
|
+
};
|
|
24196
24380
|
const isIdentityIteratorCallback = (callback) => {
|
|
24197
24381
|
const callbackFunction = stripParenExpression(callback);
|
|
24198
24382
|
if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
|
|
@@ -24289,7 +24473,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
|
|
|
24289
24473
|
namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
|
|
24290
24474
|
return iteratorCall;
|
|
24291
24475
|
};
|
|
24292
|
-
const findEnclosingIteratorContext = (jsxNode) => {
|
|
24476
|
+
const findEnclosingIteratorContext = (jsxNode, scopes) => {
|
|
24293
24477
|
let current = jsxNode;
|
|
24294
24478
|
let isOutsideContainingFunction = false;
|
|
24295
24479
|
let didSeeReturnStatement = false;
|
|
@@ -24311,6 +24495,8 @@ const findEnclosingIteratorContext = (jsxNode) => {
|
|
|
24311
24495
|
isOutsideContainingFunction = true;
|
|
24312
24496
|
} else if (isNodeOfType(parent, "ArrayExpression")) {
|
|
24313
24497
|
if (isOutsideContainingFunction) return null;
|
|
24498
|
+
if (isArrayNestedInObjectProperty(parent)) return null;
|
|
24499
|
+
if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
|
|
24314
24500
|
const arrayParent = parent.parent;
|
|
24315
24501
|
if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
|
|
24316
24502
|
if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
|
|
@@ -24392,9 +24578,15 @@ const isWithinChildrenToArray = (jsxNode) => {
|
|
|
24392
24578
|
const isKeyPropertyName = (propertyKey) => {
|
|
24393
24579
|
if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
|
|
24394
24580
|
if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
|
|
24395
|
-
return
|
|
24581
|
+
return false;
|
|
24396
24582
|
};
|
|
24397
24583
|
const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
|
|
24584
|
+
const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
|
|
24585
|
+
if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
|
|
24586
|
+
const firstParameter = functionNode.params?.[0];
|
|
24587
|
+
if (!firstParameter) return false;
|
|
24588
|
+
return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
|
|
24589
|
+
};
|
|
24398
24590
|
const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
24399
24591
|
const restElement = bindingIdentifier.parent;
|
|
24400
24592
|
if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
|
|
@@ -24404,7 +24596,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
|
24404
24596
|
const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
|
|
24405
24597
|
const functionNode = parameterNode.parent;
|
|
24406
24598
|
if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
|
|
24407
|
-
return functionNode
|
|
24599
|
+
return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
|
|
24408
24600
|
};
|
|
24409
24601
|
const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
24410
24602
|
const restElement = bindingIdentifier.parent;
|
|
@@ -24417,14 +24609,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
|
24417
24609
|
}
|
|
24418
24610
|
return false;
|
|
24419
24611
|
};
|
|
24420
|
-
const
|
|
24421
|
-
let
|
|
24612
|
+
const isBindingAssignedKey = (scopeOwner, bindingName) => {
|
|
24613
|
+
let didFindKeyAssignment = false;
|
|
24422
24614
|
walkAst(scopeOwner, (node) => {
|
|
24423
|
-
if (
|
|
24615
|
+
if (didFindKeyAssignment) return false;
|
|
24424
24616
|
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
|
|
24425
24617
|
const assignedObject = stripParenExpression(node.left.object);
|
|
24426
|
-
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
|
|
24427
|
-
|
|
24618
|
+
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
|
|
24619
|
+
didFindKeyAssignment = true;
|
|
24428
24620
|
return false;
|
|
24429
24621
|
}
|
|
24430
24622
|
}
|
|
@@ -24432,59 +24624,63 @@ const isBindingObjectMutated = (scopeOwner, bindingName) => {
|
|
|
24432
24624
|
const callee = node.callee;
|
|
24433
24625
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
|
|
24434
24626
|
const firstArgument = node.arguments[0];
|
|
24435
|
-
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
|
|
24436
|
-
|
|
24437
|
-
|
|
24627
|
+
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) for (const sourceArgument of node.arguments.slice(1)) {
|
|
24628
|
+
const source = stripParenExpression(sourceArgument);
|
|
24629
|
+
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
24630
|
+
if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
|
|
24631
|
+
didFindKeyAssignment = true;
|
|
24632
|
+
return false;
|
|
24633
|
+
}
|
|
24438
24634
|
}
|
|
24439
24635
|
}
|
|
24440
24636
|
}
|
|
24441
24637
|
});
|
|
24442
|
-
return
|
|
24638
|
+
return didFindKeyAssignment;
|
|
24443
24639
|
};
|
|
24444
|
-
const
|
|
24640
|
+
const spreadExpressionHasKey = (expression, depth) => {
|
|
24445
24641
|
const inner = stripParenExpression(expression);
|
|
24446
24642
|
if (isNodeOfType(inner, "ObjectExpression")) {
|
|
24447
24643
|
for (const property of inner.properties) {
|
|
24448
24644
|
if (isNodeOfType(property, "SpreadElement")) {
|
|
24449
|
-
if (depth >= 3)
|
|
24450
|
-
if (
|
|
24645
|
+
if (depth >= 3) continue;
|
|
24646
|
+
if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
|
|
24451
24647
|
continue;
|
|
24452
24648
|
}
|
|
24453
|
-
if (!isNodeOfType(property, "Property"))
|
|
24454
|
-
if (property.computed) return true;
|
|
24649
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
24455
24650
|
if (isKeyPropertyName(property.key)) return true;
|
|
24456
24651
|
}
|
|
24457
24652
|
return false;
|
|
24458
24653
|
}
|
|
24459
24654
|
if (isNodeOfType(inner, "ConditionalExpression")) {
|
|
24460
|
-
if (depth >= 3) return
|
|
24461
|
-
return
|
|
24655
|
+
if (depth >= 3) return false;
|
|
24656
|
+
return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
|
|
24462
24657
|
}
|
|
24463
24658
|
if (isNodeOfType(inner, "LogicalExpression")) {
|
|
24464
|
-
if (depth >= 3) return
|
|
24465
|
-
if (inner.operator === "&&") return
|
|
24466
|
-
return
|
|
24659
|
+
if (depth >= 3) return false;
|
|
24660
|
+
if (inner.operator === "&&") return spreadExpressionHasKey(inner.right, depth + 1);
|
|
24661
|
+
return spreadExpressionHasKey(inner.left, depth + 1) || spreadExpressionHasKey(inner.right, depth + 1);
|
|
24467
24662
|
}
|
|
24468
24663
|
if (isNodeOfType(inner, "Literal")) return false;
|
|
24469
24664
|
if (isThisPropsMember(inner)) return false;
|
|
24470
24665
|
if (isNodeOfType(inner, "Identifier")) {
|
|
24471
24666
|
if (inner.name === "undefined") return false;
|
|
24472
|
-
if (depth >= 3) return
|
|
24667
|
+
if (depth >= 3) return false;
|
|
24473
24668
|
const binding = findVariableInitializer(inner, inner.name);
|
|
24474
|
-
if (!binding) return
|
|
24669
|
+
if (!binding) return false;
|
|
24475
24670
|
if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
|
|
24476
24671
|
if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
|
|
24477
|
-
if (
|
|
24478
|
-
if (
|
|
24479
|
-
|
|
24672
|
+
if (isDirectComponentPropsParameter(binding.bindingIdentifier, binding.scopeOwner)) return false;
|
|
24673
|
+
if (!isConstDeclaredBinding(binding) || !binding.initializer) return false;
|
|
24674
|
+
if (isBindingAssignedKey(binding.scopeOwner, inner.name)) return true;
|
|
24675
|
+
return spreadExpressionHasKey(binding.initializer, depth + 1);
|
|
24480
24676
|
}
|
|
24481
|
-
return
|
|
24677
|
+
return false;
|
|
24482
24678
|
};
|
|
24483
|
-
const spreadCanOverwriteKey = (spreadAttribute) =>
|
|
24679
|
+
const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
|
|
24484
24680
|
const checkKeyBeforeSpread = (context, openingElement) => {
|
|
24485
24681
|
let keyIndex = null;
|
|
24486
24682
|
let keyAttribute = null;
|
|
24487
|
-
let
|
|
24683
|
+
let lastKeyCarryingSpreadIndex = null;
|
|
24488
24684
|
for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
|
|
24489
24685
|
const attribute = openingElement.attributes[attributeIndex];
|
|
24490
24686
|
if (isNodeOfType(attribute, "JSXAttribute")) {
|
|
@@ -24492,11 +24688,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
|
|
|
24492
24688
|
keyIndex = attributeIndex;
|
|
24493
24689
|
keyAttribute = attribute;
|
|
24494
24690
|
}
|
|
24495
|
-
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") &&
|
|
24691
|
+
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
|
|
24496
24692
|
}
|
|
24497
|
-
if (keyIndex !== null &&
|
|
24693
|
+
if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
|
|
24498
24694
|
node: keyAttribute,
|
|
24499
|
-
message:
|
|
24695
|
+
message: KEY_BEFORE_SPREAD
|
|
24500
24696
|
});
|
|
24501
24697
|
};
|
|
24502
24698
|
const getKeyAttributeValueString = (openingElement) => {
|
|
@@ -24540,7 +24736,7 @@ const jsxKey = defineRule({
|
|
|
24540
24736
|
severity: "error",
|
|
24541
24737
|
recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
|
|
24542
24738
|
create: (context) => {
|
|
24543
|
-
const settings = resolveSettings$
|
|
24739
|
+
const settings = resolveSettings$30(context.settings);
|
|
24544
24740
|
return {
|
|
24545
24741
|
JSXElement(node) {
|
|
24546
24742
|
const openingElement = node.openingElement;
|
|
@@ -24558,7 +24754,7 @@ const jsxKey = defineRule({
|
|
|
24558
24754
|
else seenKeys.add(keyValue.keyValue);
|
|
24559
24755
|
}
|
|
24560
24756
|
}
|
|
24561
|
-
const enclosingContext = findEnclosingIteratorContext(node);
|
|
24757
|
+
const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
|
|
24562
24758
|
if (!enclosingContext) return;
|
|
24563
24759
|
if (isWithinChildrenToArray(node)) return;
|
|
24564
24760
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
@@ -24594,7 +24790,7 @@ const jsxKey = defineRule({
|
|
|
24594
24790
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
24595
24791
|
const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
24596
24792
|
const DEFAULT_MAX_DEPTH = 14;
|
|
24597
|
-
const resolveSettings$
|
|
24793
|
+
const resolveSettings$29 = (settings) => {
|
|
24598
24794
|
const reactDoctor = settings?.["react-doctor"];
|
|
24599
24795
|
return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
|
|
24600
24796
|
};
|
|
@@ -24663,7 +24859,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24663
24859
|
recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
|
|
24664
24860
|
category: "Architecture",
|
|
24665
24861
|
create: (context) => {
|
|
24666
|
-
const { max } = resolveSettings$
|
|
24862
|
+
const { max } = resolveSettings$29(context.settings);
|
|
24667
24863
|
const deepestLeafPerTree = /* @__PURE__ */ new Map();
|
|
24668
24864
|
const checkNode = (node) => {
|
|
24669
24865
|
if (!isLeafJsxNode(node)) return;
|
|
@@ -26635,7 +26831,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26635
26831
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26636
26832
|
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26637
26833
|
const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
|
|
26638
|
-
const resolveSettings$
|
|
26834
|
+
const resolveSettings$28 = (settings) => {
|
|
26639
26835
|
const reactDoctor = settings?.["react-doctor"];
|
|
26640
26836
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
26641
26837
|
return reactDoctor.jsxNoScriptUrl ?? {};
|
|
@@ -26657,7 +26853,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26657
26853
|
recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
|
|
26658
26854
|
category: "Security",
|
|
26659
26855
|
create: (context) => {
|
|
26660
|
-
const options = resolveSettings$
|
|
26856
|
+
const options = resolveSettings$28(context.settings);
|
|
26661
26857
|
return { JSXOpeningElement(node) {
|
|
26662
26858
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26663
26859
|
const elementName = resolveJsxElementType(node);
|
|
@@ -26678,283 +26874,6 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26678
26874
|
}
|
|
26679
26875
|
});
|
|
26680
26876
|
//#endregion
|
|
26681
|
-
//#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
|
|
26682
|
-
const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
|
|
26683
|
-
const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
|
|
26684
|
-
const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
|
|
26685
|
-
const resolveSettings$28 = (settings) => {
|
|
26686
|
-
const reactDoctor = settings?.["react-doctor"];
|
|
26687
|
-
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
|
|
26688
|
-
const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
|
|
26689
|
-
const linkComponents = /* @__PURE__ */ new Map();
|
|
26690
|
-
for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
|
|
26691
|
-
else if (typeof entry === "object" && entry !== null) {
|
|
26692
|
-
const linkAttribute = entry.linkAttribute ?? "href";
|
|
26693
|
-
linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
|
|
26694
|
-
}
|
|
26695
|
-
const formComponents = /* @__PURE__ */ new Map();
|
|
26696
|
-
for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
|
|
26697
|
-
else if (typeof entry === "object" && entry !== null) {
|
|
26698
|
-
const formAttribute = entry.formAttribute ?? "action";
|
|
26699
|
-
formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
|
|
26700
|
-
}
|
|
26701
|
-
return {
|
|
26702
|
-
enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
|
|
26703
|
-
warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
|
|
26704
|
-
allowReferrer: ruleSettings.allowReferrer ?? false,
|
|
26705
|
-
links: ruleSettings.links ?? true,
|
|
26706
|
-
forms: ruleSettings.forms ?? false,
|
|
26707
|
-
linkComponents,
|
|
26708
|
-
formComponents
|
|
26709
|
-
};
|
|
26710
|
-
};
|
|
26711
|
-
const isExternalLink = (href) => href.includes("//");
|
|
26712
|
-
const matchHrefExpression = (expression, state) => {
|
|
26713
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
|
|
26714
|
-
if (isExternalLink(expression.value)) state.isExternal = true;
|
|
26715
|
-
return;
|
|
26716
|
-
}
|
|
26717
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
26718
|
-
state.isDynamic = true;
|
|
26719
|
-
return;
|
|
26720
|
-
}
|
|
26721
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26722
|
-
matchHrefExpression(expression.consequent, state);
|
|
26723
|
-
matchHrefExpression(expression.alternate, state);
|
|
26724
|
-
}
|
|
26725
|
-
};
|
|
26726
|
-
const checkHref = (attributeValue, enforceDynamicLinks) => {
|
|
26727
|
-
const state = {
|
|
26728
|
-
isExternal: false,
|
|
26729
|
-
isDynamic: false
|
|
26730
|
-
};
|
|
26731
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
|
|
26732
|
-
else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
|
|
26733
|
-
if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
|
|
26734
|
-
return !(state.isExternal || state.isDynamic);
|
|
26735
|
-
};
|
|
26736
|
-
const checkRelValue = (text, allowReferrer) => {
|
|
26737
|
-
const tokens = text.split(/\s+/);
|
|
26738
|
-
if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
|
|
26739
|
-
return tokens.some((token) => token.toLowerCase() === "noreferrer");
|
|
26740
|
-
};
|
|
26741
|
-
const matchRelExpression = (expression, allowReferrer) => {
|
|
26742
|
-
const empty = {
|
|
26743
|
-
combined: false,
|
|
26744
|
-
testName: "",
|
|
26745
|
-
consequent: false,
|
|
26746
|
-
alternate: false
|
|
26747
|
-
};
|
|
26748
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26749
|
-
combined: checkRelValue(expression.value, allowReferrer),
|
|
26750
|
-
testName: "",
|
|
26751
|
-
consequent: false,
|
|
26752
|
-
alternate: false
|
|
26753
|
-
};
|
|
26754
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26755
|
-
const consequent = matchRelExpression(expression.consequent, allowReferrer);
|
|
26756
|
-
const alternate = matchRelExpression(expression.alternate, allowReferrer);
|
|
26757
|
-
const test = expression.test;
|
|
26758
|
-
if (isNodeOfType(test, "Identifier")) return {
|
|
26759
|
-
combined: consequent.combined && alternate.combined,
|
|
26760
|
-
testName: test.name,
|
|
26761
|
-
consequent: consequent.combined,
|
|
26762
|
-
alternate: alternate.combined
|
|
26763
|
-
};
|
|
26764
|
-
return {
|
|
26765
|
-
combined: consequent.combined && alternate.combined,
|
|
26766
|
-
testName: "",
|
|
26767
|
-
consequent: consequent.combined,
|
|
26768
|
-
alternate: alternate.combined
|
|
26769
|
-
};
|
|
26770
|
-
}
|
|
26771
|
-
return empty;
|
|
26772
|
-
};
|
|
26773
|
-
const checkRel = (attributeValue, allowReferrer) => {
|
|
26774
|
-
const empty = {
|
|
26775
|
-
combined: false,
|
|
26776
|
-
testName: "",
|
|
26777
|
-
consequent: false,
|
|
26778
|
-
alternate: false
|
|
26779
|
-
};
|
|
26780
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26781
|
-
combined: checkRelValue(attributeValue.value, allowReferrer),
|
|
26782
|
-
testName: "",
|
|
26783
|
-
consequent: false,
|
|
26784
|
-
alternate: false
|
|
26785
|
-
};
|
|
26786
|
-
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26787
|
-
const expression = attributeValue.expression;
|
|
26788
|
-
if (expression.type === "JSXEmptyExpression") return empty;
|
|
26789
|
-
return matchRelExpression(expression, allowReferrer);
|
|
26790
|
-
}
|
|
26791
|
-
return empty;
|
|
26792
|
-
};
|
|
26793
|
-
const matchTargetExpression = (expression) => {
|
|
26794
|
-
const empty = {
|
|
26795
|
-
combined: false,
|
|
26796
|
-
testName: "",
|
|
26797
|
-
consequent: false,
|
|
26798
|
-
alternate: false
|
|
26799
|
-
};
|
|
26800
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26801
|
-
combined: expression.value.toLowerCase() === "_blank",
|
|
26802
|
-
testName: "",
|
|
26803
|
-
consequent: false,
|
|
26804
|
-
alternate: false
|
|
26805
|
-
};
|
|
26806
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26807
|
-
const consequent = matchTargetExpression(expression.consequent);
|
|
26808
|
-
const alternate = matchTargetExpression(expression.alternate);
|
|
26809
|
-
const test = expression.test;
|
|
26810
|
-
const combined = consequent.combined || alternate.combined;
|
|
26811
|
-
if (isNodeOfType(test, "Identifier")) return {
|
|
26812
|
-
combined,
|
|
26813
|
-
testName: test.name,
|
|
26814
|
-
consequent: consequent.combined,
|
|
26815
|
-
alternate: alternate.combined
|
|
26816
|
-
};
|
|
26817
|
-
return {
|
|
26818
|
-
combined,
|
|
26819
|
-
testName: "",
|
|
26820
|
-
consequent: consequent.combined,
|
|
26821
|
-
alternate: alternate.combined
|
|
26822
|
-
};
|
|
26823
|
-
}
|
|
26824
|
-
return empty;
|
|
26825
|
-
};
|
|
26826
|
-
const checkTarget = (attributeValue) => {
|
|
26827
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26828
|
-
combined: attributeValue.value.toLowerCase() === "_blank",
|
|
26829
|
-
testName: "",
|
|
26830
|
-
consequent: false,
|
|
26831
|
-
alternate: false
|
|
26832
|
-
};
|
|
26833
|
-
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26834
|
-
const expression = attributeValue.expression;
|
|
26835
|
-
if (expression.type === "JSXEmptyExpression") return {
|
|
26836
|
-
combined: false,
|
|
26837
|
-
testName: "",
|
|
26838
|
-
consequent: false,
|
|
26839
|
-
alternate: false
|
|
26840
|
-
};
|
|
26841
|
-
return matchTargetExpression(expression);
|
|
26842
|
-
}
|
|
26843
|
-
return {
|
|
26844
|
-
combined: false,
|
|
26845
|
-
testName: "",
|
|
26846
|
-
consequent: false,
|
|
26847
|
-
alternate: false
|
|
26848
|
-
};
|
|
26849
|
-
};
|
|
26850
|
-
const jsxNoTargetBlank = defineRule({
|
|
26851
|
-
id: "jsx-no-target-blank",
|
|
26852
|
-
title: "Unsafe target=_blank link",
|
|
26853
|
-
severity: "warn",
|
|
26854
|
-
recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
|
|
26855
|
-
category: "Security",
|
|
26856
|
-
create: (context) => {
|
|
26857
|
-
const settings = resolveSettings$28(context.settings);
|
|
26858
|
-
const isLink = (tagName) => {
|
|
26859
|
-
if (!settings.links) return false;
|
|
26860
|
-
if (tagName === "a") return true;
|
|
26861
|
-
return settings.linkComponents.has(tagName);
|
|
26862
|
-
};
|
|
26863
|
-
const isForm = (tagName) => {
|
|
26864
|
-
if (!settings.forms) return false;
|
|
26865
|
-
if (tagName === "form") return true;
|
|
26866
|
-
return settings.formComponents.has(tagName);
|
|
26867
|
-
};
|
|
26868
|
-
return { JSXOpeningElement(node) {
|
|
26869
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26870
|
-
const tagName = resolveJsxElementType(node);
|
|
26871
|
-
if (!tagName) return;
|
|
26872
|
-
if (!isLink(tagName) && !isForm(tagName)) return;
|
|
26873
|
-
const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
|
|
26874
|
-
const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
|
|
26875
|
-
let targetTuple = {
|
|
26876
|
-
combined: false,
|
|
26877
|
-
testName: "",
|
|
26878
|
-
consequent: false,
|
|
26879
|
-
alternate: false
|
|
26880
|
-
};
|
|
26881
|
-
let relTuple = {
|
|
26882
|
-
combined: false,
|
|
26883
|
-
testName: "",
|
|
26884
|
-
consequent: false,
|
|
26885
|
-
alternate: false
|
|
26886
|
-
};
|
|
26887
|
-
let isHrefValid = true;
|
|
26888
|
-
let hasHrefValue = false;
|
|
26889
|
-
let warnSpread = false;
|
|
26890
|
-
let targetReportNode = node.name;
|
|
26891
|
-
let spreadReportNode = null;
|
|
26892
|
-
for (const attribute of node.attributes) {
|
|
26893
|
-
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
26894
|
-
if (settings.warnOnSpreadAttributes) {
|
|
26895
|
-
warnSpread = true;
|
|
26896
|
-
spreadReportNode = attribute;
|
|
26897
|
-
targetTuple = {
|
|
26898
|
-
combined: false,
|
|
26899
|
-
testName: "",
|
|
26900
|
-
consequent: false,
|
|
26901
|
-
alternate: false
|
|
26902
|
-
};
|
|
26903
|
-
relTuple = {
|
|
26904
|
-
combined: false,
|
|
26905
|
-
testName: "",
|
|
26906
|
-
consequent: false,
|
|
26907
|
-
alternate: false
|
|
26908
|
-
};
|
|
26909
|
-
isHrefValid = false;
|
|
26910
|
-
hasHrefValue = true;
|
|
26911
|
-
}
|
|
26912
|
-
continue;
|
|
26913
|
-
}
|
|
26914
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
26915
|
-
const attributeName = attribute.name;
|
|
26916
|
-
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
26917
|
-
const propName = attributeName.name;
|
|
26918
|
-
const value = attribute.value;
|
|
26919
|
-
if (propName === "target") {
|
|
26920
|
-
if (value) {
|
|
26921
|
-
targetTuple = checkTarget(value);
|
|
26922
|
-
targetReportNode = value;
|
|
26923
|
-
}
|
|
26924
|
-
} else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
|
|
26925
|
-
if (value) {
|
|
26926
|
-
hasHrefValue = true;
|
|
26927
|
-
isHrefValid = checkHref(value, settings.enforceDynamicLinks);
|
|
26928
|
-
}
|
|
26929
|
-
} else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
|
|
26930
|
-
}
|
|
26931
|
-
if (warnSpread) {
|
|
26932
|
-
if (hasHrefValue && isHrefValid || relTuple.combined) return;
|
|
26933
|
-
context.report({
|
|
26934
|
-
node: spreadReportNode ?? node,
|
|
26935
|
-
message: SPREAD_MESSAGE
|
|
26936
|
-
});
|
|
26937
|
-
return;
|
|
26938
|
-
}
|
|
26939
|
-
if (!isHrefValid) {
|
|
26940
|
-
if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
|
|
26941
|
-
const consequentBad = targetTuple.consequent && !relTuple.consequent;
|
|
26942
|
-
const alternateBad = targetTuple.alternate && !relTuple.alternate;
|
|
26943
|
-
if (consequentBad || alternateBad) context.report({
|
|
26944
|
-
node: targetReportNode,
|
|
26945
|
-
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26946
|
-
});
|
|
26947
|
-
return;
|
|
26948
|
-
}
|
|
26949
|
-
if (targetTuple.combined && !relTuple.combined) context.report({
|
|
26950
|
-
node: targetReportNode,
|
|
26951
|
-
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26952
|
-
});
|
|
26953
|
-
}
|
|
26954
|
-
} };
|
|
26955
|
-
}
|
|
26956
|
-
});
|
|
26957
|
-
//#endregion
|
|
26958
26877
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
26959
26878
|
const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
26960
26879
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -28612,6 +28531,30 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
|
|
|
28612
28531
|
const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
|
|
28613
28532
|
//#endregion
|
|
28614
28533
|
//#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
|
|
28534
|
+
const unwrapAwaitedExpression = (node) => {
|
|
28535
|
+
let expression = stripParenExpression(node);
|
|
28536
|
+
while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
|
|
28537
|
+
return expression;
|
|
28538
|
+
};
|
|
28539
|
+
const isNextNavigationRedirectCall = (node, context) => {
|
|
28540
|
+
const expression = unwrapAwaitedExpression(node);
|
|
28541
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
28542
|
+
const callee = stripParenExpression(expression.callee);
|
|
28543
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
28544
|
+
if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
|
|
28545
|
+
const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
|
|
28546
|
+
return importedName === "redirect" || importedName === "permanentRedirect";
|
|
28547
|
+
};
|
|
28548
|
+
const isRedirectOnlyDefaultExport = (programNode, context) => {
|
|
28549
|
+
const defaultExport = findExportedValue(programNode, "default");
|
|
28550
|
+
if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
|
|
28551
|
+
if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
|
|
28552
|
+
if (defaultExport.body.body.length !== 1) return false;
|
|
28553
|
+
const onlyStatement = defaultExport.body.body[0];
|
|
28554
|
+
if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
|
|
28555
|
+
if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
|
|
28556
|
+
return false;
|
|
28557
|
+
};
|
|
28615
28558
|
const nextjsMissingMetadata = defineRule({
|
|
28616
28559
|
id: "nextjs-missing-metadata",
|
|
28617
28560
|
title: "Page missing metadata for search previews",
|
|
@@ -28631,6 +28574,7 @@ const nextjsMissingMetadata = defineRule({
|
|
|
28631
28574
|
if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
|
|
28632
28575
|
return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
|
|
28633
28576
|
})) return;
|
|
28577
|
+
if (isRedirectOnlyDefaultExport(programNode, context)) return;
|
|
28634
28578
|
if (hasAncestorMetadataLayout(context.filename ?? "")) return;
|
|
28635
28579
|
context.report({
|
|
28636
28580
|
node: programNode,
|
|
@@ -34759,6 +34703,19 @@ const noCascadingSetState = defineRetiredRule({
|
|
|
34759
34703
|
recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
|
|
34760
34704
|
});
|
|
34761
34705
|
//#endregion
|
|
34706
|
+
//#region src/plugin/utils/get-react-use-callback-call.ts
|
|
34707
|
+
const getReactUseCallbackCall = (expression, scopes) => {
|
|
34708
|
+
const identifier = stripParenExpression(expression);
|
|
34709
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
34710
|
+
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
34711
|
+
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
34712
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
34713
|
+
return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
|
|
34714
|
+
allowGlobalReactNamespace: true,
|
|
34715
|
+
resolveNamedAliases: true
|
|
34716
|
+
}) ? initializer : null;
|
|
34717
|
+
};
|
|
34718
|
+
//#endregion
|
|
34762
34719
|
//#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
|
|
34763
34720
|
const getRefCurrentSymbol = (node, context) => {
|
|
34764
34721
|
const expression = stripParenExpression(node);
|
|
@@ -35287,19 +35244,8 @@ const isBuiltinNamespaceCallee = (callee) => {
|
|
|
35287
35244
|
}
|
|
35288
35245
|
return false;
|
|
35289
35246
|
};
|
|
35290
|
-
const getReactUseCallbackSource = (reference, context) => {
|
|
35291
|
-
const identifier = reference.identifier;
|
|
35292
|
-
const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
|
|
35293
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
35294
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
35295
|
-
if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
|
|
35296
|
-
allowGlobalReactNamespace: true,
|
|
35297
|
-
resolveNamedAliases: true
|
|
35298
|
-
})) return initializer;
|
|
35299
|
-
return null;
|
|
35300
|
-
};
|
|
35301
35247
|
const getDependencyStateRefs = (analysis, context, dependencyReference) => {
|
|
35302
|
-
const useCallbackCall =
|
|
35248
|
+
const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
|
|
35303
35249
|
if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
35304
35250
|
const dependencyList = useCallbackCall.arguments?.[1];
|
|
35305
35251
|
if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
@@ -36887,19 +36833,6 @@ const noDefaultProps = defineRule({
|
|
|
36887
36833
|
} })
|
|
36888
36834
|
});
|
|
36889
36835
|
//#endregion
|
|
36890
|
-
//#region src/plugin/utils/is-component-function.ts
|
|
36891
|
-
const isFunctionAssignedToComponent = (functionNode) => {
|
|
36892
|
-
let cursor = functionNode.parent ?? null;
|
|
36893
|
-
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
36894
|
-
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
36895
|
-
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
36896
|
-
};
|
|
36897
|
-
const isComponentFunction$1 = (node) => {
|
|
36898
|
-
if (!isFunctionLike$1(node)) return false;
|
|
36899
|
-
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
36900
|
-
return isFunctionAssignedToComponent(node);
|
|
36901
|
-
};
|
|
36902
|
-
//#endregion
|
|
36903
36836
|
//#region src/plugin/utils/create-component-prop-stack-tracker.ts
|
|
36904
36837
|
const extractDestructuredPropNames = (params) => {
|
|
36905
36838
|
const propNames = /* @__PURE__ */ new Set();
|
|
@@ -38813,6 +38746,17 @@ const noDynamicImportPath = defineRule({
|
|
|
38813
38746
|
})
|
|
38814
38747
|
});
|
|
38815
38748
|
//#endregion
|
|
38749
|
+
//#region src/plugin/utils/get-require-call-source.ts
|
|
38750
|
+
const getRequireCallSource = (expression) => {
|
|
38751
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
38752
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
38753
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
38754
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
38755
|
+
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
38756
|
+
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
38757
|
+
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
38758
|
+
};
|
|
38759
|
+
//#endregion
|
|
38816
38760
|
//#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
|
|
38817
38761
|
const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
|
|
38818
38762
|
"each",
|
|
@@ -38957,7 +38901,16 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
|
|
|
38957
38901
|
}
|
|
38958
38902
|
return dependencyStateSymbolIds;
|
|
38959
38903
|
};
|
|
38960
|
-
const
|
|
38904
|
+
const resolveSynchronouslyInvokedFunction = (expression, scopes) => {
|
|
38905
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
38906
|
+
if (localFunction) return localFunction;
|
|
38907
|
+
const useCallbackCall = getReactUseCallbackCall(expression, scopes);
|
|
38908
|
+
if (!useCallbackCall) return null;
|
|
38909
|
+
const callback = useCallbackCall.arguments[0];
|
|
38910
|
+
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
38911
|
+
return resolveExactLocalFunction(callback, scopes);
|
|
38912
|
+
};
|
|
38913
|
+
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
|
|
38961
38914
|
const analysisFunctions = new Set([effectCallback]);
|
|
38962
38915
|
const pendingFunctions = [effectCallback];
|
|
38963
38916
|
while (pendingFunctions.length > 0) {
|
|
@@ -38965,9 +38918,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
|
38965
38918
|
if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
|
|
38966
38919
|
walkInsideStatementBlocks(currentFunction.body, (child) => {
|
|
38967
38920
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38968
|
-
const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
38921
|
+
const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
|
|
38969
38922
|
if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
|
|
38970
|
-
if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
|
|
38923
|
+
if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
|
|
38971
38924
|
analysisFunctions.add(invokedFunction);
|
|
38972
38925
|
pendingFunctions.push(invokedFunction);
|
|
38973
38926
|
});
|
|
@@ -38980,7 +38933,51 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
38980
38933
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
38981
38934
|
}
|
|
38982
38935
|
};
|
|
38983
|
-
const
|
|
38936
|
+
const getStateNameForSetterCall = (node, setterSymbolIdToStateName, scopes) => {
|
|
38937
|
+
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
|
|
38938
|
+
const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
|
|
38939
|
+
return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
|
|
38940
|
+
};
|
|
38941
|
+
const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
|
|
38942
|
+
const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
|
|
38943
|
+
const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
|
|
38944
|
+
const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
|
|
38945
|
+
if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
|
|
38946
|
+
let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
|
|
38947
|
+
if (!isFunctionLike$1(analysisFunction)) return false;
|
|
38948
|
+
let hasExecutableDefault = false;
|
|
38949
|
+
for (const parameter of analysisFunction.params) {
|
|
38950
|
+
walkInsideStatementBlocks(parameter, (child) => {
|
|
38951
|
+
if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasExecutableDefault = true;
|
|
38952
|
+
});
|
|
38953
|
+
if (hasExecutableDefault) return true;
|
|
38954
|
+
}
|
|
38955
|
+
return false;
|
|
38956
|
+
});
|
|
38957
|
+
const stableWrittenStateNames = /* @__PURE__ */ new Set();
|
|
38958
|
+
const allWrittenStateNames = /* @__PURE__ */ new Set();
|
|
38959
|
+
visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
|
|
38960
|
+
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
38961
|
+
if (writtenStateName) allWrittenStateNames.add(writtenStateName);
|
|
38962
|
+
});
|
|
38963
|
+
visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
|
|
38964
|
+
if (hasUnprovenObservableWork) return;
|
|
38965
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
38966
|
+
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
38967
|
+
if (writtenStateName) {
|
|
38968
|
+
stableWrittenStateNames.add(writtenStateName);
|
|
38969
|
+
return;
|
|
38970
|
+
}
|
|
38971
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
38972
|
+
if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
|
|
38973
|
+
hasUnprovenObservableWork = true;
|
|
38974
|
+
return;
|
|
38975
|
+
}
|
|
38976
|
+
if (isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasUnprovenObservableWork = true;
|
|
38977
|
+
});
|
|
38978
|
+
return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
|
|
38979
|
+
};
|
|
38980
|
+
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
38984
38981
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38985
38982
|
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
38986
38983
|
const literalValue = unwrappedExpression.value;
|
|
@@ -38988,46 +38985,49 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
|
|
|
38988
38985
|
return null;
|
|
38989
38986
|
}
|
|
38990
38987
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
38991
|
-
|
|
38988
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
38989
|
+
if (symbol?.id === stateSymbolId) return stateValue;
|
|
38990
|
+
if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
|
|
38992
38991
|
if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
|
|
38992
|
+
if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
|
|
38993
38993
|
const immutableSymbol = scopes.symbolFor(unwrappedExpression);
|
|
38994
38994
|
if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
|
|
38995
|
-
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
|
|
38995
|
+
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id), additionalStateValues);
|
|
38996
38996
|
}
|
|
38997
38997
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
|
|
38998
38998
|
if (unwrappedExpression.operator === "void") return { value: void 0 };
|
|
38999
38999
|
if (unwrappedExpression.operator !== "!") return null;
|
|
39000
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39000
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39001
39001
|
return argumentValue ? { value: !argumentValue.value } : null;
|
|
39002
39002
|
}
|
|
39003
39003
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
39004
39004
|
if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
|
|
39005
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39005
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39006
39006
|
return argumentValue ? { value: Boolean(argumentValue.value) } : null;
|
|
39007
39007
|
}
|
|
39008
39008
|
return null;
|
|
39009
39009
|
}
|
|
39010
39010
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
39011
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39011
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39012
39012
|
if (!leftValue) return null;
|
|
39013
39013
|
if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
|
|
39014
39014
|
if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
|
|
39015
39015
|
if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
|
|
39016
|
-
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39016
|
+
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39017
39017
|
}
|
|
39018
39018
|
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
39019
|
-
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39019
|
+
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39020
39020
|
if (!testValue) return null;
|
|
39021
|
-
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39021
|
+
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39022
39022
|
}
|
|
39023
39023
|
if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
|
|
39024
|
-
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39024
|
+
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39025
39025
|
if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
|
|
39026
39026
|
return null;
|
|
39027
39027
|
}
|
|
39028
39028
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
39029
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39030
|
-
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39029
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39030
|
+
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39031
39031
|
if (!leftValue || !rightValue) return null;
|
|
39032
39032
|
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
39033
39033
|
const areEqual = leftValue.value === rightValue.value;
|
|
@@ -39066,8 +39066,7 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39066
39066
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39067
39067
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39068
39068
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
39069
|
-
const
|
|
39070
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
39069
|
+
const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
39071
39070
|
if (!stateName) return;
|
|
39072
39071
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
39073
39072
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -39083,27 +39082,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39083
39082
|
const isGlobalBooleanCall = (node, scopes) => {
|
|
39084
39083
|
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
|
|
39085
39084
|
};
|
|
39086
|
-
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
|
|
39085
|
+
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
39087
39086
|
let currentNode = workNode;
|
|
39088
39087
|
while (currentNode.parent) {
|
|
39089
39088
|
const parentNode = currentNode.parent;
|
|
39090
39089
|
if (isFunctionLike$1(parentNode)) break;
|
|
39091
39090
|
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
39092
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39091
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39093
39092
|
if (testValue) {
|
|
39094
39093
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39095
39094
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39096
39095
|
}
|
|
39097
39096
|
}
|
|
39098
39097
|
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
39099
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39098
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39100
39099
|
if (testValue) {
|
|
39101
39100
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39102
39101
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39103
39102
|
}
|
|
39104
39103
|
}
|
|
39105
39104
|
if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
|
|
39106
|
-
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
|
|
39105
|
+
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39107
39106
|
if (leftValue) {
|
|
39108
39107
|
if (parentNode.operator === "&&" && !leftValue.value) return false;
|
|
39109
39108
|
if (parentNode.operator === "||" && leftValue.value) return false;
|
|
@@ -39115,7 +39114,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39115
39114
|
if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
|
|
39116
39115
|
const earlierStatement = parentNode.body[index];
|
|
39117
39116
|
if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
|
|
39118
|
-
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
|
|
39117
|
+
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
|
|
39119
39118
|
}
|
|
39120
39119
|
}
|
|
39121
39120
|
currentNode = parentNode;
|
|
@@ -39125,24 +39124,93 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39125
39124
|
const isReaderWorkNode = (node, analysisFunctions, scopes) => {
|
|
39126
39125
|
if (isNodeOfType(node, "CallExpression")) {
|
|
39127
39126
|
if (isGlobalBooleanCall(node, scopes)) return false;
|
|
39128
|
-
const invokedFunction =
|
|
39127
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
|
|
39129
39128
|
return !invokedFunction || !analysisFunctions.has(invokedFunction);
|
|
39130
39129
|
}
|
|
39131
39130
|
return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
|
|
39132
39131
|
};
|
|
39133
|
-
const
|
|
39134
|
-
if (
|
|
39135
|
-
|
|
39136
|
-
|
|
39132
|
+
const mergeKnownStateValues = (additionalStateValues, parameterStateValues) => {
|
|
39133
|
+
if (parameterStateValues.size === 0) return additionalStateValues;
|
|
39134
|
+
const knownStateValues = new Map(additionalStateValues);
|
|
39135
|
+
for (const [symbolId, value] of parameterStateValues) knownStateValues.set(symbolId, value);
|
|
39136
|
+
return knownStateValues;
|
|
39137
|
+
};
|
|
39138
|
+
const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
|
|
39139
|
+
if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
|
|
39140
|
+
const parameterStateValues = /* @__PURE__ */ new Map();
|
|
39141
|
+
const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
|
|
39142
|
+
for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
|
|
39143
|
+
const rawParameter = invokedFunction.params[parameterIndex];
|
|
39144
|
+
const rawArgument = invocation.arguments[parameterIndex];
|
|
39145
|
+
if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
|
|
39146
|
+
const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
|
|
39147
|
+
if (!isNodeOfType(parameter, "Identifier")) continue;
|
|
39148
|
+
const parameterSymbol = scopes.symbolFor(parameter);
|
|
39149
|
+
if (!parameterSymbol) continue;
|
|
39150
|
+
let argumentValue = null;
|
|
39151
|
+
if (rawArgument) {
|
|
39152
|
+
argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
|
|
39153
|
+
if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39154
|
+
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39155
|
+
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39156
|
+
}
|
|
39157
|
+
} else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39158
|
+
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39159
|
+
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39160
|
+
} else argumentValue = { value: void 0 };
|
|
39161
|
+
if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
|
|
39162
|
+
}
|
|
39163
|
+
return parameterStateValues;
|
|
39164
|
+
};
|
|
39165
|
+
const haveEqualParameterStateValues = (leftValues, rightValues) => {
|
|
39166
|
+
if (leftValues.size !== rightValues.size) return false;
|
|
39167
|
+
for (const [symbolId, leftValue] of leftValues) {
|
|
39168
|
+
const rightValue = rightValues.get(symbolId);
|
|
39169
|
+
if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
|
|
39170
|
+
}
|
|
39171
|
+
return true;
|
|
39172
|
+
};
|
|
39173
|
+
const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
39174
|
+
const pendingFrames = [{
|
|
39175
|
+
functionNode: readerEffect.callback,
|
|
39176
|
+
parameterStateValues: /* @__PURE__ */ new Map()
|
|
39177
|
+
}];
|
|
39178
|
+
const visitedParameterStateValues = /* @__PURE__ */ new Map();
|
|
39179
|
+
while (pendingFrames.length > 0) {
|
|
39180
|
+
const frame = pendingFrames.pop();
|
|
39181
|
+
if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
|
|
39182
|
+
const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
|
|
39183
|
+
if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
|
|
39184
|
+
functionParameterStateValues.push(frame.parameterStateValues);
|
|
39185
|
+
visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
|
|
39186
|
+
const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
|
|
39137
39187
|
let didFindReachableWork = false;
|
|
39138
|
-
|
|
39139
|
-
if (didFindReachableWork
|
|
39140
|
-
if (
|
|
39188
|
+
walkInsideStatementBlocks(frame.functionNode.body, (child) => {
|
|
39189
|
+
if (didFindReachableWork) return;
|
|
39190
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
39191
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
39192
|
+
if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
|
|
39193
|
+
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
|
|
39194
|
+
functionNode: invokedFunction,
|
|
39195
|
+
parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
|
|
39196
|
+
});
|
|
39197
|
+
return;
|
|
39198
|
+
}
|
|
39199
|
+
}
|
|
39200
|
+
if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
|
|
39141
39201
|
});
|
|
39142
39202
|
if (didFindReachableWork) return true;
|
|
39143
39203
|
}
|
|
39144
39204
|
return false;
|
|
39145
39205
|
};
|
|
39206
|
+
const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
|
|
39207
|
+
const writeInfo = writerEffect.stateWrites.get(writtenStateName);
|
|
39208
|
+
const stateSymbolId = stateSymbolIds.get(writtenStateName);
|
|
39209
|
+
if (!writeInfo || stateSymbolId === void 0) return true;
|
|
39210
|
+
if (writeInfo.hasUnknownValue) return true;
|
|
39211
|
+
for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
|
|
39212
|
+
return false;
|
|
39213
|
+
};
|
|
39146
39214
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
39147
39215
|
const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
39148
39216
|
"clear",
|
|
@@ -39153,26 +39221,499 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
39153
39221
|
"keys",
|
|
39154
39222
|
"values"
|
|
39155
39223
|
]);
|
|
39156
|
-
const
|
|
39157
|
-
|
|
39158
|
-
|
|
39159
|
-
|
|
39160
|
-
|
|
39161
|
-
|
|
39162
|
-
|
|
39224
|
+
const EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES = new Map([
|
|
39225
|
+
["fetch", new Set([
|
|
39226
|
+
"cross-fetch",
|
|
39227
|
+
"node-fetch",
|
|
39228
|
+
"undici"
|
|
39229
|
+
])],
|
|
39230
|
+
["ky", new Set(["ky"])],
|
|
39231
|
+
["got", new Set(["got"])],
|
|
39232
|
+
["wretch", new Set(["wretch"])],
|
|
39233
|
+
["ofetch", new Set(["ofetch"])],
|
|
39234
|
+
["setTimeout", new Set([
|
|
39235
|
+
"node:timers",
|
|
39236
|
+
"node:timers/promises",
|
|
39237
|
+
"timers",
|
|
39238
|
+
"timers/promises"
|
|
39239
|
+
])],
|
|
39240
|
+
["setInterval", new Set([
|
|
39241
|
+
"node:timers",
|
|
39242
|
+
"node:timers/promises",
|
|
39243
|
+
"timers",
|
|
39244
|
+
"timers/promises"
|
|
39245
|
+
])]
|
|
39246
|
+
]);
|
|
39247
|
+
const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
|
|
39248
|
+
["axios", "axios"],
|
|
39249
|
+
["cross-fetch", "fetch"],
|
|
39250
|
+
["got", "got"],
|
|
39251
|
+
["ky", "ky"],
|
|
39252
|
+
["node-fetch", "fetch"],
|
|
39253
|
+
["wretch", "wretch"]
|
|
39254
|
+
]);
|
|
39255
|
+
const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
|
|
39256
|
+
"axios",
|
|
39257
|
+
"cross-fetch",
|
|
39258
|
+
"got",
|
|
39259
|
+
"ky",
|
|
39260
|
+
"node-fetch",
|
|
39261
|
+
"ofetch",
|
|
39262
|
+
"undici",
|
|
39263
|
+
"wretch"
|
|
39264
|
+
]);
|
|
39265
|
+
const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
|
|
39266
|
+
"axios",
|
|
39267
|
+
"got",
|
|
39268
|
+
"ky",
|
|
39269
|
+
"ofetch",
|
|
39270
|
+
"wretch"
|
|
39271
|
+
]);
|
|
39272
|
+
const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
|
|
39273
|
+
...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
|
|
39274
|
+
"fetch",
|
|
39275
|
+
"patch",
|
|
39276
|
+
"post",
|
|
39277
|
+
"put",
|
|
39278
|
+
"request"
|
|
39279
|
+
]);
|
|
39280
|
+
const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
|
|
39281
|
+
const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
|
|
39282
|
+
const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
|
|
39283
|
+
...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
|
|
39284
|
+
...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
|
|
39285
|
+
"EventTarget",
|
|
39286
|
+
"XMLHttpRequest"
|
|
39287
|
+
]);
|
|
39288
|
+
const getDestructuredBindingDepth = (bindingIdentifier) => {
|
|
39289
|
+
let bindingNode = bindingIdentifier;
|
|
39290
|
+
let depth = 0;
|
|
39291
|
+
while (true) {
|
|
39292
|
+
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
39293
|
+
const property = bindingNode.parent;
|
|
39294
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
|
|
39295
|
+
depth += 1;
|
|
39296
|
+
bindingNode = property.parent;
|
|
39297
|
+
}
|
|
39298
|
+
};
|
|
39299
|
+
const isMutatedMemberExpression = (rawMemberExpression) => {
|
|
39300
|
+
let expression = findTransparentExpressionRoot(rawMemberExpression);
|
|
39301
|
+
while (expression.parent) {
|
|
39302
|
+
const parent = expression.parent;
|
|
39303
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
|
|
39304
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39305
|
+
continue;
|
|
39306
|
+
}
|
|
39307
|
+
if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
|
|
39308
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39309
|
+
continue;
|
|
39310
|
+
}
|
|
39311
|
+
if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
|
|
39312
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39313
|
+
continue;
|
|
39314
|
+
}
|
|
39315
|
+
if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
|
|
39316
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39317
|
+
continue;
|
|
39318
|
+
}
|
|
39319
|
+
if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
|
|
39320
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39321
|
+
continue;
|
|
39322
|
+
}
|
|
39323
|
+
break;
|
|
39324
|
+
}
|
|
39325
|
+
const parent = expression.parent;
|
|
39326
|
+
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expression || isNodeOfType(parent, "UpdateExpression") && parent.argument === expression || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression || (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) && parent.left === expression);
|
|
39327
|
+
};
|
|
39328
|
+
const isReactHookDependencyArgument = (expression, scopes) => {
|
|
39329
|
+
const call = expression.parent;
|
|
39330
|
+
return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
|
|
39331
|
+
allowGlobalReactNamespace: true,
|
|
39332
|
+
allowUnboundBareCalls: true,
|
|
39333
|
+
resolveNamedAliases: true
|
|
39334
|
+
}));
|
|
39335
|
+
};
|
|
39336
|
+
const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
|
|
39337
|
+
const getContainingReactDependencyValue = (rawExpression, containerKind) => {
|
|
39338
|
+
const expression = findTransparentExpressionRoot(rawExpression);
|
|
39339
|
+
const parent = expression.parent;
|
|
39340
|
+
if (isNodeOfType(parent, "ArrayExpression")) return {
|
|
39341
|
+
expression: parent,
|
|
39342
|
+
containerKind: "array"
|
|
39343
|
+
};
|
|
39344
|
+
if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
|
|
39345
|
+
expression: parent.parent,
|
|
39346
|
+
containerKind: "array"
|
|
39347
|
+
};
|
|
39348
|
+
if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
|
|
39349
|
+
expression: parent.parent,
|
|
39350
|
+
containerKind: "object"
|
|
39351
|
+
};
|
|
39352
|
+
return null;
|
|
39353
|
+
};
|
|
39354
|
+
const getFullArrayRestCopySymbol = (initializer, scopes) => {
|
|
39355
|
+
const declaration = initializer.parent;
|
|
39356
|
+
if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
|
|
39357
|
+
const restElement = declaration.id.elements[0];
|
|
39358
|
+
if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
|
|
39359
|
+
const symbol = scopes.symbolFor(restElement.argument);
|
|
39360
|
+
return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
|
|
39361
|
+
};
|
|
39362
|
+
const isReactDependencyArrayReference = (initialExpression, scopes) => {
|
|
39363
|
+
const pendingPaths = [{
|
|
39364
|
+
expression: initialExpression,
|
|
39365
|
+
containerKind: null
|
|
39366
|
+
}];
|
|
39367
|
+
const visitedExpressions = /* @__PURE__ */ new Set();
|
|
39368
|
+
let didFindReactDependencyArgument = false;
|
|
39369
|
+
while (pendingPaths.length > 0) {
|
|
39370
|
+
const pendingPath = pendingPaths.pop();
|
|
39371
|
+
if (!pendingPath) continue;
|
|
39372
|
+
const expression = findTransparentExpressionRoot(pendingPath.expression);
|
|
39373
|
+
if (visitedExpressions.has(expression)) continue;
|
|
39374
|
+
visitedExpressions.add(expression);
|
|
39375
|
+
if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
|
|
39376
|
+
didFindReactDependencyArgument = true;
|
|
39377
|
+
continue;
|
|
39378
|
+
}
|
|
39379
|
+
const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
|
|
39380
|
+
if (containingValue) {
|
|
39381
|
+
pendingPaths.push(containingValue);
|
|
39382
|
+
continue;
|
|
39383
|
+
}
|
|
39384
|
+
const declaration = expression.parent;
|
|
39385
|
+
if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
|
|
39386
|
+
const symbol = scopes.symbolFor(declaration.id);
|
|
39387
|
+
if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
|
|
39388
|
+
if (symbol.references.length === 0) return false;
|
|
39389
|
+
for (const reference of symbol.references) pendingPaths.push({
|
|
39390
|
+
expression: reference.identifier,
|
|
39391
|
+
containerKind: pendingPath.containerKind
|
|
39392
|
+
});
|
|
39393
|
+
continue;
|
|
39394
|
+
}
|
|
39395
|
+
if (pendingPath.containerKind === "array") {
|
|
39396
|
+
const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
|
|
39397
|
+
if (restCopySymbol) {
|
|
39398
|
+
if (restCopySymbol.references.length === 0) return false;
|
|
39399
|
+
for (const reference of restCopySymbol.references) pendingPaths.push({
|
|
39400
|
+
expression: reference.identifier,
|
|
39401
|
+
containerKind: "array"
|
|
39402
|
+
});
|
|
39403
|
+
continue;
|
|
39404
|
+
}
|
|
39405
|
+
}
|
|
39406
|
+
return false;
|
|
39407
|
+
}
|
|
39408
|
+
return didFindReactDependencyArgument;
|
|
39409
|
+
};
|
|
39410
|
+
const hasSafeReceiverAliases = (rootSymbols, scopes) => {
|
|
39411
|
+
const pendingSymbols = [...rootSymbols];
|
|
39412
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
39413
|
+
while (pendingSymbols.length > 0) {
|
|
39414
|
+
const symbol = pendingSymbols.pop();
|
|
39415
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
|
|
39416
|
+
visitedSymbolIds.add(symbol.id);
|
|
39417
|
+
for (const reference of symbol.references) {
|
|
39418
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39419
|
+
const container = expression.parent;
|
|
39420
|
+
if (isReactDependencyArrayReference(expression, scopes)) continue;
|
|
39421
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
|
|
39422
|
+
if (isMutatedMemberExpression(container)) return false;
|
|
39423
|
+
continue;
|
|
39424
|
+
}
|
|
39425
|
+
if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
|
|
39426
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
|
|
39427
|
+
if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
|
|
39428
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
|
|
39429
|
+
const aliasSymbol = scopes.symbolFor(container.id);
|
|
39430
|
+
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39431
|
+
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39432
|
+
pendingSymbols.push(aliasSymbol);
|
|
39433
|
+
continue;
|
|
39434
|
+
}
|
|
39435
|
+
return false;
|
|
39163
39436
|
}
|
|
39164
|
-
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39165
39437
|
}
|
|
39166
|
-
|
|
39438
|
+
return true;
|
|
39439
|
+
};
|
|
39440
|
+
const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
|
|
39441
|
+
for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
|
|
39167
39442
|
return false;
|
|
39168
39443
|
};
|
|
39169
|
-
const
|
|
39170
|
-
|
|
39171
|
-
|
|
39172
|
-
if (isNodeOfType(
|
|
39173
|
-
|
|
39444
|
+
const getTypeScriptImportEqualsSource = (symbol) => {
|
|
39445
|
+
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return null;
|
|
39446
|
+
const moduleReference = symbol.declarationNode.moduleReference;
|
|
39447
|
+
if (!isNodeOfType(moduleReference, "TSExternalModuleReference") || !isNodeOfType(moduleReference.expression, "Literal")) return null;
|
|
39448
|
+
return typeof moduleReference.expression.value === "string" ? moduleReference.expression.value : null;
|
|
39449
|
+
};
|
|
39450
|
+
const getUnshadowedRequireSource = (rawExpression, scopes) => {
|
|
39451
|
+
let expression = stripParenExpression(rawExpression);
|
|
39452
|
+
while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
|
|
39453
|
+
if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
|
|
39454
|
+
return getRequireCallSource(rawExpression);
|
|
39455
|
+
};
|
|
39456
|
+
const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
|
|
39457
|
+
let expression = stripParenExpression(rawExpression);
|
|
39458
|
+
let outermostExportedName = null;
|
|
39459
|
+
let exportedPropertyDepth = 0;
|
|
39460
|
+
let resolvedBinding = null;
|
|
39461
|
+
let isCommonJsModuleObject = false;
|
|
39462
|
+
const traversedSymbolIds = new Set(visitedSymbolIds);
|
|
39463
|
+
const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
|
|
39464
|
+
while (true) {
|
|
39465
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39466
|
+
const propertyName = getStaticPropertyName(expression);
|
|
39467
|
+
if (propertyName === null) return null;
|
|
39468
|
+
outermostExportedName ??= propertyName;
|
|
39469
|
+
exportedPropertyDepth += 1;
|
|
39470
|
+
expression = stripParenExpression(expression.object);
|
|
39471
|
+
continue;
|
|
39472
|
+
}
|
|
39473
|
+
const requireSource = getUnshadowedRequireSource(expression, scopes);
|
|
39474
|
+
if (requireSource !== null) {
|
|
39475
|
+
resolvedBinding = {
|
|
39476
|
+
source: requireSource,
|
|
39477
|
+
exportedName: null,
|
|
39478
|
+
isModuleObject: true
|
|
39479
|
+
};
|
|
39480
|
+
isCommonJsModuleObject = true;
|
|
39481
|
+
break;
|
|
39482
|
+
}
|
|
39483
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39484
|
+
const symbol = scopes.symbolFor(expression);
|
|
39485
|
+
if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
|
|
39486
|
+
if (symbol.kind === "import") {
|
|
39487
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
39488
|
+
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
|
|
39489
|
+
if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
|
|
39490
|
+
source: importDeclaration.source.value,
|
|
39491
|
+
exportedName: getImportedName(symbol.declarationNode) ?? null,
|
|
39492
|
+
isModuleObject: false
|
|
39493
|
+
};
|
|
39494
|
+
else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
|
|
39495
|
+
source: importDeclaration.source.value,
|
|
39496
|
+
exportedName: "default",
|
|
39497
|
+
isModuleObject: false
|
|
39498
|
+
};
|
|
39499
|
+
else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
|
|
39500
|
+
source: importDeclaration.source.value,
|
|
39501
|
+
exportedName: null,
|
|
39502
|
+
isModuleObject: true
|
|
39503
|
+
};
|
|
39504
|
+
break;
|
|
39505
|
+
}
|
|
39506
|
+
const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
|
|
39507
|
+
if (importEqualsSource !== null) {
|
|
39508
|
+
resolvedBinding = {
|
|
39509
|
+
source: importEqualsSource,
|
|
39510
|
+
exportedName: null,
|
|
39511
|
+
isModuleObject: true
|
|
39512
|
+
};
|
|
39513
|
+
isCommonJsModuleObject = true;
|
|
39514
|
+
commonJsModuleObjectSymbols.add(symbol);
|
|
39515
|
+
break;
|
|
39516
|
+
}
|
|
39517
|
+
const directInitializer = getDirectUnreassignedInitializer(symbol);
|
|
39518
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
39519
|
+
const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
|
|
39520
|
+
if (!initializer) return null;
|
|
39521
|
+
traversedSymbolIds.add(symbol.id);
|
|
39522
|
+
outermostExportedName ??= destructuredPropertyName;
|
|
39523
|
+
if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
|
|
39524
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
39525
|
+
if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
|
|
39526
|
+
expression = unwrappedInitializer;
|
|
39527
|
+
}
|
|
39528
|
+
if (!resolvedBinding) return null;
|
|
39529
|
+
if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
|
|
39530
|
+
if (outermostExportedName === null) return resolvedBinding;
|
|
39531
|
+
if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
|
|
39532
|
+
return {
|
|
39533
|
+
source: resolvedBinding.source,
|
|
39534
|
+
exportedName: outermostExportedName,
|
|
39535
|
+
isModuleObject: false
|
|
39536
|
+
};
|
|
39537
|
+
};
|
|
39538
|
+
const getCanonicalExternalSyncExportName = (binding) => {
|
|
39539
|
+
if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
|
|
39540
|
+
return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
|
|
39541
|
+
};
|
|
39542
|
+
const isKnownExternalSyncDirectModuleBinding = (binding) => {
|
|
39543
|
+
const exportedName = getCanonicalExternalSyncExportName(binding);
|
|
39544
|
+
return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
|
|
39545
|
+
};
|
|
39546
|
+
const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
|
|
39547
|
+
"Array",
|
|
39548
|
+
"BigInt",
|
|
39549
|
+
"Boolean",
|
|
39550
|
+
"Date",
|
|
39551
|
+
"Number",
|
|
39552
|
+
"String",
|
|
39553
|
+
"Symbol"
|
|
39554
|
+
]);
|
|
39555
|
+
const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
|
|
39556
|
+
"Array",
|
|
39557
|
+
"Boolean",
|
|
39558
|
+
"Date",
|
|
39559
|
+
"Map",
|
|
39560
|
+
"Number",
|
|
39561
|
+
"Promise",
|
|
39562
|
+
"RegExp",
|
|
39563
|
+
"Set",
|
|
39564
|
+
"String",
|
|
39565
|
+
"URL",
|
|
39566
|
+
"URLSearchParams",
|
|
39567
|
+
"WeakMap",
|
|
39568
|
+
"WeakSet"
|
|
39569
|
+
]);
|
|
39570
|
+
const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
|
|
39571
|
+
"all",
|
|
39572
|
+
"allSettled",
|
|
39573
|
+
"any",
|
|
39574
|
+
"race",
|
|
39575
|
+
"reject",
|
|
39576
|
+
"resolve",
|
|
39577
|
+
"withResolvers"
|
|
39578
|
+
]);
|
|
39579
|
+
const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39580
|
+
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
|
|
39581
|
+
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39582
|
+
const isNonCleanupValue = (returnValue) => {
|
|
39583
|
+
const expression = stripParenExpression(returnValue);
|
|
39584
|
+
if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
|
|
39585
|
+
if (isNodeOfType(expression, "Literal") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "JSXElement") || isNodeOfType(expression, "JSXFragment") || isNodeOfType(expression, "TemplateLiteral") || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
|
|
39586
|
+
if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
|
|
39587
|
+
if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
|
|
39588
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39589
|
+
const finalExpression = expression.expressions.at(-1);
|
|
39590
|
+
return Boolean(finalExpression && isNonCleanupValue(finalExpression));
|
|
39591
|
+
}
|
|
39592
|
+
if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39593
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
39594
|
+
if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39595
|
+
if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.object, "Identifier") && expression.callee.object.name === "Promise" && scopes.isGlobalReference(expression.callee.object) && PROMISE_STATIC_RESULT_METHOD_NAMES.has(getStaticPropertyName(expression.callee) ?? "")) return true;
|
|
39596
|
+
if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
|
|
39597
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39598
|
+
return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
|
|
39599
|
+
};
|
|
39600
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
|
|
39601
|
+
let returnsOnlyNonCleanup = true;
|
|
39602
|
+
walkAst(functionNode.body, (child) => {
|
|
39603
|
+
if (!returnsOnlyNonCleanup) return false;
|
|
39604
|
+
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39605
|
+
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39606
|
+
if (child.argument && !isNonCleanupValue(child.argument)) {
|
|
39607
|
+
returnsOnlyNonCleanup = false;
|
|
39608
|
+
return false;
|
|
39609
|
+
}
|
|
39610
|
+
});
|
|
39611
|
+
return returnsOnlyNonCleanup;
|
|
39612
|
+
};
|
|
39613
|
+
const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39614
|
+
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
|
|
39615
|
+
hasCleanup: false,
|
|
39616
|
+
isValid: false
|
|
39617
|
+
};
|
|
39618
|
+
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39619
|
+
const getCleanupReturnProof = (returnValue) => {
|
|
39620
|
+
const expression = stripParenExpression(returnValue);
|
|
39621
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
|
|
39622
|
+
hasCleanup: true,
|
|
39623
|
+
isValid: true
|
|
39624
|
+
};
|
|
39625
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
39626
|
+
if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
|
|
39627
|
+
hasCleanup: false,
|
|
39628
|
+
isValid: true
|
|
39629
|
+
};
|
|
39630
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
39631
|
+
return {
|
|
39632
|
+
hasCleanup: Boolean(localFunction),
|
|
39633
|
+
isValid: Boolean(localFunction)
|
|
39634
|
+
};
|
|
39635
|
+
}
|
|
39636
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
|
|
39637
|
+
hasCleanup: false,
|
|
39638
|
+
isValid: true
|
|
39639
|
+
};
|
|
39640
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
39641
|
+
const consequentProof = getCleanupReturnProof(expression.consequent);
|
|
39642
|
+
const alternateProof = getCleanupReturnProof(expression.alternate);
|
|
39643
|
+
return {
|
|
39644
|
+
hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
|
|
39645
|
+
isValid: consequentProof.isValid && alternateProof.isValid
|
|
39646
|
+
};
|
|
39647
|
+
}
|
|
39648
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
39649
|
+
const leftProof = getCleanupReturnProof(expression.left);
|
|
39650
|
+
const rightProof = getCleanupReturnProof(expression.right);
|
|
39651
|
+
return {
|
|
39652
|
+
hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
|
|
39653
|
+
isValid: leftProof.isValid && rightProof.isValid
|
|
39654
|
+
};
|
|
39655
|
+
}
|
|
39656
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39657
|
+
const finalExpression = expression.expressions.at(-1);
|
|
39658
|
+
return finalExpression ? getCleanupReturnProof(finalExpression) : {
|
|
39659
|
+
hasCleanup: false,
|
|
39660
|
+
isValid: false
|
|
39661
|
+
};
|
|
39662
|
+
}
|
|
39663
|
+
if (!isNodeOfType(expression, "CallExpression")) return {
|
|
39664
|
+
hasCleanup: false,
|
|
39665
|
+
isValid: false
|
|
39666
|
+
};
|
|
39667
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39668
|
+
return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
|
|
39669
|
+
hasCleanup: false,
|
|
39670
|
+
isValid: false
|
|
39671
|
+
};
|
|
39672
|
+
};
|
|
39673
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
|
|
39674
|
+
let cleanupReturnProof = {
|
|
39675
|
+
hasCleanup: false,
|
|
39676
|
+
isValid: true
|
|
39677
|
+
};
|
|
39678
|
+
walkAst(functionNode.body, (child) => {
|
|
39679
|
+
if (!cleanupReturnProof.isValid) return false;
|
|
39680
|
+
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39681
|
+
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39682
|
+
if (!child.argument) return;
|
|
39683
|
+
const returnProof = getCleanupReturnProof(child.argument);
|
|
39684
|
+
if (!returnProof.isValid) {
|
|
39685
|
+
cleanupReturnProof = {
|
|
39686
|
+
hasCleanup: false,
|
|
39687
|
+
isValid: false
|
|
39688
|
+
};
|
|
39689
|
+
return false;
|
|
39690
|
+
}
|
|
39691
|
+
cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
|
|
39692
|
+
});
|
|
39693
|
+
return cleanupReturnProof;
|
|
39694
|
+
};
|
|
39695
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
|
|
39696
|
+
const unwrappedReturnedValue = stripParenExpression(returnedValue);
|
|
39697
|
+
if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
|
|
39698
|
+
if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
|
|
39699
|
+
if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
|
|
39700
|
+
const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
|
|
39701
|
+
if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
39702
|
+
}
|
|
39703
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
|
|
39704
|
+
if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
|
|
39705
|
+
if (invokedFunction) {
|
|
39706
|
+
const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
|
|
39707
|
+
if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
|
|
39708
|
+
}
|
|
39709
|
+
if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
|
|
39710
|
+
return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39711
|
+
}
|
|
39712
|
+
if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
|
|
39174
39713
|
return false;
|
|
39175
39714
|
};
|
|
39715
|
+
const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
|
|
39716
|
+
const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
|
|
39176
39717
|
const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
|
|
39177
39718
|
const collectStorageHookSetterNames = (componentBody) => {
|
|
39178
39719
|
const setterNames = /* @__PURE__ */ new Set();
|
|
@@ -39214,14 +39755,6 @@ const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, sco
|
|
|
39214
39755
|
allowUnboundBareCalls: true,
|
|
39215
39756
|
resolveNamedAliases: true
|
|
39216
39757
|
});
|
|
39217
|
-
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
39218
|
-
const expression = stripParenExpression(rawExpression);
|
|
39219
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39220
|
-
const symbol = scopes.symbolFor(expression);
|
|
39221
|
-
if (!symbol) return null;
|
|
39222
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39223
|
-
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
39224
|
-
};
|
|
39225
39758
|
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
39226
39759
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
39227
39760
|
const symbol = scopes.symbolFor(openingElement.name);
|
|
@@ -39229,19 +39762,33 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
|
39229
39762
|
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
39230
39763
|
};
|
|
39231
39764
|
const isDirectHostJsxRef = (symbol, scopes) => {
|
|
39232
|
-
let
|
|
39233
|
-
|
|
39234
|
-
|
|
39235
|
-
|
|
39236
|
-
|
|
39237
|
-
if (!
|
|
39238
|
-
|
|
39239
|
-
|
|
39240
|
-
|
|
39241
|
-
|
|
39242
|
-
|
|
39243
|
-
|
|
39244
|
-
|
|
39765
|
+
let didFindHostRef = false;
|
|
39766
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
39767
|
+
const pendingSymbols = [symbol];
|
|
39768
|
+
while (pendingSymbols.length > 0) {
|
|
39769
|
+
const currentSymbol = pendingSymbols.pop();
|
|
39770
|
+
if (!currentSymbol || visitedSymbolIds.has(currentSymbol.id)) continue;
|
|
39771
|
+
visitedSymbolIds.add(currentSymbol.id);
|
|
39772
|
+
for (const reference of currentSymbol.references) {
|
|
39773
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39774
|
+
const container = expression.parent;
|
|
39775
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
39776
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
|
|
39777
|
+
const aliasSymbol = scopes.symbolFor(container.id);
|
|
39778
|
+
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39779
|
+
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39780
|
+
pendingSymbols.push(aliasSymbol);
|
|
39781
|
+
continue;
|
|
39782
|
+
}
|
|
39783
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
39784
|
+
const attribute = container.parent;
|
|
39785
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
39786
|
+
const openingElement = attribute.parent;
|
|
39787
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
39788
|
+
didFindHostRef = true;
|
|
39789
|
+
}
|
|
39790
|
+
}
|
|
39791
|
+
return didFindHostRef;
|
|
39245
39792
|
};
|
|
39246
39793
|
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
39247
39794
|
const identifier = stripParenExpression(expression);
|
|
@@ -39328,7 +39875,11 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
|
|
|
39328
39875
|
}
|
|
39329
39876
|
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39330
39877
|
if (getStaticPropertyName(expression) === "current") {
|
|
39331
|
-
const symbol =
|
|
39878
|
+
const symbol = resolveReactRefSymbol(expression, scopes, {
|
|
39879
|
+
allowUnboundBareCalls: true,
|
|
39880
|
+
includeCreateRef: true,
|
|
39881
|
+
resolveNamedAliases: true
|
|
39882
|
+
});
|
|
39332
39883
|
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
39333
39884
|
}
|
|
39334
39885
|
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
@@ -39346,19 +39897,80 @@ const isCommittedDomSyncNode = (node, scopes) => {
|
|
|
39346
39897
|
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
39347
39898
|
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
39348
39899
|
};
|
|
39349
|
-
const
|
|
39350
|
-
|
|
39351
|
-
if (isNodeOfType(
|
|
39900
|
+
const isProvenHttpClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39901
|
+
const expression = stripParenExpression(rawExpression);
|
|
39902
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
39903
|
+
const receiverSymbol = scopes.symbolFor(expression);
|
|
39904
|
+
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
39905
|
+
}
|
|
39906
|
+
if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
|
|
39907
|
+
const moduleBinding = resolveExternalModuleBinding(expression, scopes);
|
|
39908
|
+
if (moduleBinding && EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES.has(moduleBinding.source) && (moduleBinding.isModuleObject || moduleBinding.exportedName === "default" && EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.has(moduleBinding.source) || isKnownExternalSyncDirectModuleBinding(moduleBinding))) return true;
|
|
39909
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
39910
|
+
const callee = stripParenExpression(expression.callee);
|
|
39911
|
+
return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
|
|
39912
|
+
}
|
|
39913
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39914
|
+
const symbol = scopes.symbolFor(expression);
|
|
39915
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39916
|
+
if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
|
|
39917
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39918
|
+
return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39919
|
+
};
|
|
39920
|
+
const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39921
|
+
const expression = stripParenExpression(rawExpression);
|
|
39922
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
39923
|
+
const receiverSymbol = scopes.symbolFor(expression);
|
|
39924
|
+
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
39925
|
+
}
|
|
39926
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
39927
|
+
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
39928
|
+
return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
|
|
39929
|
+
}
|
|
39930
|
+
if (isNodeOfType(expression, "NewExpression")) {
|
|
39931
|
+
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
39932
|
+
return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
|
|
39933
|
+
}
|
|
39934
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39935
|
+
const symbol = scopes.symbolFor(expression);
|
|
39936
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39937
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39938
|
+
return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39939
|
+
};
|
|
39940
|
+
const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39941
|
+
const expression = stripParenExpression(rawExpression);
|
|
39942
|
+
if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
|
|
39943
|
+
if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
|
|
39944
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39945
|
+
const symbol = scopes.symbolFor(expression);
|
|
39946
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39947
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39948
|
+
return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39949
|
+
};
|
|
39950
|
+
const isProvenExternalSyncDirectCallee = (callee, scopes) => {
|
|
39951
|
+
if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
|
|
39952
|
+
const moduleBinding = resolveExternalModuleBinding(callee, scopes);
|
|
39953
|
+
return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
|
|
39954
|
+
};
|
|
39955
|
+
const isExternalSyncNode = (node, scopes) => {
|
|
39956
|
+
if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
|
|
39957
|
+
if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
|
|
39958
|
+
const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
|
|
39959
|
+
return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
|
|
39960
|
+
allowUnboundBareCalls: true,
|
|
39961
|
+
includeCreateRef: true,
|
|
39962
|
+
resolveNamedAliases: true
|
|
39963
|
+
}));
|
|
39964
|
+
}
|
|
39352
39965
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
39353
|
-
if (
|
|
39966
|
+
if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
|
|
39354
39967
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
39355
39968
|
const propertyName = getStaticPropertyName(node.callee);
|
|
39356
39969
|
if (propertyName === null) return false;
|
|
39357
|
-
if (
|
|
39358
|
-
if (
|
|
39359
|
-
if (
|
|
39360
|
-
|
|
39361
|
-
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39970
|
+
if (isBrowserStorageReceiver(node.callee.object, scopes)) return true;
|
|
39971
|
+
if (TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES.has(propertyName)) return isProvenTanStackQueryClientReceiver(node.callee.object, scopes);
|
|
39972
|
+
if (EXTERNAL_SYNC_HTTP_METHOD_NAMES.has(propertyName)) return isProvenHttpClientReceiver(node.callee.object, scopes);
|
|
39973
|
+
return EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName) && isProvenExternalResourceReceiver(node.callee.object, scopes);
|
|
39362
39974
|
};
|
|
39363
39975
|
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
|
|
39364
39976
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -39367,7 +39979,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
|
|
|
39367
39979
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
|
|
39368
39980
|
let didFindExternalCall = false;
|
|
39369
39981
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39370
|
-
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39982
|
+
if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39371
39983
|
});
|
|
39372
39984
|
return didFindExternalCall;
|
|
39373
39985
|
};
|
|
@@ -39389,11 +40001,11 @@ const noEffectChain = defineRule({
|
|
|
39389
40001
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39390
40002
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
39391
40003
|
const stateIdentifier = binding.declarator.id.elements[0];
|
|
40004
|
+
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39392
40005
|
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
39393
40006
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39394
40007
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39395
40008
|
}
|
|
39396
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39397
40009
|
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39398
40010
|
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39399
40011
|
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
@@ -39406,10 +40018,11 @@ const noEffectChain = defineRule({
|
|
|
39406
40018
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39407
40019
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39408
40020
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39409
|
-
const stateWrites = collectStateWritesInEffect(
|
|
40021
|
+
const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
|
|
39410
40022
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39411
40023
|
effectInfos.push({
|
|
39412
40024
|
node: effectCall,
|
|
40025
|
+
callback,
|
|
39413
40026
|
dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
|
|
39414
40027
|
stateWrites,
|
|
39415
40028
|
analysisFunctions,
|
|
@@ -39426,10 +40039,10 @@ const noEffectChain = defineRule({
|
|
|
39426
40039
|
if (readerEffect.isExternalSync) continue;
|
|
39427
40040
|
if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
|
|
39428
40041
|
let chainedStateName = null;
|
|
39429
|
-
for (const
|
|
40042
|
+
for (const writtenName of writerEffect.stateWrites.keys()) {
|
|
39430
40043
|
const writtenStateSymbolId = stateSymbolIds.get(writtenName);
|
|
39431
40044
|
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
39432
|
-
if (!canStateWriteReachReaderWork(
|
|
40045
|
+
if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
|
|
39433
40046
|
chainedStateName = writtenName;
|
|
39434
40047
|
break;
|
|
39435
40048
|
}
|
|
@@ -46603,6 +47216,103 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
46603
47216
|
"useVisibility",
|
|
46604
47217
|
"useWindowSize"
|
|
46605
47218
|
]);
|
|
47219
|
+
const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
|
|
47220
|
+
"useMatchMedia",
|
|
47221
|
+
"useMediaQuery",
|
|
47222
|
+
"useVisibility"
|
|
47223
|
+
]);
|
|
47224
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47225
|
+
const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
|
|
47226
|
+
const callee = stripParenExpression(rawCallee);
|
|
47227
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
47228
|
+
const calleeRef = getRef(analysis, callee);
|
|
47229
|
+
if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
|
|
47230
|
+
const importBinding = getImportBindingForName(callee, callee.name);
|
|
47231
|
+
if (!importBinding || importBinding.isNamespace) return null;
|
|
47232
|
+
if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
|
|
47233
|
+
return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
|
|
47234
|
+
}
|
|
47235
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47236
|
+
const hookName = getStaticMemberPropertyName(callee);
|
|
47237
|
+
const namespaceIdentifier = stripParenExpression(callee.object);
|
|
47238
|
+
if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
47239
|
+
const namespaceRef = getRef(analysis, namespaceIdentifier);
|
|
47240
|
+
if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
|
|
47241
|
+
return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
|
|
47242
|
+
};
|
|
47243
|
+
const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
|
|
47244
|
+
let current = bindingIdentifier;
|
|
47245
|
+
let didCrossDestructuringBoundary = false;
|
|
47246
|
+
while (current !== bindingPattern) {
|
|
47247
|
+
const parent = current.parent;
|
|
47248
|
+
if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
|
|
47249
|
+
if (isNodeOfType(parent, "Property")) {
|
|
47250
|
+
if (parent.value !== current) return false;
|
|
47251
|
+
didCrossDestructuringBoundary = true;
|
|
47252
|
+
}
|
|
47253
|
+
if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
|
|
47254
|
+
current = parent;
|
|
47255
|
+
}
|
|
47256
|
+
return didCrossDestructuringBoundary;
|
|
47257
|
+
};
|
|
47258
|
+
const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
|
|
47259
|
+
const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
47260
|
+
if (visitedVariables.has(variable)) return false;
|
|
47261
|
+
visitedVariables.add(variable);
|
|
47262
|
+
return variable.references.some((candidateReference) => {
|
|
47263
|
+
if (candidateReference.init) return false;
|
|
47264
|
+
if (candidateReference.isWrite()) return true;
|
|
47265
|
+
let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
|
|
47266
|
+
while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
|
|
47267
|
+
const usageParent = usageRoot.parent;
|
|
47268
|
+
if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
|
|
47269
|
+
let usageAncestor = usageRoot.parent;
|
|
47270
|
+
while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
|
|
47271
|
+
if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
|
|
47272
|
+
const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
|
|
47273
|
+
return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
|
|
47274
|
+
}
|
|
47275
|
+
return false;
|
|
47276
|
+
});
|
|
47277
|
+
};
|
|
47278
|
+
const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
|
|
47279
|
+
return variables.length > 0 && variables.every((variable) => {
|
|
47280
|
+
const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
|
|
47281
|
+
return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
|
|
47282
|
+
});
|
|
47283
|
+
};
|
|
47284
|
+
const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
|
|
47285
|
+
const callee = stripParenExpression(rawCallee);
|
|
47286
|
+
const callExpression = callee.parent;
|
|
47287
|
+
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
|
|
47288
|
+
const initializer = findTransparentExpressionRoot(callExpression);
|
|
47289
|
+
const declarator = initializer.parent;
|
|
47290
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47291
|
+
const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
|
|
47292
|
+
if (relatedVariables.length === 0) return false;
|
|
47293
|
+
return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
|
|
47294
|
+
};
|
|
47295
|
+
const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
|
|
47296
|
+
const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
|
|
47297
|
+
return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
|
|
47298
|
+
};
|
|
47299
|
+
const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
47300
|
+
const declarator = def.node;
|
|
47301
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47302
|
+
const initializer = stripParenExpression(declarator.init);
|
|
47303
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
47304
|
+
const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
|
|
47305
|
+
if (!hookName) return false;
|
|
47306
|
+
if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47307
|
+
const bindingIdentifier = def.name;
|
|
47308
|
+
return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47309
|
+
}));
|
|
47310
|
+
const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
|
|
47311
|
+
const unwrappedArgument = stripParenExpression(argument);
|
|
47312
|
+
if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
|
|
47313
|
+
const argumentRef = getRef(analysis, unwrappedArgument);
|
|
47314
|
+
return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
|
|
47315
|
+
};
|
|
46606
47316
|
const isCallbackPropReference = (analysis, ref) => {
|
|
46607
47317
|
if (!isProp(analysis, ref)) return false;
|
|
46608
47318
|
const identifier = ref.identifier;
|
|
@@ -46635,8 +47345,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46635
47345
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46636
47346
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
46637
47347
|
};
|
|
46638
|
-
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47348
|
+
const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
46639
47349
|
let hookFunction = resolveToFunction(ref);
|
|
47350
|
+
let didResolveThroughResultBinding = false;
|
|
46640
47351
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
46641
47352
|
const definitionNode = definition.node;
|
|
46642
47353
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -46647,29 +47358,27 @@ const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
|
46647
47358
|
const calleeReference = getRef(analysis, callee);
|
|
46648
47359
|
if (!calleeReference) continue;
|
|
46649
47360
|
hookFunction = resolveToFunction(calleeReference);
|
|
46650
|
-
if (hookFunction)
|
|
47361
|
+
if (hookFunction) {
|
|
47362
|
+
didResolveThroughResultBinding = true;
|
|
47363
|
+
break;
|
|
47364
|
+
}
|
|
46651
47365
|
}
|
|
46652
47366
|
if (!hookFunction) return null;
|
|
46653
|
-
|
|
47367
|
+
if (didResolveThroughResultBinding && (!ref.resolved || hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved))) return false;
|
|
47368
|
+
if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
|
|
47369
|
+
const returnStatements = collectFunctionReturnStatements(hookFunction);
|
|
47370
|
+
if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
|
|
47371
|
+
const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46654
47372
|
if (returnedReferences.length === 0) return null;
|
|
46655
47373
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
46656
47374
|
};
|
|
46657
|
-
const
|
|
46658
|
-
const
|
|
46659
|
-
if (!isNodeOfType(
|
|
46660
|
-
const
|
|
46661
|
-
if (
|
|
46662
|
-
|
|
46663
|
-
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46664
|
-
const node = def.node;
|
|
46665
|
-
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46666
|
-
const initializer = stripParenExpression(node.init);
|
|
46667
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46668
|
-
const callee = stripParenExpression(initializer.callee);
|
|
46669
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
46670
|
-
}));
|
|
47375
|
+
const isImmutableLocalExternalStoreHookCallee = (analysis, rawCallee, relatedRefs, scopes) => {
|
|
47376
|
+
const callee = stripParenExpression(rawCallee);
|
|
47377
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
47378
|
+
const calleeReference = getRef(analysis, callee);
|
|
47379
|
+
if (!calleeReference || getLocalHookExternalStateProof(analysis, calleeReference, scopes) !== true) return false;
|
|
47380
|
+
return hasImmutableExternalSubscriptionCallResult(analysis, callee, relatedRefs, true);
|
|
46671
47381
|
};
|
|
46672
|
-
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
46673
47382
|
const isCalleePosition = (identifier) => {
|
|
46674
47383
|
const parent = identifier.parent;
|
|
46675
47384
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -46730,25 +47439,35 @@ const noPassDataToParent = defineRule({
|
|
|
46730
47439
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
46731
47440
|
}
|
|
46732
47441
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47442
|
+
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
46733
47443
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
46734
47444
|
if (isNodeOfType(argument, "Identifier")) {
|
|
46735
47445
|
const argumentRef = getRef(analysis, argument);
|
|
46736
47446
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46737
47447
|
}
|
|
46738
47448
|
return getDownstreamRefs(analysis, argument);
|
|
46739
|
-
}).flatMap((argumentRef) =>
|
|
46740
|
-
|
|
47449
|
+
}).flatMap((argumentRef) => {
|
|
47450
|
+
if (isExternallyDrivenState(analysis, argumentRef) || getLocalHookExternalStateProof(analysis, argumentRef, context.scopes) === true) return [];
|
|
47451
|
+
const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
|
|
47452
|
+
return upstreamRefs.filter((upstreamRef) => {
|
|
47453
|
+
if (!isLeafRef(upstreamRef)) return false;
|
|
47454
|
+
return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
|
|
47455
|
+
});
|
|
47456
|
+
});
|
|
47457
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
|
|
47458
|
+
const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
|
|
47459
|
+
argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
|
|
47460
|
+
}
|
|
46741
47461
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46742
|
-
|
|
46743
|
-
if (
|
|
47462
|
+
const argIdentifier = argRef.identifier;
|
|
47463
|
+
if (isUseStateIdentifier(argIdentifier)) return false;
|
|
46744
47464
|
if (isProp(analysis, argRef)) return false;
|
|
46745
|
-
if (isUseRefIdentifier(
|
|
47465
|
+
if (isUseRefIdentifier(argIdentifier)) return false;
|
|
46746
47466
|
if (isRefCurrent(argRef)) return false;
|
|
46747
47467
|
if (isConstant(argRef)) return false;
|
|
46748
47468
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
46749
47469
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
46750
47470
|
if (resolveToFunction(argRef)) return false;
|
|
46751
|
-
const argIdentifier = argRef.identifier;
|
|
46752
47471
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
46753
47472
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
46754
47473
|
return true;
|
|
@@ -55739,17 +56458,6 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
55739
56458
|
}
|
|
55740
56459
|
});
|
|
55741
56460
|
//#endregion
|
|
55742
|
-
//#region src/plugin/utils/get-require-call-source.ts
|
|
55743
|
-
const getRequireCallSource = (expression) => {
|
|
55744
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
55745
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
55746
|
-
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
55747
|
-
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
55748
|
-
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
55749
|
-
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55750
|
-
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55751
|
-
};
|
|
55752
|
-
//#endregion
|
|
55753
56461
|
//#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
|
|
55754
56462
|
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
55755
56463
|
const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
|
|
@@ -59076,7 +59784,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
59076
59784
|
const isRepositorySecretFilePath = (relativePath) => DOTENV_FILE_PATTERN.test(relativePath) || /(?:^|\/)\.npmrc$/.test(relativePath) || /(?:^|\/)[^/]*(?:credential|credentials|service-account|serviceAccount|firebase-admin|google-service-account|gcp-service-account)[^/]*\.(?:json|env|pem|key)$/i.test(relativePath);
|
|
59077
59785
|
//#endregion
|
|
59078
59786
|
//#region src/plugin/rules/security-scan/repository-secret-file.ts
|
|
59079
|
-
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env
|
|
59787
|
+
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(?:\.[^./]+)*\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
|
|
59080
59788
|
const repositorySecretFile = defineRule({
|
|
59081
59789
|
id: "repository-secret-file",
|
|
59082
59790
|
title: "Secret file checked into repository",
|
|
@@ -60457,6 +61165,717 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
60457
61165
|
return found;
|
|
60458
61166
|
};
|
|
60459
61167
|
//#endregion
|
|
61168
|
+
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
61169
|
+
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
61170
|
+
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
61171
|
+
const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61172
|
+
"assign",
|
|
61173
|
+
"defineProperties",
|
|
61174
|
+
"defineProperty",
|
|
61175
|
+
"setPrototypeOf"
|
|
61176
|
+
]);
|
|
61177
|
+
const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61178
|
+
"defineProperty",
|
|
61179
|
+
"deleteProperty",
|
|
61180
|
+
"set",
|
|
61181
|
+
"setPrototypeOf"
|
|
61182
|
+
]);
|
|
61183
|
+
const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
|
|
61184
|
+
const containsGlobalLocationSnapshotRead = (node, scopes) => {
|
|
61185
|
+
let didFindLocationSnapshotRead = false;
|
|
61186
|
+
walkAst(node, (child) => {
|
|
61187
|
+
if (didFindLocationSnapshotRead) return false;
|
|
61188
|
+
if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61189
|
+
if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
|
|
61190
|
+
didFindLocationSnapshotRead = true;
|
|
61191
|
+
return false;
|
|
61192
|
+
});
|
|
61193
|
+
return didFindLocationSnapshotRead;
|
|
61194
|
+
};
|
|
61195
|
+
const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
|
|
61196
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
61197
|
+
if (isFunctionLike$1(localFunction)) return localFunction;
|
|
61198
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
61199
|
+
let callbackSource = unwrappedExpression;
|
|
61200
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
61201
|
+
const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
|
|
61202
|
+
if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
|
|
61203
|
+
callbackSource = callbackSymbol.initializer;
|
|
61204
|
+
}
|
|
61205
|
+
const unwrappedCallbackSource = stripParenExpression(callbackSource);
|
|
61206
|
+
if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
|
|
61207
|
+
const callback = unwrappedCallbackSource.arguments?.[0];
|
|
61208
|
+
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
61209
|
+
return resolveExactLocalFunction(stripParenExpression(callback), scopes);
|
|
61210
|
+
};
|
|
61211
|
+
const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61212
|
+
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
61213
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
61214
|
+
if ((symbol.kind === "function" ? scopes.scopeFor(symbol.declarationNode) : symbol.scope).symbols.some((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.references.some((reference) => reference.flag !== "read"))) return false;
|
|
61215
|
+
const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
|
|
61216
|
+
const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
|
|
61217
|
+
if (!initializer) return false;
|
|
61218
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61219
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
61220
|
+
if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
|
|
61221
|
+
let didFindAliasedLocationSnapshotRead = false;
|
|
61222
|
+
walkAst(initializer, (child) => {
|
|
61223
|
+
if (didFindAliasedLocationSnapshotRead) return false;
|
|
61224
|
+
if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61225
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
61226
|
+
if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
|
|
61227
|
+
didFindAliasedLocationSnapshotRead = true;
|
|
61228
|
+
return false;
|
|
61229
|
+
}
|
|
61230
|
+
});
|
|
61231
|
+
return didFindAliasedLocationSnapshotRead;
|
|
61232
|
+
};
|
|
61233
|
+
const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
|
|
61234
|
+
if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
|
|
61235
|
+
for (const statement of componentBody.body ?? []) {
|
|
61236
|
+
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
|
|
61237
|
+
if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
|
|
61238
|
+
continue;
|
|
61239
|
+
}
|
|
61240
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
61241
|
+
for (const declarator of statement.declarations ?? []) {
|
|
61242
|
+
if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
61243
|
+
if (!directRenderNames.has(declarator.id.name)) continue;
|
|
61244
|
+
if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
|
|
61245
|
+
}
|
|
61246
|
+
}
|
|
61247
|
+
return false;
|
|
61248
|
+
};
|
|
61249
|
+
const isGlobalHistoryLocationMutation = (node, scopes) => {
|
|
61250
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
61251
|
+
const callee = stripParenExpression(node.callee);
|
|
61252
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61253
|
+
const methodName = getStaticPropertyName(callee);
|
|
61254
|
+
return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
|
|
61255
|
+
};
|
|
61256
|
+
const getStaticLocationChangeEvent = (node) => {
|
|
61257
|
+
if (!node) return null;
|
|
61258
|
+
const eventNameNode = stripParenExpression(node);
|
|
61259
|
+
if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
|
|
61260
|
+
return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
|
|
61261
|
+
};
|
|
61262
|
+
const addToSetIndex = (index, key, value) => {
|
|
61263
|
+
const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
61264
|
+
indexedValues.add(value);
|
|
61265
|
+
index.set(key, indexedValues);
|
|
61266
|
+
};
|
|
61267
|
+
const getSynchronousInvocationExpression = (functionNode, scopes) => {
|
|
61268
|
+
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61269
|
+
if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
|
|
61270
|
+
const parent = functionExpressionRoot.parent;
|
|
61271
|
+
return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
|
|
61272
|
+
};
|
|
61273
|
+
const getLocationListenerOperation = (callExpression, scopes) => {
|
|
61274
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61275
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
61276
|
+
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
61277
|
+
if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
|
|
61278
|
+
if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
|
|
61279
|
+
const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
|
|
61280
|
+
if (!eventName) return null;
|
|
61281
|
+
const listenerExpression = callExpression.arguments?.[1];
|
|
61282
|
+
if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
|
|
61283
|
+
const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
|
|
61284
|
+
if (!isFunctionLike$1(listenerFunction)) return null;
|
|
61285
|
+
const captureArgument = callExpression.arguments?.[2];
|
|
61286
|
+
const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
|
|
61287
|
+
allowComputedString: true,
|
|
61288
|
+
allowIndeterminateEntries: true
|
|
61289
|
+
});
|
|
61290
|
+
return {
|
|
61291
|
+
operation: methodName === "addEventListener" ? "add" : "remove",
|
|
61292
|
+
registration: {
|
|
61293
|
+
callExpression,
|
|
61294
|
+
listenerFunction,
|
|
61295
|
+
capture,
|
|
61296
|
+
eventName
|
|
61297
|
+
}
|
|
61298
|
+
};
|
|
61299
|
+
};
|
|
61300
|
+
const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
|
|
61301
|
+
const index = {
|
|
61302
|
+
componentFunction,
|
|
61303
|
+
context,
|
|
61304
|
+
effectCallbacks: /* @__PURE__ */ new Set(),
|
|
61305
|
+
expressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61306
|
+
historyMutationsByOwner: /* @__PURE__ */ new Map(),
|
|
61307
|
+
awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61308
|
+
callSitesByFunction: /* @__PURE__ */ new Map(),
|
|
61309
|
+
calledFunctionByExpression: /* @__PURE__ */ new Map(),
|
|
61310
|
+
synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
|
|
61311
|
+
synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
|
|
61312
|
+
callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
|
|
61313
|
+
identifierCalls: /* @__PURE__ */ new Set(),
|
|
61314
|
+
listenerRegistrations: [],
|
|
61315
|
+
listenerRemovals: [],
|
|
61316
|
+
mountedListenerFunctions: /* @__PURE__ */ new Set(),
|
|
61317
|
+
mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
|
|
61318
|
+
synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
|
|
61319
|
+
};
|
|
61320
|
+
walkAst(componentBody, (child) => {
|
|
61321
|
+
if (isFunctionLike$1(child)) {
|
|
61322
|
+
const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
|
|
61323
|
+
if (invocationExpression) {
|
|
61324
|
+
addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
|
|
61325
|
+
addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
|
|
61326
|
+
}
|
|
61327
|
+
return;
|
|
61328
|
+
}
|
|
61329
|
+
const owner = context.cfg.enclosingFunction(child);
|
|
61330
|
+
if (!owner) return;
|
|
61331
|
+
if (isNodeOfType(child, "AwaitExpression")) {
|
|
61332
|
+
addToSetIndex(index.awaitExpressionsByOwner, owner, child);
|
|
61333
|
+
return;
|
|
61334
|
+
}
|
|
61335
|
+
if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
|
|
61336
|
+
addToSetIndex(index.expressionsByOwner, owner, child);
|
|
61337
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
61338
|
+
if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
|
|
61339
|
+
if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
|
|
61340
|
+
const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
|
|
61341
|
+
if (isFunctionLike$1(calledFunction)) {
|
|
61342
|
+
addToSetIndex(index.callSitesByFunction, calledFunction, child);
|
|
61343
|
+
index.calledFunctionByExpression.set(child, calledFunction);
|
|
61344
|
+
}
|
|
61345
|
+
if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
61346
|
+
allowGlobalReactNamespace: true,
|
|
61347
|
+
allowUnboundBareCalls: true,
|
|
61348
|
+
resolveNamedAliases: true
|
|
61349
|
+
})) {
|
|
61350
|
+
const effectCallback = getEffectCallback(child, context.scopes);
|
|
61351
|
+
if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
|
|
61352
|
+
}
|
|
61353
|
+
const listenerOperation = getLocationListenerOperation(child, context.scopes);
|
|
61354
|
+
if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
|
|
61355
|
+
else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
|
|
61356
|
+
}
|
|
61357
|
+
});
|
|
61358
|
+
return index;
|
|
61359
|
+
};
|
|
61360
|
+
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61361
|
+
let current = descendant;
|
|
61362
|
+
while (current && current !== ancestor) {
|
|
61363
|
+
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61364
|
+
current = current.parent;
|
|
61365
|
+
}
|
|
61366
|
+
return current === ancestor;
|
|
61367
|
+
};
|
|
61368
|
+
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
61369
|
+
const firstBranches = /* @__PURE__ */ new Map();
|
|
61370
|
+
let current = firstNode;
|
|
61371
|
+
while (current?.parent) {
|
|
61372
|
+
const parent = current.parent;
|
|
61373
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
|
|
61374
|
+
if (current !== firstNode && isFunctionLike$1(current)) break;
|
|
61375
|
+
current = parent;
|
|
61376
|
+
}
|
|
61377
|
+
current = secondNode;
|
|
61378
|
+
while (current?.parent) {
|
|
61379
|
+
const parent = current.parent;
|
|
61380
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
|
|
61381
|
+
const firstBranch = firstBranches.get(parent);
|
|
61382
|
+
if (firstBranch && firstBranch !== current) return true;
|
|
61383
|
+
}
|
|
61384
|
+
if (current !== secondNode && isFunctionLike$1(current)) break;
|
|
61385
|
+
current = parent;
|
|
61386
|
+
}
|
|
61387
|
+
return false;
|
|
61388
|
+
};
|
|
61389
|
+
const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
|
|
61390
|
+
const constraintsBySymbolId = /* @__PURE__ */ new Map();
|
|
61391
|
+
const recordConstraint = (test, requiredTestValue) => {
|
|
61392
|
+
let expression = stripParenExpression(test);
|
|
61393
|
+
let requiredValue = requiredTestValue;
|
|
61394
|
+
while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
61395
|
+
requiredValue = !requiredValue;
|
|
61396
|
+
expression = stripParenExpression(expression.argument);
|
|
61397
|
+
}
|
|
61398
|
+
if (!isNodeOfType(expression, "Identifier")) return;
|
|
61399
|
+
const symbol = scopes.symbolFor(expression);
|
|
61400
|
+
if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
61401
|
+
const existingConstraint = constraintsBySymbolId.get(symbol.id);
|
|
61402
|
+
constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
|
|
61403
|
+
};
|
|
61404
|
+
let current = node;
|
|
61405
|
+
while (current?.parent && current !== functionBoundary) {
|
|
61406
|
+
const parent = current.parent;
|
|
61407
|
+
if (isNodeOfType(parent, "IfStatement")) {
|
|
61408
|
+
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61409
|
+
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61410
|
+
} else if (isNodeOfType(parent, "ConditionalExpression")) {
|
|
61411
|
+
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61412
|
+
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61413
|
+
} else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
|
|
61414
|
+
if (parent.operator === "&&") recordConstraint(parent.left, true);
|
|
61415
|
+
if (parent.operator === "||") recordConstraint(parent.left, false);
|
|
61416
|
+
}
|
|
61417
|
+
if (current !== node && isFunctionLike$1(current)) break;
|
|
61418
|
+
current = parent;
|
|
61419
|
+
}
|
|
61420
|
+
return constraintsBySymbolId;
|
|
61421
|
+
};
|
|
61422
|
+
const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
|
|
61423
|
+
const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
|
|
61424
|
+
const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
|
|
61425
|
+
for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
|
|
61426
|
+
for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
|
|
61427
|
+
for (const [symbolId, firstRequiredValue] of firstConstraints) {
|
|
61428
|
+
const secondRequiredValue = secondConstraints.get(symbolId);
|
|
61429
|
+
if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
|
|
61430
|
+
}
|
|
61431
|
+
return false;
|
|
61432
|
+
};
|
|
61433
|
+
const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
|
|
61434
|
+
let current = node;
|
|
61435
|
+
while (current && current !== functionBoundary) {
|
|
61436
|
+
if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
|
|
61437
|
+
current = current.parent;
|
|
61438
|
+
}
|
|
61439
|
+
return false;
|
|
61440
|
+
};
|
|
61441
|
+
const getTransparentExpressionBindingIdentifier = (expression) => {
|
|
61442
|
+
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
61443
|
+
const parent = expressionRoot.parent;
|
|
61444
|
+
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
61445
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
|
|
61446
|
+
return null;
|
|
61447
|
+
};
|
|
61448
|
+
const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
|
|
61449
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
61450
|
+
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61451
|
+
const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
|
|
61452
|
+
if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
|
|
61453
|
+
const callbackCall = functionExpressionRoot.parent;
|
|
61454
|
+
if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61455
|
+
return getTransparentExpressionBindingIdentifier(callbackCall);
|
|
61456
|
+
};
|
|
61457
|
+
const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
|
|
61458
|
+
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61459
|
+
if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
|
|
61460
|
+
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61461
|
+
return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
|
|
61462
|
+
};
|
|
61463
|
+
const canNodeReachNode = (sourceNode, targetNode, index) => {
|
|
61464
|
+
const { context } = index;
|
|
61465
|
+
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
61466
|
+
if (!isNodeReachableWithinFunction(targetNode, context)) return false;
|
|
61467
|
+
const sourceOwner = context.cfg.enclosingFunction(sourceNode);
|
|
61468
|
+
const targetOwner = context.cfg.enclosingFunction(targetNode);
|
|
61469
|
+
if (!sourceOwner || sourceOwner !== targetOwner) return false;
|
|
61470
|
+
if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
|
|
61471
|
+
if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
|
|
61472
|
+
if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
|
|
61473
|
+
if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
|
|
61474
|
+
const functionCfg = context.cfg.cfgFor(sourceOwner);
|
|
61475
|
+
const sourceBlock = functionCfg?.blockOf(sourceNode);
|
|
61476
|
+
const targetBlock = functionCfg?.blockOf(targetNode);
|
|
61477
|
+
if (!functionCfg || !sourceBlock || !targetBlock) return false;
|
|
61478
|
+
if (sourceBlock === targetBlock) {
|
|
61479
|
+
const sourceStart = getRangeStart(sourceNode);
|
|
61480
|
+
const targetStart = getRangeStart(targetNode);
|
|
61481
|
+
return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
|
|
61482
|
+
}
|
|
61483
|
+
const visitedBlocks = new Set([sourceBlock]);
|
|
61484
|
+
const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
|
|
61485
|
+
while (pendingBlocks.length > 0) {
|
|
61486
|
+
const block = pendingBlocks.pop();
|
|
61487
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61488
|
+
if (block === targetBlock) return true;
|
|
61489
|
+
visitedBlocks.add(block);
|
|
61490
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61491
|
+
}
|
|
61492
|
+
return false;
|
|
61493
|
+
};
|
|
61494
|
+
const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
|
|
61495
|
+
let currentNode = functionNode;
|
|
61496
|
+
while (currentNode) {
|
|
61497
|
+
if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
|
|
61498
|
+
const openingElement = currentNode.parent;
|
|
61499
|
+
return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
|
|
61500
|
+
}
|
|
61501
|
+
if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
|
|
61502
|
+
currentNode = currentNode.parent;
|
|
61503
|
+
}
|
|
61504
|
+
return null;
|
|
61505
|
+
};
|
|
61506
|
+
const isProvenNonEscapingVoidRead = (identifier) => {
|
|
61507
|
+
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
61508
|
+
return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
|
|
61509
|
+
};
|
|
61510
|
+
const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
|
|
61511
|
+
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61512
|
+
const parent = referenceRoot.parent;
|
|
61513
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
|
|
61514
|
+
const aliasSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61515
|
+
const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
|
|
61516
|
+
const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
|
|
61517
|
+
return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
|
|
61518
|
+
}
|
|
61519
|
+
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61520
|
+
const callbackResultRoot = findTransparentExpressionRoot(parent);
|
|
61521
|
+
const resultParent = callbackResultRoot.parent;
|
|
61522
|
+
if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
|
|
61523
|
+
const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
|
|
61524
|
+
return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
|
|
61525
|
+
};
|
|
61526
|
+
const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
|
|
61527
|
+
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61528
|
+
const parent = referenceRoot.parent;
|
|
61529
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
|
|
61530
|
+
const memberRoot = findTransparentExpressionRoot(parent);
|
|
61531
|
+
const callExpression = memberRoot.parent;
|
|
61532
|
+
const methodName = getStaticPropertyName(parent);
|
|
61533
|
+
if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
|
|
61534
|
+
}
|
|
61535
|
+
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
|
|
61536
|
+
const callee = stripParenExpression(parent.callee);
|
|
61537
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61538
|
+
const methodName = getStaticPropertyName(callee);
|
|
61539
|
+
if (methodName === null) return false;
|
|
61540
|
+
return OBJECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Object", index.context.scopes) || REFLECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Reflect", index.context.scopes);
|
|
61541
|
+
};
|
|
61542
|
+
const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61543
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61544
|
+
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
|
|
61545
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61546
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
61547
|
+
const escapeAnchors = [];
|
|
61548
|
+
for (const reference of symbol.references) {
|
|
61549
|
+
if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
|
|
61550
|
+
const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
|
|
61551
|
+
if (!aliasSymbol) {
|
|
61552
|
+
escapeAnchors.push(reference.identifier);
|
|
61553
|
+
continue;
|
|
61554
|
+
}
|
|
61555
|
+
const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
|
|
61556
|
+
if (!aliasEscapeAnchors) return null;
|
|
61557
|
+
escapeAnchors.push(...aliasEscapeAnchors);
|
|
61558
|
+
}
|
|
61559
|
+
return escapeAnchors.length > 0 ? escapeAnchors : null;
|
|
61560
|
+
};
|
|
61561
|
+
const findReadonlyJsxAggregateRoot = (jsxElement) => {
|
|
61562
|
+
let aggregateRoot = findTransparentExpressionRoot(jsxElement);
|
|
61563
|
+
while (aggregateRoot.parent) {
|
|
61564
|
+
const parent = aggregateRoot.parent;
|
|
61565
|
+
if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
|
|
61566
|
+
aggregateRoot = parent;
|
|
61567
|
+
continue;
|
|
61568
|
+
}
|
|
61569
|
+
if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
|
|
61570
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61571
|
+
continue;
|
|
61572
|
+
}
|
|
61573
|
+
if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
|
|
61574
|
+
aggregateRoot = findTransparentExpressionRoot(parent.parent);
|
|
61575
|
+
continue;
|
|
61576
|
+
}
|
|
61577
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
|
|
61578
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61579
|
+
continue;
|
|
61580
|
+
}
|
|
61581
|
+
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
|
|
61582
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61583
|
+
continue;
|
|
61584
|
+
}
|
|
61585
|
+
if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
|
|
61586
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61587
|
+
continue;
|
|
61588
|
+
}
|
|
61589
|
+
break;
|
|
61590
|
+
}
|
|
61591
|
+
return aggregateRoot;
|
|
61592
|
+
};
|
|
61593
|
+
const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
|
|
61594
|
+
const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
|
|
61595
|
+
const parent = jsxValueRoot.parent;
|
|
61596
|
+
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
|
|
61597
|
+
if (!isNodeOfType(parent.id, "Identifier")) return null;
|
|
61598
|
+
const bindingSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61599
|
+
if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
|
|
61600
|
+
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
|
|
61601
|
+
};
|
|
61602
|
+
const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
|
|
61603
|
+
if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
|
|
61604
|
+
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61605
|
+
if (!bindingIdentifier) {
|
|
61606
|
+
const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
|
|
61607
|
+
return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
|
|
61608
|
+
}
|
|
61609
|
+
return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
|
|
61610
|
+
};
|
|
61611
|
+
const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
|
|
61612
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
61613
|
+
if (!bindingIdentifier) return null;
|
|
61614
|
+
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61615
|
+
if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
|
|
61616
|
+
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
|
|
61617
|
+
};
|
|
61618
|
+
const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
61619
|
+
if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
|
|
61620
|
+
const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
|
|
61621
|
+
const executionOwner = index.context.cfg.enclosingFunction(executionNode);
|
|
61622
|
+
if (!aliasOwner || !executionOwner) return false;
|
|
61623
|
+
if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
|
|
61624
|
+
if (visitedFunctionNodes.has(executionOwner)) return false;
|
|
61625
|
+
const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
|
|
61626
|
+
nextVisitedFunctionNodes.add(executionOwner);
|
|
61627
|
+
if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
|
|
61628
|
+
const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
|
|
61629
|
+
if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
|
|
61630
|
+
const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
|
|
61631
|
+
if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
|
|
61632
|
+
const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
|
|
61633
|
+
return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
|
|
61634
|
+
};
|
|
61635
|
+
const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
|
|
61636
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61637
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
61638
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
61639
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
61640
|
+
let symbol = index.context.scopes.symbolFor(callee);
|
|
61641
|
+
while (symbol) {
|
|
61642
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61643
|
+
visitedSymbolIds.add(symbol.id);
|
|
61644
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
61645
|
+
if (!initializer) return symbol.id;
|
|
61646
|
+
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
|
|
61647
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
61648
|
+
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
|
|
61649
|
+
const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
|
|
61650
|
+
if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
|
|
61651
|
+
symbol = initializerSymbol;
|
|
61652
|
+
}
|
|
61653
|
+
return null;
|
|
61654
|
+
};
|
|
61655
|
+
const collectExactReadonlyCalleeCalls = (index) => {
|
|
61656
|
+
for (const callExpression of index.identifierCalls) {
|
|
61657
|
+
const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
|
|
61658
|
+
if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
|
|
61659
|
+
}
|
|
61660
|
+
};
|
|
61661
|
+
const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
61662
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61663
|
+
const sourceBlock = functionCfg?.blockOf(node);
|
|
61664
|
+
if (!functionCfg || !sourceBlock) return false;
|
|
61665
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61666
|
+
const pendingBlocks = [sourceBlock];
|
|
61667
|
+
while (pendingBlocks.length > 0) {
|
|
61668
|
+
const block = pendingBlocks.pop();
|
|
61669
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61670
|
+
visitedBlocks.add(block);
|
|
61671
|
+
for (const edge of block.successors) {
|
|
61672
|
+
if (edge.kind === "throw") continue;
|
|
61673
|
+
if (edge.to === functionCfg.exit) return true;
|
|
61674
|
+
pendingBlocks.push(edge.to);
|
|
61675
|
+
}
|
|
61676
|
+
}
|
|
61677
|
+
return false;
|
|
61678
|
+
};
|
|
61679
|
+
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
61680
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
61681
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61682
|
+
const targetBlock = functionCfg?.blockOf(node);
|
|
61683
|
+
if (!functionCfg || !targetBlock) return false;
|
|
61684
|
+
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
61685
|
+
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
61686
|
+
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
61687
|
+
if (!awaitBlock) continue;
|
|
61688
|
+
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
61689
|
+
blockAwaits.push(awaitExpression);
|
|
61690
|
+
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
61691
|
+
}
|
|
61692
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61693
|
+
const pendingBlocks = [functionCfg.entry];
|
|
61694
|
+
const targetStart = getRangeStart(node);
|
|
61695
|
+
while (pendingBlocks.length > 0) {
|
|
61696
|
+
const block = pendingBlocks.pop();
|
|
61697
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61698
|
+
visitedBlocks.add(block);
|
|
61699
|
+
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
61700
|
+
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
61701
|
+
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
61702
|
+
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
61703
|
+
const awaitStart = getRangeStart(awaitExpression);
|
|
61704
|
+
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
61705
|
+
});
|
|
61706
|
+
if (blockAwaits.length > 0) continue;
|
|
61707
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61708
|
+
}
|
|
61709
|
+
return false;
|
|
61710
|
+
};
|
|
61711
|
+
const canReactBatchMutationAfterExecution = (executionNode, mutationNode, owner, index) => (isExclusiveIntrinsicReactEventHandler(owner, index) || index.effectCallbacks.has(owner)) && canNodeReachNode(executionNode, mutationNode, index) && canExecuteBeforeAsyncSuspension(mutationNode, owner, index) && canNodeReachNormalFunctionExit(mutationNode, owner, index);
|
|
61712
|
+
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
61713
|
+
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
61714
|
+
if (cachedResult !== void 0) return cachedResult;
|
|
61715
|
+
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61716
|
+
if (visitingFunctions.has(functionNode)) {
|
|
61717
|
+
let didReachCycleEntry = false;
|
|
61718
|
+
for (const visitingFunction of visitingFunctions) {
|
|
61719
|
+
if (visitingFunction === functionNode) didReachCycleEntry = true;
|
|
61720
|
+
if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
|
|
61721
|
+
}
|
|
61722
|
+
return false;
|
|
61723
|
+
}
|
|
61724
|
+
visitingFunctions.add(functionNode);
|
|
61725
|
+
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
61726
|
+
visitingFunctions.delete(functionNode);
|
|
61727
|
+
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
61728
|
+
return doesMutateSynchronously;
|
|
61729
|
+
};
|
|
61730
|
+
const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
|
|
61731
|
+
const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
|
|
61732
|
+
if (cachedExecutions) return cachedExecutions;
|
|
61733
|
+
const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
|
|
61734
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61735
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61736
|
+
if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61737
|
+
for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61738
|
+
}
|
|
61739
|
+
if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
|
|
61740
|
+
return mutationExecutions;
|
|
61741
|
+
};
|
|
61742
|
+
const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
|
|
61743
|
+
const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
|
|
61744
|
+
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61745
|
+
if (visitingFunctions.has(functionNode)) return false;
|
|
61746
|
+
const nextVisitingFunctions = new Set(visitingFunctions);
|
|
61747
|
+
nextVisitingFunctions.add(functionNode);
|
|
61748
|
+
const removalExecutions = [];
|
|
61749
|
+
for (const removal of index.listenerRemovals) {
|
|
61750
|
+
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
61751
|
+
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
61752
|
+
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
61753
|
+
}
|
|
61754
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61755
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61756
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
61757
|
+
}
|
|
61758
|
+
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
61759
|
+
};
|
|
61760
|
+
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
61761
|
+
const removalExecutions = /* @__PURE__ */ new Set();
|
|
61762
|
+
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.add(removal.callExpression);
|
|
61763
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61764
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61765
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
61766
|
+
}
|
|
61767
|
+
return removalExecutions;
|
|
61768
|
+
};
|
|
61769
|
+
const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
|
|
61770
|
+
const expressionBoundaries = [];
|
|
61771
|
+
let currentChild = node;
|
|
61772
|
+
let currentParent = currentChild.parent ?? null;
|
|
61773
|
+
while (currentParent && currentParent !== owner) {
|
|
61774
|
+
if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
|
|
61775
|
+
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
61776
|
+
if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
|
|
61777
|
+
}
|
|
61778
|
+
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
|
|
61779
|
+
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
61780
|
+
if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
|
|
61781
|
+
}
|
|
61782
|
+
if (isNodeOfType(currentParent, "ConditionalExpression") && (currentParent.consequent === currentChild || currentParent.alternate === currentChild) || isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild || isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) expressionBoundaries.push(currentChild);
|
|
61783
|
+
currentChild = currentParent;
|
|
61784
|
+
currentParent = currentChild.parent ?? null;
|
|
61785
|
+
}
|
|
61786
|
+
return expressionBoundaries;
|
|
61787
|
+
};
|
|
61788
|
+
const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
|
|
61789
|
+
if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
|
|
61790
|
+
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61791
|
+
if (!owner) return false;
|
|
61792
|
+
const functionCfg = index.context.cfg.cfgFor(owner);
|
|
61793
|
+
const sourceBlock = functionCfg?.blockOf(executionNode);
|
|
61794
|
+
if (!functionCfg || !sourceBlock) return false;
|
|
61795
|
+
const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
|
|
61796
|
+
const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
|
|
61797
|
+
for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
|
|
61798
|
+
const removalBlock = functionCfg.blockOf(removalExecution);
|
|
61799
|
+
if (!removalBlock) continue;
|
|
61800
|
+
const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
|
|
61801
|
+
blockRemovals.push(removalExecution);
|
|
61802
|
+
matchingRemovalsByBlock.set(removalBlock, blockRemovals);
|
|
61803
|
+
}
|
|
61804
|
+
const sourceStart = getRangeStart(executionNode);
|
|
61805
|
+
const hasRemovalAfterExecution = (block) => {
|
|
61806
|
+
const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
|
|
61807
|
+
if (block !== sourceBlock) return true;
|
|
61808
|
+
const removalStart = getRangeStart(removal);
|
|
61809
|
+
return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
|
|
61810
|
+
});
|
|
61811
|
+
if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
|
|
61812
|
+
if (block !== sourceBlock) return false;
|
|
61813
|
+
return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
|
|
61814
|
+
};
|
|
61815
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61816
|
+
const pendingBlocks = [sourceBlock];
|
|
61817
|
+
while (pendingBlocks.length > 0) {
|
|
61818
|
+
const block = pendingBlocks.pop();
|
|
61819
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61820
|
+
visitedBlocks.add(block);
|
|
61821
|
+
if (hasRemovalAfterExecution(block)) continue;
|
|
61822
|
+
for (const edge of block.successors) {
|
|
61823
|
+
if (edge.kind === "throw") continue;
|
|
61824
|
+
if (edge.to === functionCfg.exit) return true;
|
|
61825
|
+
pendingBlocks.push(edge.to);
|
|
61826
|
+
}
|
|
61827
|
+
}
|
|
61828
|
+
return false;
|
|
61829
|
+
};
|
|
61830
|
+
const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61831
|
+
if (visitedExecutions.has(executionNode)) return false;
|
|
61832
|
+
if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
|
|
61833
|
+
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61834
|
+
if (!owner) return false;
|
|
61835
|
+
if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
|
|
61836
|
+
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61837
|
+
nextVisitedExecutions.add(executionNode);
|
|
61838
|
+
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
|
|
61839
|
+
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
|
|
61840
|
+
return false;
|
|
61841
|
+
};
|
|
61842
|
+
const collectMountedListenerFunctions = (index) => {
|
|
61843
|
+
for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
|
|
61844
|
+
};
|
|
61845
|
+
const setterArgumentMutatesLocation = (setterCall, index) => {
|
|
61846
|
+
if (!isNodeOfType(setterCall, "CallExpression")) return false;
|
|
61847
|
+
return (setterCall.arguments ?? []).some((argument) => {
|
|
61848
|
+
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
61849
|
+
const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
|
|
61850
|
+
return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
|
|
61851
|
+
});
|
|
61852
|
+
};
|
|
61853
|
+
const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61854
|
+
if (visitedExecutions.has(executionAnchor)) return false;
|
|
61855
|
+
if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
|
|
61856
|
+
const owner = index.context.cfg.enclosingFunction(executionAnchor);
|
|
61857
|
+
if (!owner) return false;
|
|
61858
|
+
if (index.mountedListenerFunctions.has(owner)) return true;
|
|
61859
|
+
if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
|
|
61860
|
+
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61861
|
+
nextVisitedExecutions.add(executionAnchor);
|
|
61862
|
+
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
|
|
61863
|
+
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
|
|
61864
|
+
return false;
|
|
61865
|
+
};
|
|
61866
|
+
const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
|
|
61867
|
+
const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
|
|
61868
|
+
if (!setterSymbol) return false;
|
|
61869
|
+
return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
|
|
61870
|
+
};
|
|
61871
|
+
const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
|
|
61872
|
+
if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
|
|
61873
|
+
const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
|
|
61874
|
+
collectMountedListenerFunctions(locationInvalidationIndex);
|
|
61875
|
+
collectExactReadonlyCalleeCalls(locationInvalidationIndex);
|
|
61876
|
+
return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
|
|
61877
|
+
};
|
|
61878
|
+
//#endregion
|
|
60460
61879
|
//#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
|
|
60461
61880
|
const isInsideConditionTest = (identifier, stopAt) => {
|
|
60462
61881
|
let current = identifier;
|
|
@@ -60578,7 +61997,8 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60578
61997
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60579
61998
|
const bindings = collectUseStateBindings(componentBody, context.scopes);
|
|
60580
61999
|
if (bindings.length === 0) return;
|
|
60581
|
-
|
|
62000
|
+
const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
|
|
62001
|
+
if (renderReachableExpressions.length === 0) return;
|
|
60582
62002
|
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
|
|
60583
62003
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60584
62004
|
const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
|
|
@@ -60592,12 +62012,22 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60592
62012
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
60593
62013
|
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
|
|
60594
62014
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
62015
|
+
const componentFunction = context.cfg.enclosingFunction(componentBody);
|
|
62016
|
+
const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
|
|
62017
|
+
componentBody,
|
|
62018
|
+
componentFunction,
|
|
62019
|
+
context,
|
|
62020
|
+
directRenderNames,
|
|
62021
|
+
renderReachableExpressions
|
|
62022
|
+
}) : () => false;
|
|
60595
62023
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60596
62024
|
walkAst(componentBody, (child) => {
|
|
60597
62025
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
60598
62026
|
});
|
|
60599
62027
|
for (const binding of bindings) {
|
|
60600
62028
|
if (renderReachableNames.has(binding.valueName)) continue;
|
|
62029
|
+
const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
|
|
62030
|
+
if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
|
|
60601
62031
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
60602
62032
|
const setterSuffix = binding.setterName.slice(3);
|
|
60603
62033
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|
|
@@ -71773,18 +73203,6 @@ const reactDoctorRules = [
|
|
|
71773
73203
|
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
71774
73204
|
}
|
|
71775
73205
|
},
|
|
71776
|
-
{
|
|
71777
|
-
key: "react-doctor/jsx-no-target-blank",
|
|
71778
|
-
id: "jsx-no-target-blank",
|
|
71779
|
-
source: "react-doctor",
|
|
71780
|
-
originallyExternal: true,
|
|
71781
|
-
rule: {
|
|
71782
|
-
...jsxNoTargetBlank,
|
|
71783
|
-
framework: "global",
|
|
71784
|
-
category: "Security",
|
|
71785
|
-
requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
|
|
71786
|
-
}
|
|
71787
|
-
},
|
|
71788
73206
|
{
|
|
71789
73207
|
key: "react-doctor/jsx-no-undef",
|
|
71790
73208
|
id: "jsx-no-undef",
|