oxlint-plugin-react-doctor 0.8.1-dev.21221c2 → 0.8.1-dev.26b4a0c
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 +46 -0
- package/dist/index.js +607 -2058
- 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
|
-
new Set([
|
|
765
|
+
const EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS = 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$53 = (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$53(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$52 = (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$52(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$51 = (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$51(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$50 = (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$50(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$49 = (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$49(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$48 = (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$48(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$47 = (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$47(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,
|
|
@@ -9008,16 +9008,6 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
|
9008
9008
|
}
|
|
9009
9009
|
};
|
|
9010
9010
|
//#endregion
|
|
9011
|
-
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
9012
|
-
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
9013
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
9014
|
-
const callee = stripParenExpression(node.callee);
|
|
9015
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
9016
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
9017
|
-
const receiver = stripParenExpression(callee.object);
|
|
9018
|
-
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
9019
|
-
};
|
|
9020
|
-
//#endregion
|
|
9021
9011
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
9022
9012
|
const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
|
|
9023
9013
|
"AbortSignal",
|
|
@@ -9164,10 +9154,8 @@ const getProvenDomEventTargetPrototypeOwnerNames = (rawExpression, scopes, visit
|
|
|
9164
9154
|
if (memberName === "body" || memberName === "documentElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
|
|
9165
9155
|
}
|
|
9166
9156
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
9167
|
-
if (isGlobalMatchMediaCall(expression, scopes)) return getDomPrototypeOwnerNamesForType("MediaQueryList");
|
|
9168
9157
|
const callee = stripParenExpression(expression.callee);
|
|
9169
9158
|
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
9170
|
-
if (methodName === "matchMedia" && isNodeOfType(callee, "MemberExpression") && getProvenDomEventTargetPrototypeOwnerNames(callee.object, scopes, visitedSymbolIds).includes("Window")) return getDomPrototypeOwnerNamesForType("MediaQueryList");
|
|
9171
9159
|
if (methodName === "createElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
|
|
9172
9160
|
if (methodName === "createElementNS") return getDomPrototypeOwnerNamesForType("SVGElement");
|
|
9173
9161
|
if (methodName && DOM_EVENT_TARGET_FACTORY_METHOD_NAMES.has(methodName)) return getDomPrototypeOwnerNamesForType("Element");
|
|
@@ -10380,7 +10368,7 @@ const WRAPPER_LABEL_PROP = "label";
|
|
|
10380
10368
|
const SELECT_ELEMENT = "select";
|
|
10381
10369
|
const DEFAULT_DEPTH = 5;
|
|
10382
10370
|
const MAX_DEPTH = 25;
|
|
10383
|
-
const resolveSettings$
|
|
10371
|
+
const resolveSettings$46 = (settings) => {
|
|
10384
10372
|
const reactDoctor = settings?.["react-doctor"];
|
|
10385
10373
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
|
|
10386
10374
|
return {
|
|
@@ -10713,7 +10701,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
10713
10701
|
recommendation: "Give every interactive control a label screen readers can read.",
|
|
10714
10702
|
category: "Accessibility",
|
|
10715
10703
|
create: (context) => {
|
|
10716
|
-
const settings = resolveSettings$
|
|
10704
|
+
const settings = resolveSettings$46(context.settings);
|
|
10717
10705
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
10718
10706
|
const iconComponentNames = /* @__PURE__ */ new Set();
|
|
10719
10707
|
const iconNamespaceNames = /* @__PURE__ */ new Set();
|
|
@@ -12665,7 +12653,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
|
|
|
12665
12653
|
"lazy",
|
|
12666
12654
|
"withTracking"
|
|
12667
12655
|
];
|
|
12668
|
-
const resolveSettings$
|
|
12656
|
+
const resolveSettings$45 = (settings) => {
|
|
12669
12657
|
const reactDoctor = settings?.["react-doctor"];
|
|
12670
12658
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
|
|
12671
12659
|
const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
|
|
@@ -12847,7 +12835,7 @@ const displayName = defineRule({
|
|
|
12847
12835
|
recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
|
|
12848
12836
|
category: "Architecture",
|
|
12849
12837
|
create: (context) => {
|
|
12850
|
-
const settings = resolveSettings$
|
|
12838
|
+
const settings = resolveSettings$45(context.settings);
|
|
12851
12839
|
const ignoreNamed = !settings.ignoreTranspilerName;
|
|
12852
12840
|
const reportAt = (node) => {
|
|
12853
12841
|
context.report({
|
|
@@ -13827,116 +13815,6 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
13827
13815
|
return null;
|
|
13828
13816
|
};
|
|
13829
13817
|
//#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
|
|
13940
13818
|
//#region src/plugin/utils/get-function-binding-name.ts
|
|
13941
13819
|
const getFunctionBindingIdentifier$1 = (functionNode) => {
|
|
13942
13820
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
@@ -13974,19 +13852,14 @@ const isAstDescendant = (inner, outer) => {
|
|
|
13974
13852
|
};
|
|
13975
13853
|
//#endregion
|
|
13976
13854
|
//#region src/plugin/utils/react-ref-origin.ts
|
|
13977
|
-
const
|
|
13978
|
-
const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
|
|
13855
|
+
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
13979
13856
|
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
13980
13857
|
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
13981
13858
|
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
13982
13859
|
if (!symbol?.initializer) return null;
|
|
13983
13860
|
const initializer = stripParenExpression(symbol.initializer);
|
|
13984
13861
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
13985
|
-
return isReactApiCall(initializer,
|
|
13986
|
-
allowGlobalReactNamespace: true,
|
|
13987
|
-
allowUnboundBareCalls: options.allowUnboundBareCalls,
|
|
13988
|
-
resolveNamedAliases: options.resolveNamedAliases
|
|
13989
|
-
}) ? symbol : null;
|
|
13862
|
+
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
13990
13863
|
};
|
|
13991
13864
|
const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13992
13865
|
const expression = stripParenExpression(node);
|
|
@@ -14085,20 +13958,6 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
14085
13958
|
return false;
|
|
14086
13959
|
};
|
|
14087
13960
|
//#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
|
|
14102
13961
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
14103
13962
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
14104
13963
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -14147,7 +14006,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
|
|
|
14147
14006
|
return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
|
|
14148
14007
|
}
|
|
14149
14008
|
if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
|
|
14150
|
-
if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number"
|
|
14009
|
+
if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
|
|
14151
14010
|
if (isFunctionLike$1(unwrappedExpression)) {
|
|
14152
14011
|
const rangeStart = getRangeStart(unwrappedExpression);
|
|
14153
14012
|
return rangeStart === null ? null : `function:${rangeStart}`;
|
|
@@ -14176,11 +14035,9 @@ const resolveForEachProjection = (expression, context) => {
|
|
|
14176
14035
|
const collectionKey = resolveExpressionKey(forEachCallee.object, context);
|
|
14177
14036
|
if (!collectionKey) return null;
|
|
14178
14037
|
const firstParameter = callbackNode.params[0];
|
|
14179
|
-
const
|
|
14180
|
-
const propertyName = isNodeOfType(firstParameter, "ObjectPattern")
|
|
14181
|
-
const
|
|
14182
|
-
if (assignmentPattern && !defaultValueKey) return null;
|
|
14183
|
-
const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
|
|
14038
|
+
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
14039
|
+
const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
|
|
14040
|
+
const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
|
|
14184
14041
|
if (!parameterProjection) return null;
|
|
14185
14042
|
return {
|
|
14186
14043
|
collectionKey,
|
|
@@ -14192,23 +14049,13 @@ const resolveForEachProjectionKey = (expression, context) => {
|
|
|
14192
14049
|
return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
|
|
14193
14050
|
};
|
|
14194
14051
|
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
|
-
};
|
|
14205
14052
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
14206
14053
|
const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
|
|
14207
14054
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
14208
14055
|
if (!optionsNode) return null;
|
|
14209
14056
|
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
14210
14057
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
14211
|
-
const optionsKey = allowOpaqueOptionsIdentity ?
|
|
14058
|
+
const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
|
|
14212
14059
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
14213
14060
|
}
|
|
14214
14061
|
let captureKey = "capture:false";
|
|
@@ -14223,7 +14070,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
|
|
|
14223
14070
|
continue;
|
|
14224
14071
|
}
|
|
14225
14072
|
if (propertyName === "capture") {
|
|
14226
|
-
const propertyValueKey =
|
|
14073
|
+
const propertyValueKey = resolveResourceIdentityKey(property.value, context);
|
|
14227
14074
|
captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
|
|
14228
14075
|
}
|
|
14229
14076
|
}
|
|
@@ -14263,30 +14110,6 @@ const findAssignedResourceKey = (resourceNode, context) => {
|
|
|
14263
14110
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
14264
14111
|
return null;
|
|
14265
14112
|
};
|
|
14266
|
-
const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14267
|
-
if (!expression) return null;
|
|
14268
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
14269
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
14270
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
14271
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
|
|
14272
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
14273
|
-
if (!initializer) return `symbol:${symbol.id}`;
|
|
14274
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
14275
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
|
|
14276
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
14277
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
14278
|
-
return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
|
|
14279
|
-
}
|
|
14280
|
-
if (isFunctionLike$1(unwrappedExpression)) {
|
|
14281
|
-
const rangeStart = getRangeStart(unwrappedExpression);
|
|
14282
|
-
return rangeStart === null ? null : `function:${rangeStart}`;
|
|
14283
|
-
}
|
|
14284
|
-
return null;
|
|
14285
|
-
};
|
|
14286
|
-
const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
|
|
14287
|
-
const callee = stripParenExpression(callNode.callee);
|
|
14288
|
-
return callNode.arguments?.length === 1 && isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName && getProvenDomEventTargetPrototypeOwnerNames(callee.object, context.scopes).includes("MediaQueryList");
|
|
14289
|
-
};
|
|
14290
14113
|
const getCallRegistrationDetails = (callNode, context) => {
|
|
14291
14114
|
const callee = stripParenExpression(callNode.callee);
|
|
14292
14115
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
|
|
@@ -14295,12 +14118,6 @@ const getCallRegistrationDetails = (callNode, context) => {
|
|
|
14295
14118
|
eventKey: null,
|
|
14296
14119
|
handlerKey: null
|
|
14297
14120
|
};
|
|
14298
|
-
if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
|
|
14299
|
-
receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
|
|
14300
|
-
registrationVerbName: callee.property.name,
|
|
14301
|
-
eventKey: null,
|
|
14302
|
-
handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
|
|
14303
|
-
};
|
|
14304
14121
|
return {
|
|
14305
14122
|
receiverKey: resolveResourceIdentityKey(callee.object, context),
|
|
14306
14123
|
registrationVerbName: callee.property.name,
|
|
@@ -14405,6 +14222,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
14405
14222
|
}
|
|
14406
14223
|
return matchingBlocks.size > 0;
|
|
14407
14224
|
};
|
|
14225
|
+
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
14226
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
14227
|
+
if (!functionCfg) return false;
|
|
14228
|
+
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
14229
|
+
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
14230
|
+
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
14231
|
+
return matchingBlock ? [matchingBlock] : [];
|
|
14232
|
+
}));
|
|
14233
|
+
if (matchingBlocks.size === 0) return false;
|
|
14234
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
14235
|
+
const pendingBlocks = [functionCfg.entry];
|
|
14236
|
+
while (pendingBlocks.length > 0) {
|
|
14237
|
+
const currentBlock = pendingBlocks.pop();
|
|
14238
|
+
if (!currentBlock) break;
|
|
14239
|
+
if (matchingBlocks.has(currentBlock)) continue;
|
|
14240
|
+
for (const edge of currentBlock.successors) {
|
|
14241
|
+
if (edge.to === functionCfg.exit) return false;
|
|
14242
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
14243
|
+
visitedBlocks.add(edge.to);
|
|
14244
|
+
pendingBlocks.push(edge.to);
|
|
14245
|
+
}
|
|
14246
|
+
}
|
|
14247
|
+
return true;
|
|
14248
|
+
};
|
|
14408
14249
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
14409
14250
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
14410
14251
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -14647,6 +14488,18 @@ const findPushedResourceCollectionKey = (usage, context) => {
|
|
|
14647
14488
|
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14648
14489
|
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14649
14490
|
};
|
|
14491
|
+
const isWithinAssignmentTarget = (identifier) => {
|
|
14492
|
+
let currentNode = identifier;
|
|
14493
|
+
let parentNode = currentNode.parent;
|
|
14494
|
+
while (parentNode) {
|
|
14495
|
+
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14496
|
+
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14497
|
+
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14498
|
+
currentNode = parentNode;
|
|
14499
|
+
parentNode = currentNode.parent;
|
|
14500
|
+
}
|
|
14501
|
+
return false;
|
|
14502
|
+
};
|
|
14650
14503
|
const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14651
14504
|
if (!expression) return null;
|
|
14652
14505
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14770,7 +14623,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14770
14623
|
const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
|
|
14771
14624
|
if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
|
|
14772
14625
|
});
|
|
14773
|
-
return didCleanupFunctionMatch ||
|
|
14626
|
+
return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
|
|
14774
14627
|
};
|
|
14775
14628
|
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
14776
14629
|
const callExpression = stripParenExpression(expression);
|
|
@@ -14799,7 +14652,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14799
14652
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
14800
14653
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
14801
14654
|
});
|
|
14802
|
-
return
|
|
14655
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14803
14656
|
};
|
|
14804
14657
|
const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
|
|
14805
14658
|
if (resolveExpressionKey(test, context) === expressionKey) return true;
|
|
@@ -14823,24 +14676,7 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
|
|
|
14823
14676
|
return null;
|
|
14824
14677
|
};
|
|
14825
14678
|
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
|
|
14826
|
-
const
|
|
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) => {
|
|
14679
|
+
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
14844
14680
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
14845
14681
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
14846
14682
|
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
@@ -14851,8 +14687,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
|
|
|
14851
14687
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
14852
14688
|
const releaseStart = getRangeStart(child);
|
|
14853
14689
|
const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
|
|
14854
|
-
|
|
14855
|
-
if (releaseStart === null || releaseStart >= usageStart || releaseBlock !== usageBlock && !handleGuard || !handleGuard && hasExecutionBoundaryNotSharedWithUsage(child, usage.node, callback)) return;
|
|
14690
|
+
if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
|
|
14856
14691
|
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
14857
14692
|
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14858
14693
|
return;
|
|
@@ -14860,7 +14695,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
|
|
|
14860
14695
|
const helperFunction = resolveStableValue(child.callee, context);
|
|
14861
14696
|
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14862
14697
|
});
|
|
14863
|
-
return
|
|
14698
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
14864
14699
|
};
|
|
14865
14700
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
14866
14701
|
const componentFunction = findEnclosingFunction$1(callback);
|
|
@@ -14880,7 +14715,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
14880
14715
|
});
|
|
14881
14716
|
return didFindUnmountCleanup;
|
|
14882
14717
|
};
|
|
14883
|
-
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null &&
|
|
14718
|
+
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
14884
14719
|
const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
|
|
14885
14720
|
const unwrappedExpression = stripParenExpression(expression);
|
|
14886
14721
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
|
|
@@ -15111,7 +14946,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
|
|
|
15111
14946
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15112
14947
|
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
15113
14948
|
const usageAssignment = usageExpression.parent;
|
|
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) || !
|
|
14949
|
+
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) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
15115
14950
|
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
15116
14951
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
15117
14952
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
@@ -15159,7 +14994,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
15159
14994
|
});
|
|
15160
14995
|
}
|
|
15161
14996
|
});
|
|
15162
|
-
if (!
|
|
14997
|
+
if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
15163
14998
|
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
15164
14999
|
}
|
|
15165
15000
|
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
@@ -15372,7 +15207,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
15372
15207
|
});
|
|
15373
15208
|
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15374
15209
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15375
|
-
return
|
|
15210
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15376
15211
|
};
|
|
15377
15212
|
const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
|
|
15378
15213
|
let currentNode = findTransparentExpressionRoot(releaseNode);
|
|
@@ -15385,7 +15220,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
|
|
|
15385
15220
|
const isDirectConciseBody = ownerFunction.body === currentNode;
|
|
15386
15221
|
const statementNode = currentNode.parent;
|
|
15387
15222
|
const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
|
|
15388
|
-
if (!isDirectConciseBody && !isDirectBlockStatement || !
|
|
15223
|
+
if (!isDirectConciseBody && !isDirectBlockStatement || !doMatchingNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
|
|
15389
15224
|
const forEachCall = findEnclosingForEachCall(ownerFunction);
|
|
15390
15225
|
if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
|
|
15391
15226
|
const forEachCallee = stripParenExpression(forEachCall.callee);
|
|
@@ -15473,7 +15308,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
|
|
|
15473
15308
|
const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
|
|
15474
15309
|
const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
|
|
15475
15310
|
const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
|
|
15476
|
-
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) &&
|
|
15311
|
+
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
|
|
15477
15312
|
const projections = [
|
|
15478
15313
|
registrationCallee.object,
|
|
15479
15314
|
registrationCall.arguments?.[0],
|
|
@@ -15520,7 +15355,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15520
15355
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15521
15356
|
if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
|
|
15522
15357
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15523
|
-
if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
|
|
15524
15358
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15525
15359
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
15526
15360
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
@@ -15672,7 +15506,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
|
15672
15506
|
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15673
15507
|
matchingReturns.push(child);
|
|
15674
15508
|
});
|
|
15675
|
-
return
|
|
15509
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15676
15510
|
};
|
|
15677
15511
|
let didFindInvocation = false;
|
|
15678
15512
|
walkAst(componentFunction.body, (child) => {
|
|
@@ -15724,7 +15558,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
|
15724
15558
|
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15725
15559
|
};
|
|
15726
15560
|
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
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") || !
|
|
15561
|
+
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") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15728
15562
|
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15729
15563
|
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15730
15564
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
|
|
@@ -15952,7 +15786,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
15952
15786
|
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15953
15787
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
|
|
15954
15788
|
});
|
|
15955
|
-
return
|
|
15789
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15956
15790
|
};
|
|
15957
15791
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
15958
15792
|
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
@@ -15995,6 +15829,15 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
|
|
|
15995
15829
|
const returnStatement = expressionRoot.parent;
|
|
15996
15830
|
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
|
|
15997
15831
|
};
|
|
15832
|
+
const getFinalSequenceExpressionValue = (expression) => {
|
|
15833
|
+
let finalExpression = stripParenExpression(expression);
|
|
15834
|
+
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
15835
|
+
const sequenceResult = finalExpression.expressions.at(-1);
|
|
15836
|
+
if (!sequenceResult) break;
|
|
15837
|
+
finalExpression = stripParenExpression(sequenceResult);
|
|
15838
|
+
}
|
|
15839
|
+
return finalExpression;
|
|
15840
|
+
};
|
|
15998
15841
|
const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
|
|
15999
15842
|
if (!allowReturnedResourceEscape) return false;
|
|
16000
15843
|
let currentNode = resourceNode;
|
|
@@ -16243,7 +16086,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
|
|
|
16243
16086
|
if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
16244
16087
|
if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
|
|
16245
16088
|
});
|
|
16246
|
-
return
|
|
16089
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
|
|
16247
16090
|
};
|
|
16248
16091
|
const matchingReturns = [];
|
|
16249
16092
|
walkInsideStatementBlocks(retainedFunction.body, (child) => {
|
|
@@ -19046,7 +18889,7 @@ const compileGlob = (pattern) => {
|
|
|
19046
18889
|
};
|
|
19047
18890
|
//#endregion
|
|
19048
18891
|
//#region src/plugin/rules/react-builtins/forbid-component-props.ts
|
|
19049
|
-
const resolveSettings$
|
|
18892
|
+
const resolveSettings$44 = (settings) => {
|
|
19050
18893
|
const reactDoctor = settings?.["react-doctor"];
|
|
19051
18894
|
return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
|
|
19052
18895
|
};
|
|
@@ -19098,7 +18941,7 @@ const forbidComponentProps = defineRule({
|
|
|
19098
18941
|
recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
|
|
19099
18942
|
category: "Architecture",
|
|
19100
18943
|
create: (context) => {
|
|
19101
|
-
const entries = resolveSettings$
|
|
18944
|
+
const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
|
|
19102
18945
|
return { JSXOpeningElement(node) {
|
|
19103
18946
|
if (entries.length === 0) return;
|
|
19104
18947
|
if (!isSupportedJsxName(node.name)) return;
|
|
@@ -19127,7 +18970,7 @@ const forbidComponentProps = defineRule({
|
|
|
19127
18970
|
//#endregion
|
|
19128
18971
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
19129
18972
|
const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
|
|
19130
|
-
const resolveSettings$
|
|
18973
|
+
const resolveSettings$43 = (settings) => {
|
|
19131
18974
|
const reactDoctor = settings?.["react-doctor"];
|
|
19132
18975
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
19133
18976
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19149,7 +18992,7 @@ const forbidDomProps = defineRule({
|
|
|
19149
18992
|
recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
|
|
19150
18993
|
category: "Architecture",
|
|
19151
18994
|
create: (context) => {
|
|
19152
|
-
const forbidMap = resolveSettings$
|
|
18995
|
+
const forbidMap = resolveSettings$43(context.settings);
|
|
19153
18996
|
return { JSXOpeningElement(node) {
|
|
19154
18997
|
if (forbidMap.size === 0) return;
|
|
19155
18998
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -19212,7 +19055,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
19212
19055
|
//#endregion
|
|
19213
19056
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
19214
19057
|
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.`;
|
|
19215
|
-
const resolveSettings$
|
|
19058
|
+
const resolveSettings$42 = (settings) => {
|
|
19216
19059
|
const reactDoctor = settings?.["react-doctor"];
|
|
19217
19060
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
19218
19061
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19228,7 +19071,7 @@ const forbidElements = defineRule({
|
|
|
19228
19071
|
recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
|
|
19229
19072
|
category: "Architecture",
|
|
19230
19073
|
create: (context) => {
|
|
19231
|
-
const forbidMap = resolveSettings$
|
|
19074
|
+
const forbidMap = resolveSettings$42(context.settings);
|
|
19232
19075
|
return {
|
|
19233
19076
|
JSXOpeningElement(node) {
|
|
19234
19077
|
if (forbidMap.size === 0) return;
|
|
@@ -19337,7 +19180,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
19337
19180
|
"h5",
|
|
19338
19181
|
"h6"
|
|
19339
19182
|
];
|
|
19340
|
-
const resolveSettings$
|
|
19183
|
+
const resolveSettings$41 = (settings) => {
|
|
19341
19184
|
const reactDoctor = settings?.["react-doctor"];
|
|
19342
19185
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
19343
19186
|
return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
|
|
@@ -19350,7 +19193,7 @@ const headingHasContent = defineRule({
|
|
|
19350
19193
|
recommendation: "Put readable text in every heading.",
|
|
19351
19194
|
category: "Accessibility",
|
|
19352
19195
|
create: (context) => {
|
|
19353
|
-
const settings = resolveSettings$
|
|
19196
|
+
const settings = resolveSettings$41(context.settings);
|
|
19354
19197
|
return { JSXOpeningElement(node) {
|
|
19355
19198
|
const elementType = getElementType(node, context.settings);
|
|
19356
19199
|
if (!settings.headingTags.has(elementType)) return;
|
|
@@ -19371,7 +19214,7 @@ const headingHasContent = defineRule({
|
|
|
19371
19214
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
19372
19215
|
const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
|
|
19373
19216
|
const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
|
|
19374
|
-
const resolveSettings$
|
|
19217
|
+
const resolveSettings$40 = (settings) => {
|
|
19375
19218
|
const reactDoctor = settings?.["react-doctor"];
|
|
19376
19219
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
19377
19220
|
};
|
|
@@ -19398,7 +19241,7 @@ const hookUseState = defineRule({
|
|
|
19398
19241
|
recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
|
|
19399
19242
|
category: "Architecture",
|
|
19400
19243
|
create: (context) => {
|
|
19401
|
-
const { allowDestructuredState } = resolveSettings$
|
|
19244
|
+
const { allowDestructuredState } = resolveSettings$40(context.settings);
|
|
19402
19245
|
return { CallExpression(node) {
|
|
19403
19246
|
const callExpressionNode = node;
|
|
19404
19247
|
if (!isReactFunctionCall(callExpressionNode, "useState")) return;
|
|
@@ -19531,7 +19374,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19531
19374
|
//#endregion
|
|
19532
19375
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19533
19376
|
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19534
|
-
const resolveSettings$
|
|
19377
|
+
const resolveSettings$39 = (settings) => {
|
|
19535
19378
|
const reactDoctor = settings?.["react-doctor"];
|
|
19536
19379
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
19537
19380
|
};
|
|
@@ -19568,7 +19411,7 @@ const htmlHasLang = defineRule({
|
|
|
19568
19411
|
recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
|
|
19569
19412
|
category: "Accessibility",
|
|
19570
19413
|
create: (context) => {
|
|
19571
|
-
const settings = resolveSettings$
|
|
19414
|
+
const settings = resolveSettings$39(context.settings);
|
|
19572
19415
|
const tagSet = new Set(settings.htmlTags);
|
|
19573
19416
|
return { JSXOpeningElement(node) {
|
|
19574
19417
|
const tag = getElementType(node, context.settings);
|
|
@@ -20084,7 +19927,7 @@ const DEFAULT_REDUNDANT_WORDS = [
|
|
|
20084
19927
|
"photo",
|
|
20085
19928
|
"picture"
|
|
20086
19929
|
];
|
|
20087
|
-
const resolveSettings$
|
|
19930
|
+
const resolveSettings$38 = (settings) => {
|
|
20088
19931
|
const reactDoctor = settings?.["react-doctor"];
|
|
20089
19932
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
|
|
20090
19933
|
return {
|
|
@@ -20132,7 +19975,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20132
19975
|
category: "Accessibility",
|
|
20133
19976
|
create: (context) => {
|
|
20134
19977
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
20135
|
-
const settings = resolveSettings$
|
|
19978
|
+
const settings = resolveSettings$38(context.settings);
|
|
20136
19979
|
return { JSXOpeningElement(node) {
|
|
20137
19980
|
const tag = getElementType(node, context.settings);
|
|
20138
19981
|
if (!settings.components.includes(tag)) return;
|
|
@@ -20440,7 +20283,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
|
|
|
20440
20283
|
"row",
|
|
20441
20284
|
"gridcell"
|
|
20442
20285
|
]);
|
|
20443
|
-
const resolveSettings$
|
|
20286
|
+
const resolveSettings$37 = (settings) => {
|
|
20444
20287
|
const reactDoctor = settings?.["react-doctor"];
|
|
20445
20288
|
return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
|
|
20446
20289
|
};
|
|
@@ -20452,7 +20295,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
20452
20295
|
recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
|
|
20453
20296
|
category: "Accessibility",
|
|
20454
20297
|
create: (context) => {
|
|
20455
|
-
const settings = resolveSettings$
|
|
20298
|
+
const settings = resolveSettings$37(context.settings);
|
|
20456
20299
|
const tabbableSet = new Set(settings.tabbable);
|
|
20457
20300
|
return { JSXOpeningElement(node) {
|
|
20458
20301
|
if (isLocalTestScaffoldJsx(node, context)) return;
|
|
@@ -22593,49 +22436,25 @@ const jsLengthCheckFirst = defineRule({
|
|
|
22593
22436
|
});
|
|
22594
22437
|
//#endregion
|
|
22595
22438
|
//#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
|
-
]);
|
|
22602
22439
|
const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
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
|
-
}
|
|
22440
|
+
const strippedExpression = stripParenExpression(expression);
|
|
22441
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
22442
|
+
if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22443
|
+
const symbol = scopes.symbolFor(strippedExpression);
|
|
22444
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22445
|
+
visitedSymbolIds.add(symbol.id);
|
|
22446
|
+
return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
22621
22447
|
};
|
|
22622
22448
|
const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22623
|
-
|
|
22624
|
-
|
|
22625
|
-
const strippedExpression = stripParenExpression(currentExpression);
|
|
22626
|
-
if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22449
|
+
const strippedExpression = stripParenExpression(expression);
|
|
22450
|
+
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
22627
22451
|
if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22628
22452
|
const symbol = scopes.symbolFor(strippedExpression);
|
|
22629
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22453
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22630
22454
|
visitedSymbolIds.add(symbol.id);
|
|
22631
|
-
|
|
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;
|
|
22455
|
+
return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
|
|
22638
22456
|
}
|
|
22457
|
+
return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22639
22458
|
};
|
|
22640
22459
|
//#endregion
|
|
22641
22460
|
//#region src/plugin/rules/js-performance/js-min-max-loop.ts
|
|
@@ -23748,7 +23567,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
23748
23567
|
const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23749
23568
|
const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23750
23569
|
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
|
|
23751
|
-
const resolveSettings$
|
|
23570
|
+
const resolveSettings$36 = (settings) => {
|
|
23752
23571
|
const reactDoctor = settings?.["react-doctor"];
|
|
23753
23572
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
23754
23573
|
return {
|
|
@@ -23766,7 +23585,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23766
23585
|
recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
|
|
23767
23586
|
category: "Architecture",
|
|
23768
23587
|
create: (context) => {
|
|
23769
|
-
const settings = resolveSettings$
|
|
23588
|
+
const settings = resolveSettings$36(context.settings);
|
|
23770
23589
|
const alwaysSet = new Set(settings.always);
|
|
23771
23590
|
const neverSet = new Set(settings.never);
|
|
23772
23591
|
return { JSXAttribute(node) {
|
|
@@ -23820,7 +23639,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23820
23639
|
const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
|
|
23821
23640
|
const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
|
|
23822
23641
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
23823
|
-
const resolveSettings$
|
|
23642
|
+
const resolveSettings$35 = (settings) => {
|
|
23824
23643
|
const reactDoctor = settings?.["react-doctor"];
|
|
23825
23644
|
const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
|
|
23826
23645
|
if (isAllowedMode(ruleSettingsRaw)) return {
|
|
@@ -23916,7 +23735,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
23916
23735
|
recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
|
|
23917
23736
|
category: "Architecture",
|
|
23918
23737
|
create: (context) => {
|
|
23919
|
-
const settings = resolveSettings$
|
|
23738
|
+
const settings = resolveSettings$35(context.settings);
|
|
23920
23739
|
return {
|
|
23921
23740
|
JSXAttribute(node) {
|
|
23922
23741
|
const value = node.value;
|
|
@@ -23999,7 +23818,7 @@ const containsJsxElement = (root) => {
|
|
|
23999
23818
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
24000
23819
|
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.`;
|
|
24001
23820
|
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.`;
|
|
24002
|
-
const resolveSettings$
|
|
23821
|
+
const resolveSettings$34 = (settings) => {
|
|
24003
23822
|
const reactDoctor = settings?.["react-doctor"];
|
|
24004
23823
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
24005
23824
|
return {
|
|
@@ -24021,7 +23840,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
24021
23840
|
recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
|
|
24022
23841
|
category: "Architecture",
|
|
24023
23842
|
create: (context) => {
|
|
24024
|
-
const settings = resolveSettings$
|
|
23843
|
+
const settings = resolveSettings$34(context.settings);
|
|
24025
23844
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
24026
23845
|
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
24027
23846
|
const extensionOnly = path.extname(filename).slice(1);
|
|
@@ -24062,7 +23881,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
24062
23881
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
24063
23882
|
const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
|
|
24064
23883
|
const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
|
|
24065
|
-
const resolveSettings$
|
|
23884
|
+
const resolveSettings$33 = (settings) => {
|
|
24066
23885
|
const reactDoctor = settings?.["react-doctor"];
|
|
24067
23886
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
24068
23887
|
};
|
|
@@ -24074,7 +23893,7 @@ const jsxFragments = defineRule({
|
|
|
24074
23893
|
recommendation: "Use one fragment style so identical wrappers do not look different across files.",
|
|
24075
23894
|
category: "Architecture",
|
|
24076
23895
|
create: (context) => {
|
|
24077
|
-
const { mode } = resolveSettings$
|
|
23896
|
+
const { mode } = resolveSettings$33(context.settings);
|
|
24078
23897
|
return {
|
|
24079
23898
|
JSXElement(node) {
|
|
24080
23899
|
if (mode !== "syntax") return;
|
|
@@ -24118,7 +23937,7 @@ const buildPropRegex = (handlerPropPrefix) => {
|
|
|
24118
23937
|
const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
24119
23938
|
return new RegExp(`^(${escaped})[A-Z].*$`);
|
|
24120
23939
|
};
|
|
24121
|
-
const resolveSettings$
|
|
23940
|
+
const resolveSettings$32 = (settings) => {
|
|
24122
23941
|
const reactDoctor = settings?.["react-doctor"];
|
|
24123
23942
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
|
|
24124
23943
|
let handlerPrefix = DEFAULT_HANDLER_PREFIX;
|
|
@@ -24194,7 +24013,7 @@ const jsxHandlerNames = defineRule({
|
|
|
24194
24013
|
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
|
|
24195
24014
|
category: "Architecture",
|
|
24196
24015
|
create: (context) => {
|
|
24197
|
-
const settings = resolveSettings$
|
|
24016
|
+
const settings = resolveSettings$32(context.settings);
|
|
24198
24017
|
return { JSXAttribute(node) {
|
|
24199
24018
|
if (settings.ignoreComponentNames.length > 0) {
|
|
24200
24019
|
const opening = node.parent;
|
|
@@ -24269,19 +24088,6 @@ const hasJsxKeyAttribute = (openingElement) => {
|
|
|
24269
24088
|
return false;
|
|
24270
24089
|
};
|
|
24271
24090
|
//#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
|
|
24285
24091
|
//#region src/plugin/utils/is-const-declared-binding.ts
|
|
24286
24092
|
const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
|
|
24287
24093
|
"VariableDeclarator",
|
|
@@ -24326,17 +24132,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
|
|
|
24326
24132
|
"flatMap",
|
|
24327
24133
|
"from"
|
|
24328
24134
|
]);
|
|
24329
|
-
const RENDERING_CALL_NAMES = new Set([
|
|
24330
|
-
"createPortal",
|
|
24331
|
-
"hydrate",
|
|
24332
|
-
"hydrateRoot",
|
|
24333
|
-
"render"
|
|
24334
|
-
]);
|
|
24335
24135
|
const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
|
|
24336
24136
|
const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
|
|
24337
|
-
const
|
|
24137
|
+
const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
|
|
24338
24138
|
const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
|
|
24339
|
-
const resolveSettings$
|
|
24139
|
+
const resolveSettings$31 = (settings) => {
|
|
24340
24140
|
const reactDoctor = settings?.["react-doctor"];
|
|
24341
24141
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
24342
24142
|
return {
|
|
@@ -24350,33 +24150,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
|
|
|
24350
24150
|
if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
|
|
24351
24151
|
return null;
|
|
24352
24152
|
};
|
|
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
|
-
};
|
|
24380
24153
|
const isIdentityIteratorCallback = (callback) => {
|
|
24381
24154
|
const callbackFunction = stripParenExpression(callback);
|
|
24382
24155
|
if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
|
|
@@ -24473,7 +24246,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
|
|
|
24473
24246
|
namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
|
|
24474
24247
|
return iteratorCall;
|
|
24475
24248
|
};
|
|
24476
|
-
const findEnclosingIteratorContext = (jsxNode
|
|
24249
|
+
const findEnclosingIteratorContext = (jsxNode) => {
|
|
24477
24250
|
let current = jsxNode;
|
|
24478
24251
|
let isOutsideContainingFunction = false;
|
|
24479
24252
|
let didSeeReturnStatement = false;
|
|
@@ -24495,8 +24268,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
|
|
|
24495
24268
|
isOutsideContainingFunction = true;
|
|
24496
24269
|
} else if (isNodeOfType(parent, "ArrayExpression")) {
|
|
24497
24270
|
if (isOutsideContainingFunction) return null;
|
|
24498
|
-
if (isArrayNestedInObjectProperty(parent)) return null;
|
|
24499
|
-
if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
|
|
24500
24271
|
const arrayParent = parent.parent;
|
|
24501
24272
|
if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
|
|
24502
24273
|
if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
|
|
@@ -24578,15 +24349,9 @@ const isWithinChildrenToArray = (jsxNode) => {
|
|
|
24578
24349
|
const isKeyPropertyName = (propertyKey) => {
|
|
24579
24350
|
if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
|
|
24580
24351
|
if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
|
|
24581
|
-
return
|
|
24352
|
+
return true;
|
|
24582
24353
|
};
|
|
24583
24354
|
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
|
-
};
|
|
24590
24355
|
const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
24591
24356
|
const restElement = bindingIdentifier.parent;
|
|
24592
24357
|
if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
|
|
@@ -24596,7 +24361,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
|
24596
24361
|
const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
|
|
24597
24362
|
const functionNode = parameterNode.parent;
|
|
24598
24363
|
if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
|
|
24599
|
-
return
|
|
24364
|
+
return functionNode.params.some((param) => param === parameterNode);
|
|
24600
24365
|
};
|
|
24601
24366
|
const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
24602
24367
|
const restElement = bindingIdentifier.parent;
|
|
@@ -24609,14 +24374,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
|
24609
24374
|
}
|
|
24610
24375
|
return false;
|
|
24611
24376
|
};
|
|
24612
|
-
const
|
|
24613
|
-
let
|
|
24377
|
+
const isBindingObjectMutated = (scopeOwner, bindingName) => {
|
|
24378
|
+
let didFindMutation = false;
|
|
24614
24379
|
walkAst(scopeOwner, (node) => {
|
|
24615
|
-
if (
|
|
24380
|
+
if (didFindMutation) return false;
|
|
24616
24381
|
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
|
|
24617
24382
|
const assignedObject = stripParenExpression(node.left.object);
|
|
24618
|
-
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName
|
|
24619
|
-
|
|
24383
|
+
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
|
|
24384
|
+
didFindMutation = true;
|
|
24620
24385
|
return false;
|
|
24621
24386
|
}
|
|
24622
24387
|
}
|
|
@@ -24624,63 +24389,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
|
|
|
24624
24389
|
const callee = node.callee;
|
|
24625
24390
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
|
|
24626
24391
|
const firstArgument = node.arguments[0];
|
|
24627
|
-
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName)
|
|
24628
|
-
|
|
24629
|
-
|
|
24630
|
-
if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
|
|
24631
|
-
didFindKeyAssignment = true;
|
|
24632
|
-
return false;
|
|
24633
|
-
}
|
|
24392
|
+
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
|
|
24393
|
+
didFindMutation = true;
|
|
24394
|
+
return false;
|
|
24634
24395
|
}
|
|
24635
24396
|
}
|
|
24636
24397
|
}
|
|
24637
24398
|
});
|
|
24638
|
-
return
|
|
24399
|
+
return didFindMutation;
|
|
24639
24400
|
};
|
|
24640
|
-
const
|
|
24401
|
+
const spreadExpressionCanCarryKey = (expression, depth) => {
|
|
24641
24402
|
const inner = stripParenExpression(expression);
|
|
24642
24403
|
if (isNodeOfType(inner, "ObjectExpression")) {
|
|
24643
24404
|
for (const property of inner.properties) {
|
|
24644
24405
|
if (isNodeOfType(property, "SpreadElement")) {
|
|
24645
|
-
if (depth >= 3)
|
|
24646
|
-
if (
|
|
24406
|
+
if (depth >= 3) return true;
|
|
24407
|
+
if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
|
|
24647
24408
|
continue;
|
|
24648
24409
|
}
|
|
24649
|
-
if (!isNodeOfType(property, "Property"))
|
|
24410
|
+
if (!isNodeOfType(property, "Property")) return true;
|
|
24411
|
+
if (property.computed) return true;
|
|
24650
24412
|
if (isKeyPropertyName(property.key)) return true;
|
|
24651
24413
|
}
|
|
24652
24414
|
return false;
|
|
24653
24415
|
}
|
|
24654
24416
|
if (isNodeOfType(inner, "ConditionalExpression")) {
|
|
24655
|
-
if (depth >= 3) return
|
|
24656
|
-
return
|
|
24417
|
+
if (depth >= 3) return true;
|
|
24418
|
+
return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
|
|
24657
24419
|
}
|
|
24658
24420
|
if (isNodeOfType(inner, "LogicalExpression")) {
|
|
24659
|
-
if (depth >= 3) return
|
|
24660
|
-
if (inner.operator === "&&") return
|
|
24661
|
-
return
|
|
24421
|
+
if (depth >= 3) return true;
|
|
24422
|
+
if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24423
|
+
return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24662
24424
|
}
|
|
24663
24425
|
if (isNodeOfType(inner, "Literal")) return false;
|
|
24664
24426
|
if (isThisPropsMember(inner)) return false;
|
|
24665
24427
|
if (isNodeOfType(inner, "Identifier")) {
|
|
24666
24428
|
if (inner.name === "undefined") return false;
|
|
24667
|
-
if (depth >= 3) return
|
|
24429
|
+
if (depth >= 3) return true;
|
|
24668
24430
|
const binding = findVariableInitializer(inner, inner.name);
|
|
24669
|
-
if (!binding) return
|
|
24431
|
+
if (!binding) return true;
|
|
24670
24432
|
if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
|
|
24671
24433
|
if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
|
|
24672
|
-
if (
|
|
24673
|
-
if (
|
|
24674
|
-
|
|
24675
|
-
return spreadExpressionHasKey(binding.initializer, depth + 1);
|
|
24434
|
+
if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
|
|
24435
|
+
if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
|
|
24436
|
+
return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
|
|
24676
24437
|
}
|
|
24677
|
-
return
|
|
24438
|
+
return true;
|
|
24678
24439
|
};
|
|
24679
|
-
const spreadCanOverwriteKey = (spreadAttribute) =>
|
|
24440
|
+
const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
|
|
24680
24441
|
const checkKeyBeforeSpread = (context, openingElement) => {
|
|
24681
24442
|
let keyIndex = null;
|
|
24682
24443
|
let keyAttribute = null;
|
|
24683
|
-
let
|
|
24444
|
+
let firstKeyCarryingSpreadIndex = null;
|
|
24684
24445
|
for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
|
|
24685
24446
|
const attribute = openingElement.attributes[attributeIndex];
|
|
24686
24447
|
if (isNodeOfType(attribute, "JSXAttribute")) {
|
|
@@ -24688,11 +24449,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
|
|
|
24688
24449
|
keyIndex = attributeIndex;
|
|
24689
24450
|
keyAttribute = attribute;
|
|
24690
24451
|
}
|
|
24691
|
-
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute))
|
|
24452
|
+
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
|
|
24692
24453
|
}
|
|
24693
|
-
if (keyIndex !== null &&
|
|
24454
|
+
if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
|
|
24694
24455
|
node: keyAttribute,
|
|
24695
|
-
message:
|
|
24456
|
+
message: KEY_AFTER_SPREAD
|
|
24696
24457
|
});
|
|
24697
24458
|
};
|
|
24698
24459
|
const getKeyAttributeValueString = (openingElement) => {
|
|
@@ -24736,7 +24497,7 @@ const jsxKey = defineRule({
|
|
|
24736
24497
|
severity: "error",
|
|
24737
24498
|
recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
|
|
24738
24499
|
create: (context) => {
|
|
24739
|
-
const settings = resolveSettings$
|
|
24500
|
+
const settings = resolveSettings$31(context.settings);
|
|
24740
24501
|
return {
|
|
24741
24502
|
JSXElement(node) {
|
|
24742
24503
|
const openingElement = node.openingElement;
|
|
@@ -24754,7 +24515,7 @@ const jsxKey = defineRule({
|
|
|
24754
24515
|
else seenKeys.add(keyValue.keyValue);
|
|
24755
24516
|
}
|
|
24756
24517
|
}
|
|
24757
|
-
const enclosingContext = findEnclosingIteratorContext(node
|
|
24518
|
+
const enclosingContext = findEnclosingIteratorContext(node);
|
|
24758
24519
|
if (!enclosingContext) return;
|
|
24759
24520
|
if (isWithinChildrenToArray(node)) return;
|
|
24760
24521
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
@@ -24790,7 +24551,7 @@ const jsxKey = defineRule({
|
|
|
24790
24551
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
24791
24552
|
const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
24792
24553
|
const DEFAULT_MAX_DEPTH = 14;
|
|
24793
|
-
const resolveSettings$
|
|
24554
|
+
const resolveSettings$30 = (settings) => {
|
|
24794
24555
|
const reactDoctor = settings?.["react-doctor"];
|
|
24795
24556
|
return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
|
|
24796
24557
|
};
|
|
@@ -24859,7 +24620,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24859
24620
|
recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
|
|
24860
24621
|
category: "Architecture",
|
|
24861
24622
|
create: (context) => {
|
|
24862
|
-
const { max } = resolveSettings$
|
|
24623
|
+
const { max } = resolveSettings$30(context.settings);
|
|
24863
24624
|
const deepestLeafPerTree = /* @__PURE__ */ new Map();
|
|
24864
24625
|
const checkNode = (node) => {
|
|
24865
24626
|
if (!isLeafJsxNode(node)) return;
|
|
@@ -26831,7 +26592,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26831
26592
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26832
26593
|
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26833
26594
|
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;
|
|
26834
|
-
const resolveSettings$
|
|
26595
|
+
const resolveSettings$29 = (settings) => {
|
|
26835
26596
|
const reactDoctor = settings?.["react-doctor"];
|
|
26836
26597
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
26837
26598
|
return reactDoctor.jsxNoScriptUrl ?? {};
|
|
@@ -26853,7 +26614,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26853
26614
|
recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
|
|
26854
26615
|
category: "Security",
|
|
26855
26616
|
create: (context) => {
|
|
26856
|
-
const options = resolveSettings$
|
|
26617
|
+
const options = resolveSettings$29(context.settings);
|
|
26857
26618
|
return { JSXOpeningElement(node) {
|
|
26858
26619
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26859
26620
|
const elementName = resolveJsxElementType(node);
|
|
@@ -26874,6 +26635,283 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26874
26635
|
}
|
|
26875
26636
|
});
|
|
26876
26637
|
//#endregion
|
|
26638
|
+
//#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
|
|
26639
|
+
const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
|
|
26640
|
+
const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
|
|
26641
|
+
const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
|
|
26642
|
+
const resolveSettings$28 = (settings) => {
|
|
26643
|
+
const reactDoctor = settings?.["react-doctor"];
|
|
26644
|
+
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
|
|
26645
|
+
const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
|
|
26646
|
+
const linkComponents = /* @__PURE__ */ new Map();
|
|
26647
|
+
for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
|
|
26648
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26649
|
+
const linkAttribute = entry.linkAttribute ?? "href";
|
|
26650
|
+
linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
|
|
26651
|
+
}
|
|
26652
|
+
const formComponents = /* @__PURE__ */ new Map();
|
|
26653
|
+
for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
|
|
26654
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26655
|
+
const formAttribute = entry.formAttribute ?? "action";
|
|
26656
|
+
formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
|
|
26657
|
+
}
|
|
26658
|
+
return {
|
|
26659
|
+
enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
|
|
26660
|
+
warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
|
|
26661
|
+
allowReferrer: ruleSettings.allowReferrer ?? false,
|
|
26662
|
+
links: ruleSettings.links ?? true,
|
|
26663
|
+
forms: ruleSettings.forms ?? false,
|
|
26664
|
+
linkComponents,
|
|
26665
|
+
formComponents
|
|
26666
|
+
};
|
|
26667
|
+
};
|
|
26668
|
+
const isExternalLink = (href) => href.includes("//");
|
|
26669
|
+
const matchHrefExpression = (expression, state) => {
|
|
26670
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
|
|
26671
|
+
if (isExternalLink(expression.value)) state.isExternal = true;
|
|
26672
|
+
return;
|
|
26673
|
+
}
|
|
26674
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
26675
|
+
state.isDynamic = true;
|
|
26676
|
+
return;
|
|
26677
|
+
}
|
|
26678
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26679
|
+
matchHrefExpression(expression.consequent, state);
|
|
26680
|
+
matchHrefExpression(expression.alternate, state);
|
|
26681
|
+
}
|
|
26682
|
+
};
|
|
26683
|
+
const checkHref = (attributeValue, enforceDynamicLinks) => {
|
|
26684
|
+
const state = {
|
|
26685
|
+
isExternal: false,
|
|
26686
|
+
isDynamic: false
|
|
26687
|
+
};
|
|
26688
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
|
|
26689
|
+
else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
|
|
26690
|
+
if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
|
|
26691
|
+
return !(state.isExternal || state.isDynamic);
|
|
26692
|
+
};
|
|
26693
|
+
const checkRelValue = (text, allowReferrer) => {
|
|
26694
|
+
const tokens = text.split(/\s+/);
|
|
26695
|
+
if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
|
|
26696
|
+
return tokens.some((token) => token.toLowerCase() === "noreferrer");
|
|
26697
|
+
};
|
|
26698
|
+
const matchRelExpression = (expression, allowReferrer) => {
|
|
26699
|
+
const empty = {
|
|
26700
|
+
combined: false,
|
|
26701
|
+
testName: "",
|
|
26702
|
+
consequent: false,
|
|
26703
|
+
alternate: false
|
|
26704
|
+
};
|
|
26705
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26706
|
+
combined: checkRelValue(expression.value, allowReferrer),
|
|
26707
|
+
testName: "",
|
|
26708
|
+
consequent: false,
|
|
26709
|
+
alternate: false
|
|
26710
|
+
};
|
|
26711
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26712
|
+
const consequent = matchRelExpression(expression.consequent, allowReferrer);
|
|
26713
|
+
const alternate = matchRelExpression(expression.alternate, allowReferrer);
|
|
26714
|
+
const test = expression.test;
|
|
26715
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26716
|
+
combined: consequent.combined && alternate.combined,
|
|
26717
|
+
testName: test.name,
|
|
26718
|
+
consequent: consequent.combined,
|
|
26719
|
+
alternate: alternate.combined
|
|
26720
|
+
};
|
|
26721
|
+
return {
|
|
26722
|
+
combined: consequent.combined && alternate.combined,
|
|
26723
|
+
testName: "",
|
|
26724
|
+
consequent: consequent.combined,
|
|
26725
|
+
alternate: alternate.combined
|
|
26726
|
+
};
|
|
26727
|
+
}
|
|
26728
|
+
return empty;
|
|
26729
|
+
};
|
|
26730
|
+
const checkRel = (attributeValue, allowReferrer) => {
|
|
26731
|
+
const empty = {
|
|
26732
|
+
combined: false,
|
|
26733
|
+
testName: "",
|
|
26734
|
+
consequent: false,
|
|
26735
|
+
alternate: false
|
|
26736
|
+
};
|
|
26737
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26738
|
+
combined: checkRelValue(attributeValue.value, allowReferrer),
|
|
26739
|
+
testName: "",
|
|
26740
|
+
consequent: false,
|
|
26741
|
+
alternate: false
|
|
26742
|
+
};
|
|
26743
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26744
|
+
const expression = attributeValue.expression;
|
|
26745
|
+
if (expression.type === "JSXEmptyExpression") return empty;
|
|
26746
|
+
return matchRelExpression(expression, allowReferrer);
|
|
26747
|
+
}
|
|
26748
|
+
return empty;
|
|
26749
|
+
};
|
|
26750
|
+
const matchTargetExpression = (expression) => {
|
|
26751
|
+
const empty = {
|
|
26752
|
+
combined: false,
|
|
26753
|
+
testName: "",
|
|
26754
|
+
consequent: false,
|
|
26755
|
+
alternate: false
|
|
26756
|
+
};
|
|
26757
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26758
|
+
combined: expression.value.toLowerCase() === "_blank",
|
|
26759
|
+
testName: "",
|
|
26760
|
+
consequent: false,
|
|
26761
|
+
alternate: false
|
|
26762
|
+
};
|
|
26763
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26764
|
+
const consequent = matchTargetExpression(expression.consequent);
|
|
26765
|
+
const alternate = matchTargetExpression(expression.alternate);
|
|
26766
|
+
const test = expression.test;
|
|
26767
|
+
const combined = consequent.combined || alternate.combined;
|
|
26768
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26769
|
+
combined,
|
|
26770
|
+
testName: test.name,
|
|
26771
|
+
consequent: consequent.combined,
|
|
26772
|
+
alternate: alternate.combined
|
|
26773
|
+
};
|
|
26774
|
+
return {
|
|
26775
|
+
combined,
|
|
26776
|
+
testName: "",
|
|
26777
|
+
consequent: consequent.combined,
|
|
26778
|
+
alternate: alternate.combined
|
|
26779
|
+
};
|
|
26780
|
+
}
|
|
26781
|
+
return empty;
|
|
26782
|
+
};
|
|
26783
|
+
const checkTarget = (attributeValue) => {
|
|
26784
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26785
|
+
combined: attributeValue.value.toLowerCase() === "_blank",
|
|
26786
|
+
testName: "",
|
|
26787
|
+
consequent: false,
|
|
26788
|
+
alternate: false
|
|
26789
|
+
};
|
|
26790
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26791
|
+
const expression = attributeValue.expression;
|
|
26792
|
+
if (expression.type === "JSXEmptyExpression") return {
|
|
26793
|
+
combined: false,
|
|
26794
|
+
testName: "",
|
|
26795
|
+
consequent: false,
|
|
26796
|
+
alternate: false
|
|
26797
|
+
};
|
|
26798
|
+
return matchTargetExpression(expression);
|
|
26799
|
+
}
|
|
26800
|
+
return {
|
|
26801
|
+
combined: false,
|
|
26802
|
+
testName: "",
|
|
26803
|
+
consequent: false,
|
|
26804
|
+
alternate: false
|
|
26805
|
+
};
|
|
26806
|
+
};
|
|
26807
|
+
const jsxNoTargetBlank = defineRule({
|
|
26808
|
+
id: "jsx-no-target-blank",
|
|
26809
|
+
title: "Unsafe target=_blank link",
|
|
26810
|
+
severity: "warn",
|
|
26811
|
+
recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
|
|
26812
|
+
category: "Security",
|
|
26813
|
+
create: (context) => {
|
|
26814
|
+
const settings = resolveSettings$28(context.settings);
|
|
26815
|
+
const isLink = (tagName) => {
|
|
26816
|
+
if (!settings.links) return false;
|
|
26817
|
+
if (tagName === "a") return true;
|
|
26818
|
+
return settings.linkComponents.has(tagName);
|
|
26819
|
+
};
|
|
26820
|
+
const isForm = (tagName) => {
|
|
26821
|
+
if (!settings.forms) return false;
|
|
26822
|
+
if (tagName === "form") return true;
|
|
26823
|
+
return settings.formComponents.has(tagName);
|
|
26824
|
+
};
|
|
26825
|
+
return { JSXOpeningElement(node) {
|
|
26826
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26827
|
+
const tagName = resolveJsxElementType(node);
|
|
26828
|
+
if (!tagName) return;
|
|
26829
|
+
if (!isLink(tagName) && !isForm(tagName)) return;
|
|
26830
|
+
const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
|
|
26831
|
+
const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
|
|
26832
|
+
let targetTuple = {
|
|
26833
|
+
combined: false,
|
|
26834
|
+
testName: "",
|
|
26835
|
+
consequent: false,
|
|
26836
|
+
alternate: false
|
|
26837
|
+
};
|
|
26838
|
+
let relTuple = {
|
|
26839
|
+
combined: false,
|
|
26840
|
+
testName: "",
|
|
26841
|
+
consequent: false,
|
|
26842
|
+
alternate: false
|
|
26843
|
+
};
|
|
26844
|
+
let isHrefValid = true;
|
|
26845
|
+
let hasHrefValue = false;
|
|
26846
|
+
let warnSpread = false;
|
|
26847
|
+
let targetReportNode = node.name;
|
|
26848
|
+
let spreadReportNode = null;
|
|
26849
|
+
for (const attribute of node.attributes) {
|
|
26850
|
+
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
26851
|
+
if (settings.warnOnSpreadAttributes) {
|
|
26852
|
+
warnSpread = true;
|
|
26853
|
+
spreadReportNode = attribute;
|
|
26854
|
+
targetTuple = {
|
|
26855
|
+
combined: false,
|
|
26856
|
+
testName: "",
|
|
26857
|
+
consequent: false,
|
|
26858
|
+
alternate: false
|
|
26859
|
+
};
|
|
26860
|
+
relTuple = {
|
|
26861
|
+
combined: false,
|
|
26862
|
+
testName: "",
|
|
26863
|
+
consequent: false,
|
|
26864
|
+
alternate: false
|
|
26865
|
+
};
|
|
26866
|
+
isHrefValid = false;
|
|
26867
|
+
hasHrefValue = true;
|
|
26868
|
+
}
|
|
26869
|
+
continue;
|
|
26870
|
+
}
|
|
26871
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
26872
|
+
const attributeName = attribute.name;
|
|
26873
|
+
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
26874
|
+
const propName = attributeName.name;
|
|
26875
|
+
const value = attribute.value;
|
|
26876
|
+
if (propName === "target") {
|
|
26877
|
+
if (value) {
|
|
26878
|
+
targetTuple = checkTarget(value);
|
|
26879
|
+
targetReportNode = value;
|
|
26880
|
+
}
|
|
26881
|
+
} else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
|
|
26882
|
+
if (value) {
|
|
26883
|
+
hasHrefValue = true;
|
|
26884
|
+
isHrefValid = checkHref(value, settings.enforceDynamicLinks);
|
|
26885
|
+
}
|
|
26886
|
+
} else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
|
|
26887
|
+
}
|
|
26888
|
+
if (warnSpread) {
|
|
26889
|
+
if (hasHrefValue && isHrefValid || relTuple.combined) return;
|
|
26890
|
+
context.report({
|
|
26891
|
+
node: spreadReportNode ?? node,
|
|
26892
|
+
message: SPREAD_MESSAGE
|
|
26893
|
+
});
|
|
26894
|
+
return;
|
|
26895
|
+
}
|
|
26896
|
+
if (!isHrefValid) {
|
|
26897
|
+
if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
|
|
26898
|
+
const consequentBad = targetTuple.consequent && !relTuple.consequent;
|
|
26899
|
+
const alternateBad = targetTuple.alternate && !relTuple.alternate;
|
|
26900
|
+
if (consequentBad || alternateBad) context.report({
|
|
26901
|
+
node: targetReportNode,
|
|
26902
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26903
|
+
});
|
|
26904
|
+
return;
|
|
26905
|
+
}
|
|
26906
|
+
if (targetTuple.combined && !relTuple.combined) context.report({
|
|
26907
|
+
node: targetReportNode,
|
|
26908
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26909
|
+
});
|
|
26910
|
+
}
|
|
26911
|
+
} };
|
|
26912
|
+
}
|
|
26913
|
+
});
|
|
26914
|
+
//#endregion
|
|
26877
26915
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
26878
26916
|
const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
26879
26917
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -28531,30 +28569,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
|
|
|
28531
28569
|
const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
|
|
28532
28570
|
//#endregion
|
|
28533
28571
|
//#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
|
-
};
|
|
28558
28572
|
const nextjsMissingMetadata = defineRule({
|
|
28559
28573
|
id: "nextjs-missing-metadata",
|
|
28560
28574
|
title: "Page missing metadata for search previews",
|
|
@@ -28574,7 +28588,6 @@ const nextjsMissingMetadata = defineRule({
|
|
|
28574
28588
|
if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
|
|
28575
28589
|
return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
|
|
28576
28590
|
})) return;
|
|
28577
|
-
if (isRedirectOnlyDefaultExport(programNode, context)) return;
|
|
28578
28591
|
if (hasAncestorMetadataLayout(context.filename ?? "")) return;
|
|
28579
28592
|
context.report({
|
|
28580
28593
|
node: programNode,
|
|
@@ -34703,19 +34716,6 @@ const noCascadingSetState = defineRetiredRule({
|
|
|
34703
34716
|
recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
|
|
34704
34717
|
});
|
|
34705
34718
|
//#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
|
|
34719
34719
|
//#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
|
|
34720
34720
|
const getRefCurrentSymbol = (node, context) => {
|
|
34721
34721
|
const expression = stripParenExpression(node);
|
|
@@ -35244,8 +35244,19 @@ const isBuiltinNamespaceCallee = (callee) => {
|
|
|
35244
35244
|
}
|
|
35245
35245
|
return false;
|
|
35246
35246
|
};
|
|
35247
|
+
const getReactUseCallbackSource = (reference, context) => {
|
|
35248
|
+
const identifier = reference.identifier;
|
|
35249
|
+
const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
|
|
35250
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
35251
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
35252
|
+
if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
|
|
35253
|
+
allowGlobalReactNamespace: true,
|
|
35254
|
+
resolveNamedAliases: true
|
|
35255
|
+
})) return initializer;
|
|
35256
|
+
return null;
|
|
35257
|
+
};
|
|
35247
35258
|
const getDependencyStateRefs = (analysis, context, dependencyReference) => {
|
|
35248
|
-
const useCallbackCall =
|
|
35259
|
+
const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
|
|
35249
35260
|
if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
35250
35261
|
const dependencyList = useCallbackCall.arguments?.[1];
|
|
35251
35262
|
if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
@@ -36833,6 +36844,19 @@ const noDefaultProps = defineRule({
|
|
|
36833
36844
|
} })
|
|
36834
36845
|
});
|
|
36835
36846
|
//#endregion
|
|
36847
|
+
//#region src/plugin/utils/is-component-function.ts
|
|
36848
|
+
const isFunctionAssignedToComponent = (functionNode) => {
|
|
36849
|
+
let cursor = functionNode.parent ?? null;
|
|
36850
|
+
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
36851
|
+
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
36852
|
+
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
36853
|
+
};
|
|
36854
|
+
const isComponentFunction$1 = (node) => {
|
|
36855
|
+
if (!isFunctionLike$1(node)) return false;
|
|
36856
|
+
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
36857
|
+
return isFunctionAssignedToComponent(node);
|
|
36858
|
+
};
|
|
36859
|
+
//#endregion
|
|
36836
36860
|
//#region src/plugin/utils/create-component-prop-stack-tracker.ts
|
|
36837
36861
|
const extractDestructuredPropNames = (params) => {
|
|
36838
36862
|
const propNames = /* @__PURE__ */ new Set();
|
|
@@ -38746,17 +38770,6 @@ const noDynamicImportPath = defineRule({
|
|
|
38746
38770
|
})
|
|
38747
38771
|
});
|
|
38748
38772
|
//#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
|
|
38760
38773
|
//#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
|
|
38761
38774
|
const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
|
|
38762
38775
|
"each",
|
|
@@ -38901,16 +38914,7 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
|
|
|
38901
38914
|
}
|
|
38902
38915
|
return dependencyStateSymbolIds;
|
|
38903
38916
|
};
|
|
38904
|
-
const
|
|
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) => {
|
|
38917
|
+
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38914
38918
|
const analysisFunctions = new Set([effectCallback]);
|
|
38915
38919
|
const pendingFunctions = [effectCallback];
|
|
38916
38920
|
while (pendingFunctions.length > 0) {
|
|
@@ -38918,9 +38922,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeSta
|
|
|
38918
38922
|
if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
|
|
38919
38923
|
walkInsideStatementBlocks(currentFunction.body, (child) => {
|
|
38920
38924
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38921
|
-
const invokedFunction =
|
|
38925
|
+
const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
38922
38926
|
if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
|
|
38923
|
-
if (isFunctionLike$1(invokedFunction) &&
|
|
38927
|
+
if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
|
|
38924
38928
|
analysisFunctions.add(invokedFunction);
|
|
38925
38929
|
pendingFunctions.push(invokedFunction);
|
|
38926
38930
|
});
|
|
@@ -38933,51 +38937,7 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
38933
38937
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
38934
38938
|
}
|
|
38935
38939
|
};
|
|
38936
|
-
const
|
|
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()) => {
|
|
38940
|
+
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38981
38941
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38982
38942
|
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
38983
38943
|
const literalValue = unwrappedExpression.value;
|
|
@@ -38985,49 +38945,46 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
|
|
|
38985
38945
|
return null;
|
|
38986
38946
|
}
|
|
38987
38947
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
38988
|
-
|
|
38989
|
-
if (symbol?.id === stateSymbolId) return stateValue;
|
|
38990
|
-
if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
|
|
38948
|
+
if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
|
|
38991
38949
|
if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
|
|
38992
|
-
if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
|
|
38993
38950
|
const immutableSymbol = scopes.symbolFor(unwrappedExpression);
|
|
38994
38951
|
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)
|
|
38952
|
+
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
|
|
38996
38953
|
}
|
|
38997
38954
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
|
|
38998
38955
|
if (unwrappedExpression.operator === "void") return { value: void 0 };
|
|
38999
38956
|
if (unwrappedExpression.operator !== "!") return null;
|
|
39000
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38957
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39001
38958
|
return argumentValue ? { value: !argumentValue.value } : null;
|
|
39002
38959
|
}
|
|
39003
38960
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
39004
38961
|
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
|
|
38962
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39006
38963
|
return argumentValue ? { value: Boolean(argumentValue.value) } : null;
|
|
39007
38964
|
}
|
|
39008
38965
|
return null;
|
|
39009
38966
|
}
|
|
39010
38967
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
39011
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38968
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39012
38969
|
if (!leftValue) return null;
|
|
39013
38970
|
if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
|
|
39014
38971
|
if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
|
|
39015
38972
|
if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
|
|
39016
|
-
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38973
|
+
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39017
38974
|
}
|
|
39018
38975
|
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
39019
|
-
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38976
|
+
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39020
38977
|
if (!testValue) return null;
|
|
39021
|
-
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38978
|
+
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39022
38979
|
}
|
|
39023
38980
|
if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
|
|
39024
|
-
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38981
|
+
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39025
38982
|
if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
|
|
39026
38983
|
return null;
|
|
39027
38984
|
}
|
|
39028
38985
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
39029
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
39030
|
-
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38986
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38987
|
+
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39031
38988
|
if (!leftValue || !rightValue) return null;
|
|
39032
38989
|
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
39033
38990
|
const areEqual = leftValue.value === rightValue.value;
|
|
@@ -39066,7 +39023,8 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39066
39023
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39067
39024
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39068
39025
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
39069
|
-
const
|
|
39026
|
+
const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
|
|
39027
|
+
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
39070
39028
|
if (!stateName) return;
|
|
39071
39029
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
39072
39030
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -39082,27 +39040,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39082
39040
|
const isGlobalBooleanCall = (node, scopes) => {
|
|
39083
39041
|
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
|
|
39084
39042
|
};
|
|
39085
|
-
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes
|
|
39043
|
+
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
|
|
39086
39044
|
let currentNode = workNode;
|
|
39087
39045
|
while (currentNode.parent) {
|
|
39088
39046
|
const parentNode = currentNode.parent;
|
|
39089
39047
|
if (isFunctionLike$1(parentNode)) break;
|
|
39090
39048
|
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
39091
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue
|
|
39049
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39092
39050
|
if (testValue) {
|
|
39093
39051
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39094
39052
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39095
39053
|
}
|
|
39096
39054
|
}
|
|
39097
39055
|
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
39098
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue
|
|
39056
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39099
39057
|
if (testValue) {
|
|
39100
39058
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39101
39059
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39102
39060
|
}
|
|
39103
39061
|
}
|
|
39104
39062
|
if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
|
|
39105
|
-
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue
|
|
39063
|
+
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
|
|
39106
39064
|
if (leftValue) {
|
|
39107
39065
|
if (parentNode.operator === "&&" && !leftValue.value) return false;
|
|
39108
39066
|
if (parentNode.operator === "||" && leftValue.value) return false;
|
|
@@ -39114,7 +39072,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39114
39072
|
if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
|
|
39115
39073
|
const earlierStatement = parentNode.body[index];
|
|
39116
39074
|
if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
|
|
39117
|
-
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue
|
|
39075
|
+
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
|
|
39118
39076
|
}
|
|
39119
39077
|
}
|
|
39120
39078
|
currentNode = parentNode;
|
|
@@ -39124,93 +39082,24 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39124
39082
|
const isReaderWorkNode = (node, analysisFunctions, scopes) => {
|
|
39125
39083
|
if (isNodeOfType(node, "CallExpression")) {
|
|
39126
39084
|
if (isGlobalBooleanCall(node, scopes)) return false;
|
|
39127
|
-
const invokedFunction =
|
|
39085
|
+
const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
|
|
39128
39086
|
return !invokedFunction || !analysisFunctions.has(invokedFunction);
|
|
39129
39087
|
}
|
|
39130
39088
|
return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
|
|
39131
39089
|
};
|
|
39132
|
-
const
|
|
39133
|
-
if (
|
|
39134
|
-
const
|
|
39135
|
-
|
|
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);
|
|
39090
|
+
const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
|
|
39091
|
+
if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
|
|
39092
|
+
for (const writtenValue of writeInfo.values) {
|
|
39093
|
+
const stateValue = { value: writtenValue };
|
|
39187
39094
|
let didFindReachableWork = false;
|
|
39188
|
-
|
|
39189
|
-
if (didFindReachableWork) return;
|
|
39190
|
-
if (
|
|
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;
|
|
39095
|
+
visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
|
|
39096
|
+
if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
|
|
39097
|
+
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
|
|
39201
39098
|
});
|
|
39202
39099
|
if (didFindReachableWork) return true;
|
|
39203
39100
|
}
|
|
39204
39101
|
return false;
|
|
39205
39102
|
};
|
|
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
|
-
};
|
|
39214
39103
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
39215
39104
|
const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
39216
39105
|
"clear",
|
|
@@ -39221,499 +39110,26 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
39221
39110
|
"keys",
|
|
39222
39111
|
"values"
|
|
39223
39112
|
]);
|
|
39224
|
-
const
|
|
39225
|
-
|
|
39226
|
-
|
|
39227
|
-
"
|
|
39228
|
-
|
|
39229
|
-
|
|
39230
|
-
|
|
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;
|
|
39113
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
|
|
39114
|
+
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
39115
|
+
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
39116
|
+
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
39117
|
+
const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
|
|
39118
|
+
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
39119
|
+
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
39436
39120
|
}
|
|
39121
|
+
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39437
39122
|
}
|
|
39438
|
-
return true;
|
|
39439
|
-
};
|
|
39440
|
-
const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
|
|
39441
|
-
for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
|
|
39123
|
+
if (isNodeOfType(returnedValue, "Identifier")) return true;
|
|
39442
39124
|
return false;
|
|
39443
39125
|
};
|
|
39444
|
-
const
|
|
39445
|
-
|
|
39446
|
-
|
|
39447
|
-
if (
|
|
39448
|
-
return
|
|
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;
|
|
39126
|
+
const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
39127
|
+
const isBrowserStorageReceiver = (receiver) => {
|
|
39128
|
+
if (!receiver) return false;
|
|
39129
|
+
if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
|
|
39130
|
+
if (isNodeOfType(receiver, "MemberExpression")) return isNodeOfType(receiver.property, "Identifier") && STORAGE_GLOBAL_NAMES.has(receiver.property.name);
|
|
39713
39131
|
return false;
|
|
39714
39132
|
};
|
|
39715
|
-
const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
|
|
39716
|
-
const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
|
|
39717
39133
|
const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
|
|
39718
39134
|
const collectStorageHookSetterNames = (componentBody) => {
|
|
39719
39135
|
const setterNames = /* @__PURE__ */ new Set();
|
|
@@ -39755,6 +39171,14 @@ const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, sco
|
|
|
39755
39171
|
allowUnboundBareCalls: true,
|
|
39756
39172
|
resolveNamedAliases: true
|
|
39757
39173
|
});
|
|
39174
|
+
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
39175
|
+
const expression = stripParenExpression(rawExpression);
|
|
39176
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39177
|
+
const symbol = scopes.symbolFor(expression);
|
|
39178
|
+
if (!symbol) return null;
|
|
39179
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39180
|
+
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
39181
|
+
};
|
|
39758
39182
|
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
39759
39183
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
39760
39184
|
const symbol = scopes.symbolFor(openingElement.name);
|
|
@@ -39762,33 +39186,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
|
39762
39186
|
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
39763
39187
|
};
|
|
39764
39188
|
const isDirectHostJsxRef = (symbol, scopes) => {
|
|
39765
|
-
let
|
|
39766
|
-
const
|
|
39767
|
-
|
|
39768
|
-
|
|
39769
|
-
|
|
39770
|
-
if (!
|
|
39771
|
-
|
|
39772
|
-
|
|
39773
|
-
|
|
39774
|
-
|
|
39775
|
-
|
|
39776
|
-
|
|
39777
|
-
|
|
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;
|
|
39189
|
+
let hostRefCount = 0;
|
|
39190
|
+
for (const reference of symbol.references) {
|
|
39191
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39192
|
+
const container = expression.parent;
|
|
39193
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
39194
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
39195
|
+
const attribute = container.parent;
|
|
39196
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
39197
|
+
const openingElement = attribute.parent;
|
|
39198
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
39199
|
+
hostRefCount += 1;
|
|
39200
|
+
}
|
|
39201
|
+
return hostRefCount > 0;
|
|
39792
39202
|
};
|
|
39793
39203
|
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
39794
39204
|
const identifier = stripParenExpression(expression);
|
|
@@ -39875,11 +39285,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
|
|
|
39875
39285
|
}
|
|
39876
39286
|
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39877
39287
|
if (getStaticPropertyName(expression) === "current") {
|
|
39878
|
-
const symbol =
|
|
39879
|
-
allowUnboundBareCalls: true,
|
|
39880
|
-
includeCreateRef: true,
|
|
39881
|
-
resolveNamedAliases: true
|
|
39882
|
-
});
|
|
39288
|
+
const symbol = getDirectReactRefSymbol(expression.object, scopes);
|
|
39883
39289
|
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
39884
39290
|
}
|
|
39885
39291
|
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
@@ -39897,80 +39303,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
|
|
|
39897
39303
|
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
39898
39304
|
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
39899
39305
|
};
|
|
39900
|
-
const
|
|
39901
|
-
|
|
39902
|
-
if (isNodeOfType(
|
|
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
|
-
}
|
|
39306
|
+
const isExternalSyncNode = (node) => {
|
|
39307
|
+
if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
|
|
39308
|
+
if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
|
|
39965
39309
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
39966
|
-
if (
|
|
39310
|
+
if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
|
|
39967
39311
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
39968
39312
|
const propertyName = getStaticPropertyName(node.callee);
|
|
39969
39313
|
if (propertyName === null) return false;
|
|
39970
|
-
if (
|
|
39971
|
-
if (
|
|
39972
|
-
if (
|
|
39973
|
-
|
|
39314
|
+
if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
|
|
39315
|
+
if (isBrowserStorageReceiver(node.callee.object)) return true;
|
|
39316
|
+
if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
|
|
39317
|
+
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39318
|
+
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39974
39319
|
};
|
|
39975
39320
|
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
|
|
39976
39321
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -39979,7 +39324,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
|
|
|
39979
39324
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
|
|
39980
39325
|
let didFindExternalCall = false;
|
|
39981
39326
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39982
|
-
if (isExternalSyncNode(child
|
|
39327
|
+
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39983
39328
|
});
|
|
39984
39329
|
return didFindExternalCall;
|
|
39985
39330
|
};
|
|
@@ -40001,11 +39346,11 @@ const noEffectChain = defineRule({
|
|
|
40001
39346
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
40002
39347
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
40003
39348
|
const stateIdentifier = binding.declarator.id.elements[0];
|
|
40004
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
40005
39349
|
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
40006
39350
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
40007
39351
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
40008
39352
|
}
|
|
39353
|
+
const setterIdentifier = binding.declarator.id.elements[1];
|
|
40009
39354
|
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
40010
39355
|
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
40011
39356
|
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
@@ -40018,11 +39363,10 @@ const noEffectChain = defineRule({
|
|
|
40018
39363
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
40019
39364
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
40020
39365
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
40021
|
-
const stateWrites = collectStateWritesInEffect(
|
|
39366
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
|
|
40022
39367
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
40023
39368
|
effectInfos.push({
|
|
40024
39369
|
node: effectCall,
|
|
40025
|
-
callback,
|
|
40026
39370
|
dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
|
|
40027
39371
|
stateWrites,
|
|
40028
39372
|
analysisFunctions,
|
|
@@ -40039,10 +39383,10 @@ const noEffectChain = defineRule({
|
|
|
40039
39383
|
if (readerEffect.isExternalSync) continue;
|
|
40040
39384
|
if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
|
|
40041
39385
|
let chainedStateName = null;
|
|
40042
|
-
for (const writtenName of writerEffect.stateWrites
|
|
39386
|
+
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
40043
39387
|
const writtenStateSymbolId = stateSymbolIds.get(writtenName);
|
|
40044
39388
|
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
40045
|
-
if (!canStateWriteReachReaderWork(
|
|
39389
|
+
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
40046
39390
|
chainedStateName = writtenName;
|
|
40047
39391
|
break;
|
|
40048
39392
|
}
|
|
@@ -44827,6 +44171,16 @@ const noManyBooleanProps = defineRule({
|
|
|
44827
44171
|
}
|
|
44828
44172
|
});
|
|
44829
44173
|
//#endregion
|
|
44174
|
+
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
44175
|
+
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
44176
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
44177
|
+
const callee = stripParenExpression(node.callee);
|
|
44178
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
44179
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
44180
|
+
const receiver = stripParenExpression(callee.object);
|
|
44181
|
+
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
44182
|
+
};
|
|
44183
|
+
//#endregion
|
|
44830
44184
|
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
44831
44185
|
const REACT_USE_STATE_OPTIONS = {
|
|
44832
44186
|
allowGlobalReactNamespace: true,
|
|
@@ -47216,103 +46570,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
47216
46570
|
"useVisibility",
|
|
47217
46571
|
"useWindowSize"
|
|
47218
46572
|
]);
|
|
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
|
-
};
|
|
47316
46573
|
const isCallbackPropReference = (analysis, ref) => {
|
|
47317
46574
|
if (!isProp(analysis, ref)) return false;
|
|
47318
46575
|
const identifier = ref.identifier;
|
|
@@ -47345,9 +46602,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
47345
46602
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
47346
46603
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
47347
46604
|
};
|
|
47348
|
-
const getLocalHookExternalStateProof = (analysis, ref
|
|
46605
|
+
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47349
46606
|
let hookFunction = resolveToFunction(ref);
|
|
47350
|
-
let didResolveThroughResultBinding = false;
|
|
47351
46607
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
47352
46608
|
const definitionNode = definition.node;
|
|
47353
46609
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -47358,27 +46614,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
|
47358
46614
|
const calleeReference = getRef(analysis, callee);
|
|
47359
46615
|
if (!calleeReference) continue;
|
|
47360
46616
|
hookFunction = resolveToFunction(calleeReference);
|
|
47361
|
-
if (hookFunction)
|
|
47362
|
-
didResolveThroughResultBinding = true;
|
|
47363
|
-
break;
|
|
47364
|
-
}
|
|
46617
|
+
if (hookFunction) break;
|
|
47365
46618
|
}
|
|
47366
46619
|
if (!hookFunction) return null;
|
|
47367
|
-
|
|
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) : []);
|
|
46620
|
+
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
47372
46621
|
if (returnedReferences.length === 0) return null;
|
|
47373
46622
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
47374
46623
|
};
|
|
47375
|
-
const
|
|
47376
|
-
const
|
|
47377
|
-
if (!isNodeOfType(
|
|
47378
|
-
const
|
|
47379
|
-
if (
|
|
47380
|
-
|
|
46624
|
+
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
46625
|
+
const identifier = ref.identifier;
|
|
46626
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46627
|
+
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
46628
|
+
if (localHookProof !== null) return localHookProof;
|
|
46629
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46630
|
+
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46631
|
+
const node = def.node;
|
|
46632
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46633
|
+
const initializer = stripParenExpression(node.init);
|
|
46634
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46635
|
+
const callee = stripParenExpression(initializer.callee);
|
|
46636
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
46637
|
+
}));
|
|
47381
46638
|
};
|
|
46639
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47382
46640
|
const isCalleePosition = (identifier) => {
|
|
47383
46641
|
const parent = identifier.parent;
|
|
47384
46642
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -47439,35 +46697,25 @@ const noPassDataToParent = defineRule({
|
|
|
47439
46697
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
47440
46698
|
}
|
|
47441
46699
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47442
|
-
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
47443
46700
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
47444
46701
|
if (isNodeOfType(argument, "Identifier")) {
|
|
47445
46702
|
const argumentRef = getRef(analysis, argument);
|
|
47446
46703
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
47447
46704
|
}
|
|
47448
46705
|
return getDownstreamRefs(analysis, argument);
|
|
47449
|
-
}).flatMap((argumentRef) =>
|
|
47450
|
-
|
|
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
|
-
}
|
|
46706
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
46707
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
47461
46708
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
47462
|
-
|
|
47463
|
-
if (
|
|
46709
|
+
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46710
|
+
if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
|
|
47464
46711
|
if (isProp(analysis, argRef)) return false;
|
|
47465
|
-
if (isUseRefIdentifier(
|
|
46712
|
+
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
47466
46713
|
if (isRefCurrent(argRef)) return false;
|
|
47467
46714
|
if (isConstant(argRef)) return false;
|
|
47468
46715
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
47469
46716
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
47470
46717
|
if (resolveToFunction(argRef)) return false;
|
|
46718
|
+
const argIdentifier = argRef.identifier;
|
|
47471
46719
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
47472
46720
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
47473
46721
|
return true;
|
|
@@ -56458,6 +55706,17 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
56458
55706
|
}
|
|
56459
55707
|
});
|
|
56460
55708
|
//#endregion
|
|
55709
|
+
//#region src/plugin/utils/get-require-call-source.ts
|
|
55710
|
+
const getRequireCallSource = (expression) => {
|
|
55711
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
55712
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
55713
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
55714
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
55715
|
+
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
55716
|
+
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55717
|
+
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55718
|
+
};
|
|
55719
|
+
//#endregion
|
|
56461
55720
|
//#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
|
|
56462
55721
|
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
56463
55722
|
const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
|
|
@@ -59784,7 +59043,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
59784
59043
|
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);
|
|
59785
59044
|
//#endregion
|
|
59786
59045
|
//#region src/plugin/rules/security-scan/repository-secret-file.ts
|
|
59787
|
-
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(
|
|
59046
|
+
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
|
|
59788
59047
|
const repositorySecretFile = defineRule({
|
|
59789
59048
|
id: "repository-secret-file",
|
|
59790
59049
|
title: "Secret file checked into repository",
|
|
@@ -61165,717 +60424,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
61165
60424
|
return found;
|
|
61166
60425
|
};
|
|
61167
60426
|
//#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
|
|
61879
60427
|
//#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
|
|
61880
60428
|
const isInsideConditionTest = (identifier, stopAt) => {
|
|
61881
60429
|
let current = identifier;
|
|
@@ -61997,8 +60545,7 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
61997
60545
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
61998
60546
|
const bindings = collectUseStateBindings(componentBody, context.scopes);
|
|
61999
60547
|
if (bindings.length === 0) return;
|
|
62000
|
-
|
|
62001
|
-
if (renderReachableExpressions.length === 0) return;
|
|
60548
|
+
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
62002
60549
|
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
|
|
62003
60550
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
62004
60551
|
const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
|
|
@@ -62012,22 +60559,12 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
62012
60559
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
62013
60560
|
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
|
|
62014
60561
|
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;
|
|
62023
60562
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
62024
60563
|
walkAst(componentBody, (child) => {
|
|
62025
60564
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
62026
60565
|
});
|
|
62027
60566
|
for (const binding of bindings) {
|
|
62028
60567
|
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;
|
|
62031
60568
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
62032
60569
|
const setterSuffix = binding.setterName.slice(3);
|
|
62033
60570
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|
|
@@ -73203,6 +71740,18 @@ const reactDoctorRules = [
|
|
|
73203
71740
|
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
73204
71741
|
}
|
|
73205
71742
|
},
|
|
71743
|
+
{
|
|
71744
|
+
key: "react-doctor/jsx-no-target-blank",
|
|
71745
|
+
id: "jsx-no-target-blank",
|
|
71746
|
+
source: "react-doctor",
|
|
71747
|
+
originallyExternal: true,
|
|
71748
|
+
rule: {
|
|
71749
|
+
...jsxNoTargetBlank,
|
|
71750
|
+
framework: "global",
|
|
71751
|
+
category: "Security",
|
|
71752
|
+
requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
|
|
71753
|
+
}
|
|
71754
|
+
},
|
|
73206
71755
|
{
|
|
73207
71756
|
key: "react-doctor/jsx-no-undef",
|
|
73208
71757
|
id: "jsx-no-undef",
|