oxlint-plugin-react-doctor 0.8.1-dev.90e794f → 0.8.1-dev.a2f9bf4
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 +635 -2223
- 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;
|
|
@@ -20674,7 +20517,7 @@ const collectHandlerReferencedNames = (root) => {
|
|
|
20674
20517
|
//#endregion
|
|
20675
20518
|
//#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
|
|
20676
20519
|
const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
|
|
20677
|
-
const COMPONENT_NAME_PATTERN
|
|
20520
|
+
const COMPONENT_NAME_PATTERN = /^[A-Z]/;
|
|
20678
20521
|
const isImportedSelectAtom = (callExpression) => {
|
|
20679
20522
|
if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
|
|
20680
20523
|
const localName = callExpression.callee.name;
|
|
@@ -20710,14 +20553,14 @@ const getHandlerNamedBindingName = (functionNode) => {
|
|
|
20710
20553
|
const containingFunctionIsComponentOrHook = (functionNode) => {
|
|
20711
20554
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
|
|
20712
20555
|
const declaredName = functionNode.id.name;
|
|
20713
|
-
return COMPONENT_NAME_PATTERN
|
|
20556
|
+
return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
|
|
20714
20557
|
}
|
|
20715
20558
|
let cursor = functionNode.parent ?? null;
|
|
20716
20559
|
while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
20717
20560
|
if (!cursor) return false;
|
|
20718
20561
|
if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
|
|
20719
20562
|
if (!isNodeOfType(cursor.id, "Identifier")) return false;
|
|
20720
|
-
return COMPONENT_NAME_PATTERN
|
|
20563
|
+
return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
|
|
20721
20564
|
};
|
|
20722
20565
|
const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
20723
20566
|
let didFindRenderPathInvocation = false;
|
|
@@ -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([
|
|
@@ -28063,7 +28101,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
28063
28101
|
recommendation: "Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.",
|
|
28064
28102
|
scan: scanByPattern({
|
|
28065
28103
|
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
28066
|
-
pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo
|
|
28104
|
+
pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
|
|
28067
28105
|
message: "MDX/markdown rendering code may evaluate user or repository content during SSR or static generation."
|
|
28068
28106
|
})
|
|
28069
28107
|
});
|
|
@@ -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();
|
|
@@ -39745,16 +39161,23 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
|
|
|
39745
39161
|
});
|
|
39746
39162
|
return didFindOpaqueSetterCall;
|
|
39747
39163
|
};
|
|
39748
|
-
const
|
|
39164
|
+
const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
|
|
39749
39165
|
allowGlobalReactNamespace: true,
|
|
39750
39166
|
allowUnboundBareCalls: true,
|
|
39751
39167
|
resolveNamedAliases: true
|
|
39752
|
-
})
|
|
39753
|
-
const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
|
|
39168
|
+
}) || isReactApiCall(expression, "createRef", scopes, {
|
|
39754
39169
|
allowGlobalReactNamespace: true,
|
|
39755
39170
|
allowUnboundBareCalls: true,
|
|
39756
39171
|
resolveNamedAliases: true
|
|
39757
|
-
});
|
|
39172
|
+
}));
|
|
39173
|
+
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
39174
|
+
const expression = stripParenExpression(rawExpression);
|
|
39175
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39176
|
+
const symbol = scopes.symbolFor(expression);
|
|
39177
|
+
if (!symbol) return null;
|
|
39178
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39179
|
+
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
39180
|
+
};
|
|
39758
39181
|
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
39759
39182
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
39760
39183
|
const symbol = scopes.symbolFor(openingElement.name);
|
|
@@ -39762,33 +39185,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
|
39762
39185
|
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
39763
39186
|
};
|
|
39764
39187
|
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;
|
|
39188
|
+
let hostRefCount = 0;
|
|
39189
|
+
for (const reference of symbol.references) {
|
|
39190
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39191
|
+
const container = expression.parent;
|
|
39192
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
39193
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
39194
|
+
const attribute = container.parent;
|
|
39195
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
39196
|
+
const openingElement = attribute.parent;
|
|
39197
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
39198
|
+
hostRefCount += 1;
|
|
39199
|
+
}
|
|
39200
|
+
return hostRefCount > 0;
|
|
39792
39201
|
};
|
|
39793
39202
|
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
39794
39203
|
const identifier = stripParenExpression(expression);
|
|
@@ -39798,18 +39207,7 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
|
39798
39207
|
const rawFirstParameter = callback.params?.[0];
|
|
39799
39208
|
const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
|
|
39800
39209
|
const symbol = scopes.symbolFor(identifier);
|
|
39801
|
-
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter
|
|
39802
|
-
if (reference.flag !== "read") return false;
|
|
39803
|
-
let referenceRoot = reference.identifier;
|
|
39804
|
-
while (referenceRoot.parent) {
|
|
39805
|
-
const parent = referenceRoot.parent;
|
|
39806
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
|
|
39807
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
|
|
39808
|
-
if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
|
|
39809
|
-
referenceRoot = parent;
|
|
39810
|
-
}
|
|
39811
|
-
return true;
|
|
39812
|
-
}));
|
|
39210
|
+
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
|
|
39813
39211
|
};
|
|
39814
39212
|
const getDirectReactRefCall = (symbol, scopes) => {
|
|
39815
39213
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
@@ -39817,38 +39215,15 @@ const getDirectReactRefCall = (symbol, scopes) => {
|
|
|
39817
39215
|
const expression = stripParenExpression(initializer);
|
|
39818
39216
|
return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
|
|
39819
39217
|
};
|
|
39820
|
-
const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
|
|
39821
|
-
const expression = stripParenExpression(rawExpression);
|
|
39822
|
-
return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
|
|
39823
|
-
};
|
|
39824
|
-
const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
|
|
39825
|
-
if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
|
|
39826
|
-
const [initialValue] = refCall.arguments;
|
|
39827
|
-
return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
|
|
39828
|
-
};
|
|
39829
|
-
const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
|
|
39830
|
-
const assignment = currentExpression.parent;
|
|
39831
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
|
|
39832
|
-
const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
39833
|
-
return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
|
|
39834
|
-
};
|
|
39835
39218
|
const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
39836
|
-
const
|
|
39837
|
-
|
|
39838
|
-
const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
|
|
39839
|
-
const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
|
|
39840
|
-
if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
|
|
39219
|
+
const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
|
|
39220
|
+
if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
|
|
39841
39221
|
let intrinsicValueWriteCount = 0;
|
|
39842
|
-
let lazyEmptyMapInitializationCount = 0;
|
|
39843
39222
|
for (const reference of symbol.references) {
|
|
39844
39223
|
const identifier = findTransparentExpressionRoot(reference.identifier);
|
|
39845
39224
|
const currentMember = identifier.parent;
|
|
39846
39225
|
if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
|
|
39847
39226
|
const currentExpression = findTransparentExpressionRoot(currentMember);
|
|
39848
|
-
if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
|
|
39849
|
-
lazyEmptyMapInitializationCount += 1;
|
|
39850
|
-
continue;
|
|
39851
|
-
}
|
|
39852
39227
|
const methodMember = currentExpression.parent;
|
|
39853
39228
|
if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
|
|
39854
39229
|
const methodName = getStaticPropertyName(methodMember);
|
|
@@ -39861,7 +39236,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
|
39861
39236
|
if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
|
|
39862
39237
|
intrinsicValueWriteCount += 1;
|
|
39863
39238
|
}
|
|
39864
|
-
return intrinsicValueWriteCount > 0
|
|
39239
|
+
return intrinsicValueWriteCount > 0;
|
|
39865
39240
|
};
|
|
39866
39241
|
const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39867
39242
|
const expression = stripParenExpression(rawExpression);
|
|
@@ -39875,11 +39250,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
|
|
|
39875
39250
|
}
|
|
39876
39251
|
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39877
39252
|
if (getStaticPropertyName(expression) === "current") {
|
|
39878
|
-
const symbol =
|
|
39879
|
-
allowUnboundBareCalls: true,
|
|
39880
|
-
includeCreateRef: true,
|
|
39881
|
-
resolveNamedAliases: true
|
|
39882
|
-
});
|
|
39253
|
+
const symbol = getDirectReactRefSymbol(expression.object, scopes);
|
|
39883
39254
|
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
39884
39255
|
}
|
|
39885
39256
|
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
@@ -39897,80 +39268,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
|
|
|
39897
39268
|
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
39898
39269
|
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
39899
39270
|
};
|
|
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
|
-
}
|
|
39271
|
+
const isExternalSyncNode = (node) => {
|
|
39272
|
+
if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
|
|
39273
|
+
if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
|
|
39965
39274
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
39966
|
-
if (
|
|
39275
|
+
if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
|
|
39967
39276
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
39968
39277
|
const propertyName = getStaticPropertyName(node.callee);
|
|
39969
39278
|
if (propertyName === null) return false;
|
|
39970
|
-
if (
|
|
39971
|
-
if (
|
|
39972
|
-
if (
|
|
39973
|
-
|
|
39279
|
+
if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
|
|
39280
|
+
if (isBrowserStorageReceiver(node.callee.object)) return true;
|
|
39281
|
+
if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
|
|
39282
|
+
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39283
|
+
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39974
39284
|
};
|
|
39975
39285
|
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
|
|
39976
39286
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -39979,7 +39289,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
|
|
|
39979
39289
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
|
|
39980
39290
|
let didFindExternalCall = false;
|
|
39981
39291
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39982
|
-
if (isExternalSyncNode(child
|
|
39292
|
+
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39983
39293
|
});
|
|
39984
39294
|
return didFindExternalCall;
|
|
39985
39295
|
};
|
|
@@ -40001,11 +39311,11 @@ const noEffectChain = defineRule({
|
|
|
40001
39311
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
40002
39312
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
40003
39313
|
const stateIdentifier = binding.declarator.id.elements[0];
|
|
40004
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
40005
39314
|
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
40006
39315
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
40007
39316
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
40008
39317
|
}
|
|
39318
|
+
const setterIdentifier = binding.declarator.id.elements[1];
|
|
40009
39319
|
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
40010
39320
|
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
40011
39321
|
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
@@ -40018,11 +39328,10 @@ const noEffectChain = defineRule({
|
|
|
40018
39328
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
40019
39329
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
40020
39330
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
40021
|
-
const stateWrites = collectStateWritesInEffect(
|
|
39331
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
|
|
40022
39332
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
40023
39333
|
effectInfos.push({
|
|
40024
39334
|
node: effectCall,
|
|
40025
|
-
callback,
|
|
40026
39335
|
dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
|
|
40027
39336
|
stateWrites,
|
|
40028
39337
|
analysisFunctions,
|
|
@@ -40039,10 +39348,10 @@ const noEffectChain = defineRule({
|
|
|
40039
39348
|
if (readerEffect.isExternalSync) continue;
|
|
40040
39349
|
if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
|
|
40041
39350
|
let chainedStateName = null;
|
|
40042
|
-
for (const writtenName of writerEffect.stateWrites
|
|
39351
|
+
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
40043
39352
|
const writtenStateSymbolId = stateSymbolIds.get(writtenName);
|
|
40044
39353
|
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
40045
|
-
if (!canStateWriteReachReaderWork(
|
|
39354
|
+
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
40046
39355
|
chainedStateName = writtenName;
|
|
40047
39356
|
break;
|
|
40048
39357
|
}
|
|
@@ -44827,6 +44136,16 @@ const noManyBooleanProps = defineRule({
|
|
|
44827
44136
|
}
|
|
44828
44137
|
});
|
|
44829
44138
|
//#endregion
|
|
44139
|
+
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
44140
|
+
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
44141
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
44142
|
+
const callee = stripParenExpression(node.callee);
|
|
44143
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
44144
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
44145
|
+
const receiver = stripParenExpression(callee.object);
|
|
44146
|
+
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
44147
|
+
};
|
|
44148
|
+
//#endregion
|
|
44830
44149
|
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
44831
44150
|
const REACT_USE_STATE_OPTIONS = {
|
|
44832
44151
|
allowGlobalReactNamespace: true,
|
|
@@ -47216,103 +46535,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
47216
46535
|
"useVisibility",
|
|
47217
46536
|
"useWindowSize"
|
|
47218
46537
|
]);
|
|
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
46538
|
const isCallbackPropReference = (analysis, ref) => {
|
|
47317
46539
|
if (!isProp(analysis, ref)) return false;
|
|
47318
46540
|
const identifier = ref.identifier;
|
|
@@ -47345,9 +46567,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
47345
46567
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
47346
46568
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
47347
46569
|
};
|
|
47348
|
-
const getLocalHookExternalStateProof = (analysis, ref
|
|
46570
|
+
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47349
46571
|
let hookFunction = resolveToFunction(ref);
|
|
47350
|
-
let didResolveThroughResultBinding = false;
|
|
47351
46572
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
47352
46573
|
const definitionNode = definition.node;
|
|
47353
46574
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -47358,27 +46579,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
|
47358
46579
|
const calleeReference = getRef(analysis, callee);
|
|
47359
46580
|
if (!calleeReference) continue;
|
|
47360
46581
|
hookFunction = resolveToFunction(calleeReference);
|
|
47361
|
-
if (hookFunction)
|
|
47362
|
-
didResolveThroughResultBinding = true;
|
|
47363
|
-
break;
|
|
47364
|
-
}
|
|
46582
|
+
if (hookFunction) break;
|
|
47365
46583
|
}
|
|
47366
46584
|
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) : []);
|
|
46585
|
+
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
47372
46586
|
if (returnedReferences.length === 0) return null;
|
|
47373
46587
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
47374
46588
|
};
|
|
47375
|
-
const
|
|
47376
|
-
const
|
|
47377
|
-
if (!isNodeOfType(
|
|
47378
|
-
const
|
|
47379
|
-
if (
|
|
47380
|
-
|
|
46589
|
+
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
46590
|
+
const identifier = ref.identifier;
|
|
46591
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46592
|
+
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
46593
|
+
if (localHookProof !== null) return localHookProof;
|
|
46594
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46595
|
+
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46596
|
+
const node = def.node;
|
|
46597
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46598
|
+
const initializer = stripParenExpression(node.init);
|
|
46599
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46600
|
+
const callee = stripParenExpression(initializer.callee);
|
|
46601
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
46602
|
+
}));
|
|
47381
46603
|
};
|
|
46604
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47382
46605
|
const isCalleePosition = (identifier) => {
|
|
47383
46606
|
const parent = identifier.parent;
|
|
47384
46607
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -47439,35 +46662,25 @@ const noPassDataToParent = defineRule({
|
|
|
47439
46662
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
47440
46663
|
}
|
|
47441
46664
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47442
|
-
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
47443
46665
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
47444
46666
|
if (isNodeOfType(argument, "Identifier")) {
|
|
47445
46667
|
const argumentRef = getRef(analysis, argument);
|
|
47446
46668
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
47447
46669
|
}
|
|
47448
46670
|
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
|
-
}
|
|
46671
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
46672
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
47461
46673
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
47462
|
-
|
|
47463
|
-
if (
|
|
46674
|
+
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46675
|
+
if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
|
|
47464
46676
|
if (isProp(analysis, argRef)) return false;
|
|
47465
|
-
if (isUseRefIdentifier(
|
|
46677
|
+
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
47466
46678
|
if (isRefCurrent(argRef)) return false;
|
|
47467
46679
|
if (isConstant(argRef)) return false;
|
|
47468
46680
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
47469
46681
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
47470
46682
|
if (resolveToFunction(argRef)) return false;
|
|
46683
|
+
const argIdentifier = argRef.identifier;
|
|
47471
46684
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
47472
46685
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
47473
46686
|
return true;
|
|
@@ -56458,6 +55671,17 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
56458
55671
|
}
|
|
56459
55672
|
});
|
|
56460
55673
|
//#endregion
|
|
55674
|
+
//#region src/plugin/utils/get-require-call-source.ts
|
|
55675
|
+
const getRequireCallSource = (expression) => {
|
|
55676
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
55677
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
55678
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
55679
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
55680
|
+
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
55681
|
+
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55682
|
+
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55683
|
+
};
|
|
55684
|
+
//#endregion
|
|
56461
55685
|
//#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
|
|
56462
55686
|
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
56463
55687
|
const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
|
|
@@ -57482,8 +56706,7 @@ const preferUseReducer = defineRule({
|
|
|
57482
56706
|
//#endregion
|
|
57483
56707
|
//#region src/plugin/rules/security-scan/utils/is-public-debug-artifact-path.ts
|
|
57484
56708
|
const LOCALE_DIRECTORY_PATTERN = /(?:^|\/)(?:locales?|i18n|lang|langs|translations?)\//i;
|
|
57485
|
-
const
|
|
57486
|
-
const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN.test(relativePath);
|
|
56709
|
+
const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && /(?:^|\/)(?:\.env(?:\.[^/]*)?|[^/]*(?:debug|crash|trace|stack|report|dump|phpinfo)[^/]*\.(?:txt|log|json|html?)|[^/]+\.log)$/i.test(relativePath);
|
|
57487
56710
|
//#endregion
|
|
57488
56711
|
//#region src/plugin/rules/security-scan/public-debug-artifact.ts
|
|
57489
56712
|
const publicDebugArtifact = defineRule({
|
|
@@ -59785,7 +59008,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
59785
59008
|
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);
|
|
59786
59009
|
//#endregion
|
|
59787
59010
|
//#region src/plugin/rules/security-scan/repository-secret-file.ts
|
|
59788
|
-
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(
|
|
59011
|
+
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
|
|
59789
59012
|
const repositorySecretFile = defineRule({
|
|
59790
59013
|
id: "repository-secret-file",
|
|
59791
59014
|
title: "Secret file checked into repository",
|
|
@@ -61166,717 +60389,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
61166
60389
|
return found;
|
|
61167
60390
|
};
|
|
61168
60391
|
//#endregion
|
|
61169
|
-
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
61170
|
-
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
61171
|
-
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
61172
|
-
const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61173
|
-
"assign",
|
|
61174
|
-
"defineProperties",
|
|
61175
|
-
"defineProperty",
|
|
61176
|
-
"setPrototypeOf"
|
|
61177
|
-
]);
|
|
61178
|
-
const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61179
|
-
"defineProperty",
|
|
61180
|
-
"deleteProperty",
|
|
61181
|
-
"set",
|
|
61182
|
-
"setPrototypeOf"
|
|
61183
|
-
]);
|
|
61184
|
-
const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
|
|
61185
|
-
const containsGlobalLocationSnapshotRead = (node, scopes) => {
|
|
61186
|
-
let didFindLocationSnapshotRead = false;
|
|
61187
|
-
walkAst(node, (child) => {
|
|
61188
|
-
if (didFindLocationSnapshotRead) return false;
|
|
61189
|
-
if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61190
|
-
if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
|
|
61191
|
-
didFindLocationSnapshotRead = true;
|
|
61192
|
-
return false;
|
|
61193
|
-
});
|
|
61194
|
-
return didFindLocationSnapshotRead;
|
|
61195
|
-
};
|
|
61196
|
-
const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
|
|
61197
|
-
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
61198
|
-
if (isFunctionLike$1(localFunction)) return localFunction;
|
|
61199
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
61200
|
-
let callbackSource = unwrappedExpression;
|
|
61201
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
61202
|
-
const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
|
|
61203
|
-
if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
|
|
61204
|
-
callbackSource = callbackSymbol.initializer;
|
|
61205
|
-
}
|
|
61206
|
-
const unwrappedCallbackSource = stripParenExpression(callbackSource);
|
|
61207
|
-
if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
|
|
61208
|
-
const callback = unwrappedCallbackSource.arguments?.[0];
|
|
61209
|
-
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
61210
|
-
return resolveExactLocalFunction(stripParenExpression(callback), scopes);
|
|
61211
|
-
};
|
|
61212
|
-
const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61213
|
-
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
61214
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
61215
|
-
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;
|
|
61216
|
-
const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
|
|
61217
|
-
const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
|
|
61218
|
-
if (!initializer) return false;
|
|
61219
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61220
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
61221
|
-
if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
|
|
61222
|
-
let didFindAliasedLocationSnapshotRead = false;
|
|
61223
|
-
walkAst(initializer, (child) => {
|
|
61224
|
-
if (didFindAliasedLocationSnapshotRead) return false;
|
|
61225
|
-
if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61226
|
-
if (!isNodeOfType(child, "Identifier")) return;
|
|
61227
|
-
if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
|
|
61228
|
-
didFindAliasedLocationSnapshotRead = true;
|
|
61229
|
-
return false;
|
|
61230
|
-
}
|
|
61231
|
-
});
|
|
61232
|
-
return didFindAliasedLocationSnapshotRead;
|
|
61233
|
-
};
|
|
61234
|
-
const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
|
|
61235
|
-
if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
|
|
61236
|
-
for (const statement of componentBody.body ?? []) {
|
|
61237
|
-
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
|
|
61238
|
-
if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
|
|
61239
|
-
continue;
|
|
61240
|
-
}
|
|
61241
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
61242
|
-
for (const declarator of statement.declarations ?? []) {
|
|
61243
|
-
if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
61244
|
-
if (!directRenderNames.has(declarator.id.name)) continue;
|
|
61245
|
-
if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
|
|
61246
|
-
}
|
|
61247
|
-
}
|
|
61248
|
-
return false;
|
|
61249
|
-
};
|
|
61250
|
-
const isGlobalHistoryLocationMutation = (node, scopes) => {
|
|
61251
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
61252
|
-
const callee = stripParenExpression(node.callee);
|
|
61253
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61254
|
-
const methodName = getStaticPropertyName(callee);
|
|
61255
|
-
return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
|
|
61256
|
-
};
|
|
61257
|
-
const getStaticLocationChangeEvent = (node) => {
|
|
61258
|
-
if (!node) return null;
|
|
61259
|
-
const eventNameNode = stripParenExpression(node);
|
|
61260
|
-
if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
|
|
61261
|
-
return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
|
|
61262
|
-
};
|
|
61263
|
-
const addToSetIndex = (index, key, value) => {
|
|
61264
|
-
const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
61265
|
-
indexedValues.add(value);
|
|
61266
|
-
index.set(key, indexedValues);
|
|
61267
|
-
};
|
|
61268
|
-
const getSynchronousInvocationExpression = (functionNode, scopes) => {
|
|
61269
|
-
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61270
|
-
if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
|
|
61271
|
-
const parent = functionExpressionRoot.parent;
|
|
61272
|
-
return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
|
|
61273
|
-
};
|
|
61274
|
-
const getLocationListenerOperation = (callExpression, scopes) => {
|
|
61275
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61276
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
61277
|
-
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
61278
|
-
if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
|
|
61279
|
-
if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
|
|
61280
|
-
const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
|
|
61281
|
-
if (!eventName) return null;
|
|
61282
|
-
const listenerExpression = callExpression.arguments?.[1];
|
|
61283
|
-
if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
|
|
61284
|
-
const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
|
|
61285
|
-
if (!isFunctionLike$1(listenerFunction)) return null;
|
|
61286
|
-
const captureArgument = callExpression.arguments?.[2];
|
|
61287
|
-
const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
|
|
61288
|
-
allowComputedString: true,
|
|
61289
|
-
allowIndeterminateEntries: true
|
|
61290
|
-
});
|
|
61291
|
-
return {
|
|
61292
|
-
operation: methodName === "addEventListener" ? "add" : "remove",
|
|
61293
|
-
registration: {
|
|
61294
|
-
callExpression,
|
|
61295
|
-
listenerFunction,
|
|
61296
|
-
capture,
|
|
61297
|
-
eventName
|
|
61298
|
-
}
|
|
61299
|
-
};
|
|
61300
|
-
};
|
|
61301
|
-
const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
|
|
61302
|
-
const index = {
|
|
61303
|
-
componentFunction,
|
|
61304
|
-
context,
|
|
61305
|
-
effectCallbacks: /* @__PURE__ */ new Set(),
|
|
61306
|
-
expressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61307
|
-
historyMutationsByOwner: /* @__PURE__ */ new Map(),
|
|
61308
|
-
awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61309
|
-
callSitesByFunction: /* @__PURE__ */ new Map(),
|
|
61310
|
-
calledFunctionByExpression: /* @__PURE__ */ new Map(),
|
|
61311
|
-
synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
|
|
61312
|
-
synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
|
|
61313
|
-
callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
|
|
61314
|
-
identifierCalls: /* @__PURE__ */ new Set(),
|
|
61315
|
-
listenerRegistrations: [],
|
|
61316
|
-
listenerRemovals: [],
|
|
61317
|
-
mountedListenerFunctions: /* @__PURE__ */ new Set(),
|
|
61318
|
-
mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
|
|
61319
|
-
synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
|
|
61320
|
-
};
|
|
61321
|
-
walkAst(componentBody, (child) => {
|
|
61322
|
-
if (isFunctionLike$1(child)) {
|
|
61323
|
-
const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
|
|
61324
|
-
if (invocationExpression) {
|
|
61325
|
-
addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
|
|
61326
|
-
addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
|
|
61327
|
-
}
|
|
61328
|
-
return;
|
|
61329
|
-
}
|
|
61330
|
-
const owner = context.cfg.enclosingFunction(child);
|
|
61331
|
-
if (!owner) return;
|
|
61332
|
-
if (isNodeOfType(child, "AwaitExpression")) {
|
|
61333
|
-
addToSetIndex(index.awaitExpressionsByOwner, owner, child);
|
|
61334
|
-
return;
|
|
61335
|
-
}
|
|
61336
|
-
if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
|
|
61337
|
-
addToSetIndex(index.expressionsByOwner, owner, child);
|
|
61338
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
61339
|
-
if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
|
|
61340
|
-
if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
|
|
61341
|
-
const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
|
|
61342
|
-
if (isFunctionLike$1(calledFunction)) {
|
|
61343
|
-
addToSetIndex(index.callSitesByFunction, calledFunction, child);
|
|
61344
|
-
index.calledFunctionByExpression.set(child, calledFunction);
|
|
61345
|
-
}
|
|
61346
|
-
if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
61347
|
-
allowGlobalReactNamespace: true,
|
|
61348
|
-
allowUnboundBareCalls: true,
|
|
61349
|
-
resolveNamedAliases: true
|
|
61350
|
-
})) {
|
|
61351
|
-
const effectCallback = getEffectCallback(child, context.scopes);
|
|
61352
|
-
if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
|
|
61353
|
-
}
|
|
61354
|
-
const listenerOperation = getLocationListenerOperation(child, context.scopes);
|
|
61355
|
-
if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
|
|
61356
|
-
else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
|
|
61357
|
-
}
|
|
61358
|
-
});
|
|
61359
|
-
return index;
|
|
61360
|
-
};
|
|
61361
|
-
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61362
|
-
let current = descendant;
|
|
61363
|
-
while (current && current !== ancestor) {
|
|
61364
|
-
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61365
|
-
current = current.parent;
|
|
61366
|
-
}
|
|
61367
|
-
return current === ancestor;
|
|
61368
|
-
};
|
|
61369
|
-
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
61370
|
-
const firstBranches = /* @__PURE__ */ new Map();
|
|
61371
|
-
let current = firstNode;
|
|
61372
|
-
while (current?.parent) {
|
|
61373
|
-
const parent = current.parent;
|
|
61374
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
|
|
61375
|
-
if (current !== firstNode && isFunctionLike$1(current)) break;
|
|
61376
|
-
current = parent;
|
|
61377
|
-
}
|
|
61378
|
-
current = secondNode;
|
|
61379
|
-
while (current?.parent) {
|
|
61380
|
-
const parent = current.parent;
|
|
61381
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
|
|
61382
|
-
const firstBranch = firstBranches.get(parent);
|
|
61383
|
-
if (firstBranch && firstBranch !== current) return true;
|
|
61384
|
-
}
|
|
61385
|
-
if (current !== secondNode && isFunctionLike$1(current)) break;
|
|
61386
|
-
current = parent;
|
|
61387
|
-
}
|
|
61388
|
-
return false;
|
|
61389
|
-
};
|
|
61390
|
-
const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
|
|
61391
|
-
const constraintsBySymbolId = /* @__PURE__ */ new Map();
|
|
61392
|
-
const recordConstraint = (test, requiredTestValue) => {
|
|
61393
|
-
let expression = stripParenExpression(test);
|
|
61394
|
-
let requiredValue = requiredTestValue;
|
|
61395
|
-
while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
61396
|
-
requiredValue = !requiredValue;
|
|
61397
|
-
expression = stripParenExpression(expression.argument);
|
|
61398
|
-
}
|
|
61399
|
-
if (!isNodeOfType(expression, "Identifier")) return;
|
|
61400
|
-
const symbol = scopes.symbolFor(expression);
|
|
61401
|
-
if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
61402
|
-
const existingConstraint = constraintsBySymbolId.get(symbol.id);
|
|
61403
|
-
constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
|
|
61404
|
-
};
|
|
61405
|
-
let current = node;
|
|
61406
|
-
while (current?.parent && current !== functionBoundary) {
|
|
61407
|
-
const parent = current.parent;
|
|
61408
|
-
if (isNodeOfType(parent, "IfStatement")) {
|
|
61409
|
-
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61410
|
-
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61411
|
-
} else if (isNodeOfType(parent, "ConditionalExpression")) {
|
|
61412
|
-
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61413
|
-
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61414
|
-
} else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
|
|
61415
|
-
if (parent.operator === "&&") recordConstraint(parent.left, true);
|
|
61416
|
-
if (parent.operator === "||") recordConstraint(parent.left, false);
|
|
61417
|
-
}
|
|
61418
|
-
if (current !== node && isFunctionLike$1(current)) break;
|
|
61419
|
-
current = parent;
|
|
61420
|
-
}
|
|
61421
|
-
return constraintsBySymbolId;
|
|
61422
|
-
};
|
|
61423
|
-
const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
|
|
61424
|
-
const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
|
|
61425
|
-
const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
|
|
61426
|
-
for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
|
|
61427
|
-
for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
|
|
61428
|
-
for (const [symbolId, firstRequiredValue] of firstConstraints) {
|
|
61429
|
-
const secondRequiredValue = secondConstraints.get(symbolId);
|
|
61430
|
-
if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
|
|
61431
|
-
}
|
|
61432
|
-
return false;
|
|
61433
|
-
};
|
|
61434
|
-
const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
|
|
61435
|
-
let current = node;
|
|
61436
|
-
while (current && current !== functionBoundary) {
|
|
61437
|
-
if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
|
|
61438
|
-
current = current.parent;
|
|
61439
|
-
}
|
|
61440
|
-
return false;
|
|
61441
|
-
};
|
|
61442
|
-
const getTransparentExpressionBindingIdentifier = (expression) => {
|
|
61443
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
61444
|
-
const parent = expressionRoot.parent;
|
|
61445
|
-
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
61446
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
|
|
61447
|
-
return null;
|
|
61448
|
-
};
|
|
61449
|
-
const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
|
|
61450
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
61451
|
-
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61452
|
-
const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
|
|
61453
|
-
if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
|
|
61454
|
-
const callbackCall = functionExpressionRoot.parent;
|
|
61455
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61456
|
-
return getTransparentExpressionBindingIdentifier(callbackCall);
|
|
61457
|
-
};
|
|
61458
|
-
const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
|
|
61459
|
-
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61460
|
-
if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
|
|
61461
|
-
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61462
|
-
return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
|
|
61463
|
-
};
|
|
61464
|
-
const canNodeReachNode = (sourceNode, targetNode, index) => {
|
|
61465
|
-
const { context } = index;
|
|
61466
|
-
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
61467
|
-
if (!isNodeReachableWithinFunction(targetNode, context)) return false;
|
|
61468
|
-
const sourceOwner = context.cfg.enclosingFunction(sourceNode);
|
|
61469
|
-
const targetOwner = context.cfg.enclosingFunction(targetNode);
|
|
61470
|
-
if (!sourceOwner || sourceOwner !== targetOwner) return false;
|
|
61471
|
-
if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
|
|
61472
|
-
if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
|
|
61473
|
-
if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
|
|
61474
|
-
if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
|
|
61475
|
-
const functionCfg = context.cfg.cfgFor(sourceOwner);
|
|
61476
|
-
const sourceBlock = functionCfg?.blockOf(sourceNode);
|
|
61477
|
-
const targetBlock = functionCfg?.blockOf(targetNode);
|
|
61478
|
-
if (!functionCfg || !sourceBlock || !targetBlock) return false;
|
|
61479
|
-
if (sourceBlock === targetBlock) {
|
|
61480
|
-
const sourceStart = getRangeStart(sourceNode);
|
|
61481
|
-
const targetStart = getRangeStart(targetNode);
|
|
61482
|
-
return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
|
|
61483
|
-
}
|
|
61484
|
-
const visitedBlocks = new Set([sourceBlock]);
|
|
61485
|
-
const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
|
|
61486
|
-
while (pendingBlocks.length > 0) {
|
|
61487
|
-
const block = pendingBlocks.pop();
|
|
61488
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61489
|
-
if (block === targetBlock) return true;
|
|
61490
|
-
visitedBlocks.add(block);
|
|
61491
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61492
|
-
}
|
|
61493
|
-
return false;
|
|
61494
|
-
};
|
|
61495
|
-
const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
|
|
61496
|
-
let currentNode = functionNode;
|
|
61497
|
-
while (currentNode) {
|
|
61498
|
-
if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
|
|
61499
|
-
const openingElement = currentNode.parent;
|
|
61500
|
-
return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
|
|
61501
|
-
}
|
|
61502
|
-
if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
|
|
61503
|
-
currentNode = currentNode.parent;
|
|
61504
|
-
}
|
|
61505
|
-
return null;
|
|
61506
|
-
};
|
|
61507
|
-
const isProvenNonEscapingVoidRead = (identifier) => {
|
|
61508
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
61509
|
-
return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
|
|
61510
|
-
};
|
|
61511
|
-
const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
|
|
61512
|
-
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61513
|
-
const parent = referenceRoot.parent;
|
|
61514
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
|
|
61515
|
-
const aliasSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61516
|
-
const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
|
|
61517
|
-
const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
|
|
61518
|
-
return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
|
|
61519
|
-
}
|
|
61520
|
-
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61521
|
-
const callbackResultRoot = findTransparentExpressionRoot(parent);
|
|
61522
|
-
const resultParent = callbackResultRoot.parent;
|
|
61523
|
-
if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
|
|
61524
|
-
const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
|
|
61525
|
-
return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
|
|
61526
|
-
};
|
|
61527
|
-
const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
|
|
61528
|
-
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61529
|
-
const parent = referenceRoot.parent;
|
|
61530
|
-
if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
|
|
61531
|
-
const memberRoot = findTransparentExpressionRoot(parent);
|
|
61532
|
-
const callExpression = memberRoot.parent;
|
|
61533
|
-
const methodName = getStaticPropertyName(parent);
|
|
61534
|
-
if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
|
|
61535
|
-
}
|
|
61536
|
-
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
|
|
61537
|
-
const callee = stripParenExpression(parent.callee);
|
|
61538
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61539
|
-
const methodName = getStaticPropertyName(callee);
|
|
61540
|
-
if (methodName === null) return false;
|
|
61541
|
-
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);
|
|
61542
|
-
};
|
|
61543
|
-
const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61544
|
-
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61545
|
-
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
|
|
61546
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61547
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
61548
|
-
const escapeAnchors = [];
|
|
61549
|
-
for (const reference of symbol.references) {
|
|
61550
|
-
if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
|
|
61551
|
-
const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
|
|
61552
|
-
if (!aliasSymbol) {
|
|
61553
|
-
escapeAnchors.push(reference.identifier);
|
|
61554
|
-
continue;
|
|
61555
|
-
}
|
|
61556
|
-
const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
|
|
61557
|
-
if (!aliasEscapeAnchors) return null;
|
|
61558
|
-
escapeAnchors.push(...aliasEscapeAnchors);
|
|
61559
|
-
}
|
|
61560
|
-
return escapeAnchors.length > 0 ? escapeAnchors : null;
|
|
61561
|
-
};
|
|
61562
|
-
const findReadonlyJsxAggregateRoot = (jsxElement) => {
|
|
61563
|
-
let aggregateRoot = findTransparentExpressionRoot(jsxElement);
|
|
61564
|
-
while (aggregateRoot.parent) {
|
|
61565
|
-
const parent = aggregateRoot.parent;
|
|
61566
|
-
if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
|
|
61567
|
-
aggregateRoot = parent;
|
|
61568
|
-
continue;
|
|
61569
|
-
}
|
|
61570
|
-
if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
|
|
61571
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61572
|
-
continue;
|
|
61573
|
-
}
|
|
61574
|
-
if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
|
|
61575
|
-
aggregateRoot = findTransparentExpressionRoot(parent.parent);
|
|
61576
|
-
continue;
|
|
61577
|
-
}
|
|
61578
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
|
|
61579
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61580
|
-
continue;
|
|
61581
|
-
}
|
|
61582
|
-
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
|
|
61583
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61584
|
-
continue;
|
|
61585
|
-
}
|
|
61586
|
-
if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
|
|
61587
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61588
|
-
continue;
|
|
61589
|
-
}
|
|
61590
|
-
break;
|
|
61591
|
-
}
|
|
61592
|
-
return aggregateRoot;
|
|
61593
|
-
};
|
|
61594
|
-
const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
|
|
61595
|
-
const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
|
|
61596
|
-
const parent = jsxValueRoot.parent;
|
|
61597
|
-
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
|
|
61598
|
-
if (!isNodeOfType(parent.id, "Identifier")) return null;
|
|
61599
|
-
const bindingSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61600
|
-
if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
|
|
61601
|
-
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
|
|
61602
|
-
};
|
|
61603
|
-
const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
|
|
61604
|
-
if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
|
|
61605
|
-
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61606
|
-
if (!bindingIdentifier) {
|
|
61607
|
-
const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
|
|
61608
|
-
return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
|
|
61609
|
-
}
|
|
61610
|
-
return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
|
|
61611
|
-
};
|
|
61612
|
-
const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
|
|
61613
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
61614
|
-
if (!bindingIdentifier) return null;
|
|
61615
|
-
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61616
|
-
if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
|
|
61617
|
-
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
|
|
61618
|
-
};
|
|
61619
|
-
const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
61620
|
-
if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
|
|
61621
|
-
const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
|
|
61622
|
-
const executionOwner = index.context.cfg.enclosingFunction(executionNode);
|
|
61623
|
-
if (!aliasOwner || !executionOwner) return false;
|
|
61624
|
-
if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
|
|
61625
|
-
if (visitedFunctionNodes.has(executionOwner)) return false;
|
|
61626
|
-
const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
|
|
61627
|
-
nextVisitedFunctionNodes.add(executionOwner);
|
|
61628
|
-
if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
|
|
61629
|
-
const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
|
|
61630
|
-
if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
|
|
61631
|
-
const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
|
|
61632
|
-
if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
|
|
61633
|
-
const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
|
|
61634
|
-
return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
|
|
61635
|
-
};
|
|
61636
|
-
const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
|
|
61637
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61638
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
61639
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
61640
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
61641
|
-
let symbol = index.context.scopes.symbolFor(callee);
|
|
61642
|
-
while (symbol) {
|
|
61643
|
-
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61644
|
-
visitedSymbolIds.add(symbol.id);
|
|
61645
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
61646
|
-
if (!initializer) return symbol.id;
|
|
61647
|
-
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
|
|
61648
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
61649
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
|
|
61650
|
-
const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
|
|
61651
|
-
if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
|
|
61652
|
-
symbol = initializerSymbol;
|
|
61653
|
-
}
|
|
61654
|
-
return null;
|
|
61655
|
-
};
|
|
61656
|
-
const collectExactReadonlyCalleeCalls = (index) => {
|
|
61657
|
-
for (const callExpression of index.identifierCalls) {
|
|
61658
|
-
const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
|
|
61659
|
-
if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
|
|
61660
|
-
}
|
|
61661
|
-
};
|
|
61662
|
-
const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
61663
|
-
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61664
|
-
const sourceBlock = functionCfg?.blockOf(node);
|
|
61665
|
-
if (!functionCfg || !sourceBlock) return false;
|
|
61666
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61667
|
-
const pendingBlocks = [sourceBlock];
|
|
61668
|
-
while (pendingBlocks.length > 0) {
|
|
61669
|
-
const block = pendingBlocks.pop();
|
|
61670
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61671
|
-
visitedBlocks.add(block);
|
|
61672
|
-
for (const edge of block.successors) {
|
|
61673
|
-
if (edge.kind === "throw") continue;
|
|
61674
|
-
if (edge.to === functionCfg.exit) return true;
|
|
61675
|
-
pendingBlocks.push(edge.to);
|
|
61676
|
-
}
|
|
61677
|
-
}
|
|
61678
|
-
return false;
|
|
61679
|
-
};
|
|
61680
|
-
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
61681
|
-
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
61682
|
-
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61683
|
-
const targetBlock = functionCfg?.blockOf(node);
|
|
61684
|
-
if (!functionCfg || !targetBlock) return false;
|
|
61685
|
-
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
61686
|
-
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
61687
|
-
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
61688
|
-
if (!awaitBlock) continue;
|
|
61689
|
-
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
61690
|
-
blockAwaits.push(awaitExpression);
|
|
61691
|
-
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
61692
|
-
}
|
|
61693
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61694
|
-
const pendingBlocks = [functionCfg.entry];
|
|
61695
|
-
const targetStart = getRangeStart(node);
|
|
61696
|
-
while (pendingBlocks.length > 0) {
|
|
61697
|
-
const block = pendingBlocks.pop();
|
|
61698
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61699
|
-
visitedBlocks.add(block);
|
|
61700
|
-
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
61701
|
-
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
61702
|
-
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
61703
|
-
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
61704
|
-
const awaitStart = getRangeStart(awaitExpression);
|
|
61705
|
-
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
61706
|
-
});
|
|
61707
|
-
if (blockAwaits.length > 0) continue;
|
|
61708
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61709
|
-
}
|
|
61710
|
-
return false;
|
|
61711
|
-
};
|
|
61712
|
-
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);
|
|
61713
|
-
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
61714
|
-
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
61715
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
61716
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61717
|
-
if (visitingFunctions.has(functionNode)) {
|
|
61718
|
-
let didReachCycleEntry = false;
|
|
61719
|
-
for (const visitingFunction of visitingFunctions) {
|
|
61720
|
-
if (visitingFunction === functionNode) didReachCycleEntry = true;
|
|
61721
|
-
if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
|
|
61722
|
-
}
|
|
61723
|
-
return false;
|
|
61724
|
-
}
|
|
61725
|
-
visitingFunctions.add(functionNode);
|
|
61726
|
-
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
61727
|
-
visitingFunctions.delete(functionNode);
|
|
61728
|
-
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
61729
|
-
return doesMutateSynchronously;
|
|
61730
|
-
};
|
|
61731
|
-
const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
|
|
61732
|
-
const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
|
|
61733
|
-
if (cachedExecutions) return cachedExecutions;
|
|
61734
|
-
const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
|
|
61735
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61736
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61737
|
-
if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61738
|
-
for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61739
|
-
}
|
|
61740
|
-
if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
|
|
61741
|
-
return mutationExecutions;
|
|
61742
|
-
};
|
|
61743
|
-
const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
|
|
61744
|
-
const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
|
|
61745
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61746
|
-
if (visitingFunctions.has(functionNode)) return false;
|
|
61747
|
-
const nextVisitingFunctions = new Set(visitingFunctions);
|
|
61748
|
-
nextVisitingFunctions.add(functionNode);
|
|
61749
|
-
const removalExecutions = [];
|
|
61750
|
-
for (const removal of index.listenerRemovals) {
|
|
61751
|
-
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
61752
|
-
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
61753
|
-
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
61754
|
-
}
|
|
61755
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61756
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61757
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
61758
|
-
}
|
|
61759
|
-
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
61760
|
-
};
|
|
61761
|
-
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
61762
|
-
const removalExecutions = /* @__PURE__ */ new Set();
|
|
61763
|
-
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);
|
|
61764
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61765
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61766
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
61767
|
-
}
|
|
61768
|
-
return removalExecutions;
|
|
61769
|
-
};
|
|
61770
|
-
const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
|
|
61771
|
-
const expressionBoundaries = [];
|
|
61772
|
-
let currentChild = node;
|
|
61773
|
-
let currentParent = currentChild.parent ?? null;
|
|
61774
|
-
while (currentParent && currentParent !== owner) {
|
|
61775
|
-
if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
|
|
61776
|
-
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
61777
|
-
if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
|
|
61778
|
-
}
|
|
61779
|
-
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
|
|
61780
|
-
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
61781
|
-
if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
|
|
61782
|
-
}
|
|
61783
|
-
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);
|
|
61784
|
-
currentChild = currentParent;
|
|
61785
|
-
currentParent = currentChild.parent ?? null;
|
|
61786
|
-
}
|
|
61787
|
-
return expressionBoundaries;
|
|
61788
|
-
};
|
|
61789
|
-
const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
|
|
61790
|
-
if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
|
|
61791
|
-
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61792
|
-
if (!owner) return false;
|
|
61793
|
-
const functionCfg = index.context.cfg.cfgFor(owner);
|
|
61794
|
-
const sourceBlock = functionCfg?.blockOf(executionNode);
|
|
61795
|
-
if (!functionCfg || !sourceBlock) return false;
|
|
61796
|
-
const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
|
|
61797
|
-
const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
|
|
61798
|
-
for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
|
|
61799
|
-
const removalBlock = functionCfg.blockOf(removalExecution);
|
|
61800
|
-
if (!removalBlock) continue;
|
|
61801
|
-
const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
|
|
61802
|
-
blockRemovals.push(removalExecution);
|
|
61803
|
-
matchingRemovalsByBlock.set(removalBlock, blockRemovals);
|
|
61804
|
-
}
|
|
61805
|
-
const sourceStart = getRangeStart(executionNode);
|
|
61806
|
-
const hasRemovalAfterExecution = (block) => {
|
|
61807
|
-
const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
|
|
61808
|
-
if (block !== sourceBlock) return true;
|
|
61809
|
-
const removalStart = getRangeStart(removal);
|
|
61810
|
-
return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
|
|
61811
|
-
});
|
|
61812
|
-
if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
|
|
61813
|
-
if (block !== sourceBlock) return false;
|
|
61814
|
-
return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
|
|
61815
|
-
};
|
|
61816
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61817
|
-
const pendingBlocks = [sourceBlock];
|
|
61818
|
-
while (pendingBlocks.length > 0) {
|
|
61819
|
-
const block = pendingBlocks.pop();
|
|
61820
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61821
|
-
visitedBlocks.add(block);
|
|
61822
|
-
if (hasRemovalAfterExecution(block)) continue;
|
|
61823
|
-
for (const edge of block.successors) {
|
|
61824
|
-
if (edge.kind === "throw") continue;
|
|
61825
|
-
if (edge.to === functionCfg.exit) return true;
|
|
61826
|
-
pendingBlocks.push(edge.to);
|
|
61827
|
-
}
|
|
61828
|
-
}
|
|
61829
|
-
return false;
|
|
61830
|
-
};
|
|
61831
|
-
const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61832
|
-
if (visitedExecutions.has(executionNode)) return false;
|
|
61833
|
-
if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
|
|
61834
|
-
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61835
|
-
if (!owner) return false;
|
|
61836
|
-
if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
|
|
61837
|
-
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61838
|
-
nextVisitedExecutions.add(executionNode);
|
|
61839
|
-
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
|
|
61840
|
-
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
|
|
61841
|
-
return false;
|
|
61842
|
-
};
|
|
61843
|
-
const collectMountedListenerFunctions = (index) => {
|
|
61844
|
-
for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
|
|
61845
|
-
};
|
|
61846
|
-
const setterArgumentMutatesLocation = (setterCall, index) => {
|
|
61847
|
-
if (!isNodeOfType(setterCall, "CallExpression")) return false;
|
|
61848
|
-
return (setterCall.arguments ?? []).some((argument) => {
|
|
61849
|
-
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
61850
|
-
const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
|
|
61851
|
-
return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
|
|
61852
|
-
});
|
|
61853
|
-
};
|
|
61854
|
-
const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61855
|
-
if (visitedExecutions.has(executionAnchor)) return false;
|
|
61856
|
-
if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
|
|
61857
|
-
const owner = index.context.cfg.enclosingFunction(executionAnchor);
|
|
61858
|
-
if (!owner) return false;
|
|
61859
|
-
if (index.mountedListenerFunctions.has(owner)) return true;
|
|
61860
|
-
if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
|
|
61861
|
-
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61862
|
-
nextVisitedExecutions.add(executionAnchor);
|
|
61863
|
-
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
|
|
61864
|
-
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
|
|
61865
|
-
return false;
|
|
61866
|
-
};
|
|
61867
|
-
const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
|
|
61868
|
-
const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
|
|
61869
|
-
if (!setterSymbol) return false;
|
|
61870
|
-
return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
|
|
61871
|
-
};
|
|
61872
|
-
const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
|
|
61873
|
-
if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
|
|
61874
|
-
const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
|
|
61875
|
-
collectMountedListenerFunctions(locationInvalidationIndex);
|
|
61876
|
-
collectExactReadonlyCalleeCalls(locationInvalidationIndex);
|
|
61877
|
-
return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
|
|
61878
|
-
};
|
|
61879
|
-
//#endregion
|
|
61880
60392
|
//#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
|
|
61881
60393
|
const isInsideConditionTest = (identifier, stopAt) => {
|
|
61882
60394
|
let current = identifier;
|
|
@@ -61998,8 +60510,7 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
61998
60510
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
61999
60511
|
const bindings = collectUseStateBindings(componentBody, context.scopes);
|
|
62000
60512
|
if (bindings.length === 0) return;
|
|
62001
|
-
|
|
62002
|
-
if (renderReachableExpressions.length === 0) return;
|
|
60513
|
+
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
62003
60514
|
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
|
|
62004
60515
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
62005
60516
|
const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
|
|
@@ -62013,22 +60524,12 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
62013
60524
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
62014
60525
|
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
|
|
62015
60526
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
62016
|
-
const componentFunction = context.cfg.enclosingFunction(componentBody);
|
|
62017
|
-
const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
|
|
62018
|
-
componentBody,
|
|
62019
|
-
componentFunction,
|
|
62020
|
-
context,
|
|
62021
|
-
directRenderNames,
|
|
62022
|
-
renderReachableExpressions
|
|
62023
|
-
}) : () => false;
|
|
62024
60527
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
62025
60528
|
walkAst(componentBody, (child) => {
|
|
62026
60529
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
62027
60530
|
});
|
|
62028
60531
|
for (const binding of bindings) {
|
|
62029
60532
|
if (renderReachableNames.has(binding.valueName)) continue;
|
|
62030
|
-
const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
|
|
62031
|
-
if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
|
|
62032
60533
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
62033
60534
|
const setterSuffix = binding.setterName.slice(3);
|
|
62034
60535
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|
|
@@ -68469,7 +66970,6 @@ const countOwnScopeHookCalls = (functionNode, scopes, settings, countedFunctionN
|
|
|
68469
66970
|
};
|
|
68470
66971
|
const MIN_HOOK_CALLS_FOR_RENDER_SCOPE = 2;
|
|
68471
66972
|
const RENDER_SCOPE_FACTORY_NAME_PATTERN = /^_?(?:init|create|make|build)(?:[A-Z0-9_]|$)/;
|
|
68472
|
-
const HOOK_NAMESPACE_NAME_PATTERN = /Hooks?$/;
|
|
68473
66973
|
const isLocalNonHookFunctionCallee = (call, scopes, settings, countedFunctionNodes) => {
|
|
68474
66974
|
const callee = call.callee;
|
|
68475
66975
|
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
@@ -68494,15 +66994,6 @@ const isPackageImportedNonReactHookCallee = (call) => {
|
|
|
68494
66994
|
if (PATH_ALIAS_IMPORT_PATTERN.test(importSource)) return false;
|
|
68495
66995
|
return !isReactEcosystemImportSource(importSource);
|
|
68496
66996
|
};
|
|
68497
|
-
const isDefaultImportedClassApiMemberCallee = (call) => {
|
|
68498
|
-
const callee = call.callee;
|
|
68499
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
68500
|
-
if (!isNodeOfType(callee.object, "Identifier")) return false;
|
|
68501
|
-
if (HOOK_NAMESPACE_NAME_PATTERN.test(callee.object.name)) return false;
|
|
68502
|
-
const importBinding = getImportBindingForName(call, callee.object.name);
|
|
68503
|
-
if (!importBinding || importBinding.exportedName !== "default") return false;
|
|
68504
|
-
return !REACT_RUNTIME_MODULE_SOURCES.has(importBinding.source);
|
|
68505
|
-
};
|
|
68506
66997
|
const isProjectOwnedMdxComponentsGetter = (call) => {
|
|
68507
66998
|
const callee = call.callee;
|
|
68508
66999
|
if (!isNodeOfType(callee, "Identifier") || callee.name !== "useMDXComponents") return false;
|
|
@@ -68652,7 +67143,6 @@ const rulesOfHooks = defineRule({
|
|
|
68652
67143
|
return;
|
|
68653
67144
|
}
|
|
68654
67145
|
if (isInsideClassComponent(node)) {
|
|
68655
|
-
if (isPackageImportedNonReactHookCallee(node) || isDefaultImportedClassApiMemberCallee(node)) return;
|
|
68656
67146
|
context.report({
|
|
68657
67147
|
node: node.callee,
|
|
68658
67148
|
message: buildClassComponentMessage(hookName)
|
|
@@ -68785,7 +67275,7 @@ const secretInFallback = defineRule({
|
|
|
68785
67275
|
recommendation: "Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",
|
|
68786
67276
|
scan: scanByPattern({
|
|
68787
67277
|
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
68788
|
-
pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!
|
|
67278
|
+
pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
|
|
68789
67279
|
message: "A secret env var has a hardcoded string fallback: the literal is a committed secret and the app fails open (uses it) when the variable is unset."
|
|
68790
67280
|
})
|
|
68791
67281
|
});
|
|
@@ -69055,30 +67545,7 @@ const isAuthGuardName = (calleeName) => {
|
|
|
69055
67545
|
//#endregion
|
|
69056
67546
|
//#region src/plugin/utils/is-non-privileged-server-action.ts
|
|
69057
67547
|
const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
|
|
69058
|
-
const
|
|
69059
|
-
const LOCALE_PREFERENCE_NOUN_TOKENS = new Set(["language", "locale"]);
|
|
69060
|
-
const COOKIE_DELETION_METHOD_NAME = "delete";
|
|
69061
|
-
const isCacheOrNavigationCall = (node) => {
|
|
69062
|
-
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return false;
|
|
69063
|
-
return NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
|
|
69064
|
-
};
|
|
69065
|
-
const isCallerScopedLocalePreferenceCall = (node) => {
|
|
69066
|
-
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.arguments?.length !== 1) return false;
|
|
69067
|
-
const nameTokens = tokenizeIdentifierWords(node.callee.name);
|
|
69068
|
-
if (!nameTokens.some((token) => LOCALE_PREFERENCE_VERB_TOKENS.has(token))) return false;
|
|
69069
|
-
if (!nameTokens.some((token) => LOCALE_PREFERENCE_NOUN_TOKENS.has(token))) return false;
|
|
69070
|
-
const importSource = getImportSourceForName(node, node.callee.name)?.toLowerCase();
|
|
69071
|
-
return Boolean(importSource?.includes("i18n") || importSource?.includes("locale"));
|
|
69072
|
-
};
|
|
69073
|
-
const isCallerScopedCookieCall = (node, cookieBindingNames) => {
|
|
69074
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
69075
|
-
if (isNodeOfType(node.callee, "Identifier")) return node.callee.name === "cookies" && getImportSourceForName(node, node.callee.name) === "next/headers";
|
|
69076
|
-
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== COOKIE_DELETION_METHOD_NAME) return false;
|
|
69077
|
-
const receiver = stripParenExpression(node.callee.object);
|
|
69078
|
-
if (isNodeOfType(receiver, "Identifier")) return cookieBindingNames.has(receiver.name);
|
|
69079
|
-
return isCookiesOrAwaitedCookiesCall(receiver) && getImportSourceForName(receiver, "cookies") === "next/headers";
|
|
69080
|
-
};
|
|
69081
|
-
const isKnownNonDataEffectCall = (node, cookieBindingNames) => isCacheOrNavigationCall(node) || isCallerScopedLocalePreferenceCall(node) || isCallerScopedCookieCall(node, cookieBindingNames);
|
|
67548
|
+
const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
|
|
69082
67549
|
const unwrapExpression = (node) => {
|
|
69083
67550
|
let current = node;
|
|
69084
67551
|
while (current) {
|
|
@@ -69094,10 +67561,10 @@ const unwrapExpression = (node) => {
|
|
|
69094
67561
|
}
|
|
69095
67562
|
return null;
|
|
69096
67563
|
};
|
|
69097
|
-
const isDataExposingValue = (node
|
|
67564
|
+
const isDataExposingValue = (node) => {
|
|
69098
67565
|
const value = unwrapExpression(node);
|
|
69099
67566
|
if (!value) return false;
|
|
69100
|
-
if (
|
|
67567
|
+
if (isCacheOrNavigationCall(value)) return false;
|
|
69101
67568
|
return !isLiteralOnlyExpression(value);
|
|
69102
67569
|
};
|
|
69103
67570
|
const isLiteralOnlyExpression = (node) => {
|
|
@@ -69115,23 +67582,22 @@ const getReturnedOrThrownArgument = (node) => {
|
|
|
69115
67582
|
if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
|
|
69116
67583
|
return null;
|
|
69117
67584
|
};
|
|
69118
|
-
const isDataExposingReturnOrThrow = (node
|
|
69119
|
-
const isPrivilegedEffect = (node
|
|
67585
|
+
const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
|
|
67586
|
+
const isPrivilegedEffect = (node) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node);
|
|
69120
67587
|
const isNonPrivilegedServerAction = (functionNode) => {
|
|
69121
67588
|
const functionBody = functionNode.body;
|
|
69122
67589
|
if (!functionBody) return false;
|
|
69123
|
-
|
|
69124
|
-
if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody, cookieBindingNames)) return false;
|
|
67590
|
+
if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
|
|
69125
67591
|
let hasNonDataEffectCall = false;
|
|
69126
67592
|
let hasPrivilegedEffect = false;
|
|
69127
67593
|
walkAst(functionBody, (child) => {
|
|
69128
67594
|
if (hasPrivilegedEffect) return false;
|
|
69129
67595
|
if (child !== functionBody && isFunctionLike$1(child)) return false;
|
|
69130
|
-
if (
|
|
67596
|
+
if (isCacheOrNavigationCall(child)) {
|
|
69131
67597
|
hasNonDataEffectCall = true;
|
|
69132
67598
|
return;
|
|
69133
67599
|
}
|
|
69134
|
-
if (isPrivilegedEffect(child
|
|
67600
|
+
if (isPrivilegedEffect(child)) {
|
|
69135
67601
|
hasPrivilegedEffect = true;
|
|
69136
67602
|
return false;
|
|
69137
67603
|
}
|
|
@@ -69210,34 +67676,6 @@ const getAuthScanRoots = (functionNode) => {
|
|
|
69210
67676
|
if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
|
|
69211
67677
|
return [bodyNode];
|
|
69212
67678
|
};
|
|
69213
|
-
const COMPONENT_NAME_PATTERN = /^[A-Z]/;
|
|
69214
|
-
const TEST_APP_SOURCE_PATH_PATTERN = /(?:^|[/\\])test[/\\](?:app|src)[/\\]/;
|
|
69215
|
-
const containsJsxOutsideNestedFunctions = (rootNode) => {
|
|
69216
|
-
let containsJsx = false;
|
|
69217
|
-
walkAst(rootNode, (child) => {
|
|
69218
|
-
if (containsJsx) return false;
|
|
69219
|
-
if (child !== rootNode && isFunctionLike$1(child)) return false;
|
|
69220
|
-
if (isNodeOfType(child, "JSXElement") || isNodeOfType(child, "JSXFragment")) {
|
|
69221
|
-
containsJsx = true;
|
|
69222
|
-
return false;
|
|
69223
|
-
}
|
|
69224
|
-
});
|
|
69225
|
-
return containsJsx;
|
|
69226
|
-
};
|
|
69227
|
-
const isComponentLikeServerExport = (candidate) => {
|
|
69228
|
-
if (!COMPONENT_NAME_PATTERN.test(candidate.displayName)) return false;
|
|
69229
|
-
const functionBody = candidate.functionNode.body;
|
|
69230
|
-
if (!isNodeOfType(functionBody, "BlockStatement")) return containsJsxOutsideNestedFunctions(functionBody);
|
|
69231
|
-
let hasReturnedJsx = false;
|
|
69232
|
-
walkAst(functionBody, (child) => {
|
|
69233
|
-
if (hasReturnedJsx) return false;
|
|
69234
|
-
if (child !== functionBody && isFunctionLike$1(child)) return false;
|
|
69235
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
69236
|
-
hasReturnedJsx = containsJsxOutsideNestedFunctions(child.argument);
|
|
69237
|
-
return false;
|
|
69238
|
-
});
|
|
69239
|
-
return hasReturnedJsx;
|
|
69240
|
-
};
|
|
69241
67679
|
const CREDENTIAL_MERGE_TAIL_TOKENS = {
|
|
69242
67680
|
sign: new Set([
|
|
69243
67681
|
"in",
|
|
@@ -69302,7 +67740,6 @@ const isCredentialEstablishingActionName = (actionName) => {
|
|
|
69302
67740
|
const hasPublicNameToken = (actionName) => tokenizeIdentifierWords(actionName).includes("public");
|
|
69303
67741
|
const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
|
|
69304
67742
|
if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
|
|
69305
|
-
if (isComponentLikeServerExport(candidate)) return;
|
|
69306
67743
|
if (isCredentialEstablishingActionName(candidate.displayName)) return;
|
|
69307
67744
|
if (hasPublicNameToken(candidate.displayName)) return;
|
|
69308
67745
|
if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
|
|
@@ -69340,42 +67777,18 @@ const serverAuthActions = defineRule({
|
|
|
69340
67777
|
title: "Unauthenticated server action can be called directly",
|
|
69341
67778
|
severity: "error",
|
|
69342
67779
|
recommendation: "Check auth before touching data because exported server actions can be called directly by unauthenticated clients.",
|
|
69343
|
-
create:
|
|
69344
|
-
const shouldSkipTestAppSource = Boolean(context.filename && TEST_APP_SOURCE_PATH_PATTERN.test(context.filename));
|
|
67780
|
+
create: (context) => {
|
|
69345
67781
|
let fileHasUseServerDirective = false;
|
|
69346
|
-
let programNode = null;
|
|
69347
|
-
const inspectedFunctions = /* @__PURE__ */ new Set();
|
|
69348
67782
|
const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
|
|
69349
67783
|
const allowedFunctionNames = customAuthFunctionNames.length > 0 ? new Set([...AUTH_FUNCTION_NAMES, ...customAuthFunctionNames]) : AUTH_FUNCTION_NAMES;
|
|
69350
|
-
const inspect = (candidate) =>
|
|
69351
|
-
if (inspectedFunctions.has(candidate.functionNode)) return;
|
|
69352
|
-
inspectedFunctions.add(candidate.functionNode);
|
|
69353
|
-
inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
|
|
69354
|
-
};
|
|
67784
|
+
const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
|
|
69355
67785
|
return {
|
|
69356
|
-
Program(
|
|
69357
|
-
|
|
69358
|
-
fileHasUseServerDirective = hasDirective(currentProgramNode, "use server");
|
|
67786
|
+
Program(programNode) {
|
|
67787
|
+
fileHasUseServerDirective = hasDirective(programNode, "use server");
|
|
69359
67788
|
},
|
|
69360
67789
|
ExportNamedDeclaration(node) {
|
|
69361
|
-
if (shouldSkipTestAppSource) return;
|
|
69362
67790
|
const declaration = node.declaration;
|
|
69363
|
-
if (!declaration)
|
|
69364
|
-
if (!programNode || node.source || node.exportKind === "type") return;
|
|
69365
|
-
for (const specifier of node.specifiers ?? []) {
|
|
69366
|
-
if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
|
|
69367
|
-
const exportedName = isNodeOfType(specifier.exported, "Identifier") ? specifier.exported.name : isNodeOfType(specifier.exported, "Literal") && typeof specifier.exported.value === "string" ? specifier.exported.value : null;
|
|
69368
|
-
if (!exportedName) continue;
|
|
69369
|
-
const exportedValue = findExportedValue(programNode, exportedName);
|
|
69370
|
-
if (!isAsyncFunctionLikeNode(exportedValue)) continue;
|
|
69371
|
-
inspect({
|
|
69372
|
-
functionNode: exportedValue,
|
|
69373
|
-
displayName: isNodeOfType(specifier.local, "Identifier") ? specifier.local.name : exportedName,
|
|
69374
|
-
reportNode: specifier.local ?? specifier
|
|
69375
|
-
});
|
|
69376
|
-
}
|
|
69377
|
-
return;
|
|
69378
|
-
}
|
|
67791
|
+
if (!declaration) return;
|
|
69379
67792
|
if (isAsyncFunctionLikeNode(declaration)) {
|
|
69380
67793
|
if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
|
|
69381
67794
|
inspect({
|
|
@@ -69388,23 +67801,11 @@ const serverAuthActions = defineRule({
|
|
|
69388
67801
|
if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
|
|
69389
67802
|
},
|
|
69390
67803
|
ExportDefaultDeclaration(node) {
|
|
69391
|
-
|
|
69392
|
-
const directCandidate = getCandidateFromDefaultDeclaration(node);
|
|
69393
|
-
if (directCandidate) {
|
|
69394
|
-
inspect(directCandidate);
|
|
69395
|
-
return;
|
|
69396
|
-
}
|
|
69397
|
-
if (!programNode) return;
|
|
69398
|
-
const resolvedDefaultExport = findExportedValue(programNode, "default");
|
|
69399
|
-
const candidate = isAsyncFunctionLikeNode(resolvedDefaultExport) ? {
|
|
69400
|
-
functionNode: resolvedDefaultExport,
|
|
69401
|
-
displayName: isNodeOfType(node.declaration, "Identifier") ? node.declaration.name : "default",
|
|
69402
|
-
reportNode: node.declaration ?? node
|
|
69403
|
-
} : null;
|
|
67804
|
+
const candidate = getCandidateFromDefaultDeclaration(node);
|
|
69404
67805
|
if (candidate) inspect(candidate);
|
|
69405
67806
|
}
|
|
69406
67807
|
};
|
|
69407
|
-
}
|
|
67808
|
+
}
|
|
69408
67809
|
});
|
|
69409
67810
|
//#endregion
|
|
69410
67811
|
//#region src/plugin/rules/server/server-cache-with-object-literal.ts
|
|
@@ -69484,7 +67885,6 @@ const serverDedupProps = defineRule({
|
|
|
69484
67885
|
for (const attr of node.attributes ?? []) {
|
|
69485
67886
|
if (!isNodeOfType(attr, "JSXAttribute")) continue;
|
|
69486
67887
|
if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
|
|
69487
|
-
if (attr.name.name === "key") continue;
|
|
69488
67888
|
if (!isNodeOfType(attr.value, "JSXExpressionContainer")) continue;
|
|
69489
67889
|
const expression = attr.value.expression;
|
|
69490
67890
|
if (!expression) continue;
|
|
@@ -73305,6 +71705,18 @@ const reactDoctorRules = [
|
|
|
73305
71705
|
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
73306
71706
|
}
|
|
73307
71707
|
},
|
|
71708
|
+
{
|
|
71709
|
+
key: "react-doctor/jsx-no-target-blank",
|
|
71710
|
+
id: "jsx-no-target-blank",
|
|
71711
|
+
source: "react-doctor",
|
|
71712
|
+
originallyExternal: true,
|
|
71713
|
+
rule: {
|
|
71714
|
+
...jsxNoTargetBlank,
|
|
71715
|
+
framework: "global",
|
|
71716
|
+
category: "Security",
|
|
71717
|
+
requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
|
|
71718
|
+
}
|
|
71719
|
+
},
|
|
73308
71720
|
{
|
|
73309
71721
|
key: "react-doctor/jsx-no-undef",
|
|
73310
71722
|
id: "jsx-no-undef",
|