oxlint-plugin-react-doctor 0.8.1-dev.b4556e3 → 0.8.1-dev.c9f2c4b
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 +48 -2
- package/dist/index.js +642 -2379
- 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,35 +13958,9 @@ 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();
|
|
14105
|
-
const REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES = new Set([
|
|
14106
|
-
"pop",
|
|
14107
|
-
"shift",
|
|
14108
|
-
"splice",
|
|
14109
|
-
"fill",
|
|
14110
|
-
"copyWithin"
|
|
14111
|
-
]);
|
|
14112
|
-
const REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES = new Set([
|
|
14113
|
-
"clear",
|
|
14114
|
-
"delete",
|
|
14115
|
-
"set"
|
|
14116
|
-
]);
|
|
14117
13964
|
const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
14118
13965
|
const RESOURCE_NOUN_BY_KIND = {
|
|
14119
13966
|
subscribe: "subscription",
|
|
@@ -14154,92 +14001,6 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
|
|
|
14154
14001
|
}
|
|
14155
14002
|
return null;
|
|
14156
14003
|
};
|
|
14157
|
-
const resolveForEachProjection = (expression, context) => {
|
|
14158
|
-
if (!expression) return null;
|
|
14159
|
-
let currentExpression = stripParenExpression(expression);
|
|
14160
|
-
const memberNames = [];
|
|
14161
|
-
while (isNodeOfType(currentExpression, "MemberExpression") && !currentExpression.computed) {
|
|
14162
|
-
if (!isNodeOfType(currentExpression.property, "Identifier")) return null;
|
|
14163
|
-
memberNames.unshift(currentExpression.property.name);
|
|
14164
|
-
currentExpression = stripParenExpression(currentExpression.object);
|
|
14165
|
-
}
|
|
14166
|
-
if (!isNodeOfType(currentExpression, "Identifier")) return null;
|
|
14167
|
-
const symbol = context.scopes.symbolFor(currentExpression);
|
|
14168
|
-
if (!symbol || symbol.kind !== "parameter") return null;
|
|
14169
|
-
let callbackNode = symbol.bindingIdentifier.parent;
|
|
14170
|
-
while (callbackNode && !isFunctionLike$1(callbackNode)) callbackNode = callbackNode.parent;
|
|
14171
|
-
if (!callbackNode || !isFunctionLike$1(callbackNode)) return null;
|
|
14172
|
-
const forEachCall = findEnclosingForEachCall(callbackNode);
|
|
14173
|
-
if (!forEachCall) return null;
|
|
14174
|
-
const forEachCallee = stripParenExpression(forEachCall.callee);
|
|
14175
|
-
if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
|
|
14176
|
-
const collectionKey = resolveExpressionKey(forEachCallee.object, context);
|
|
14177
|
-
if (!collectionKey) return null;
|
|
14178
|
-
const firstParameter = callbackNode.params[0];
|
|
14179
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
14180
|
-
const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
|
|
14181
|
-
const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
|
|
14182
|
-
if (!parameterProjection) return null;
|
|
14183
|
-
return {
|
|
14184
|
-
collectionKey,
|
|
14185
|
-
projectionKey: [parameterProjection, ...memberNames].join(".")
|
|
14186
|
-
};
|
|
14187
|
-
};
|
|
14188
|
-
const resolveForEachProjectionKey = (expression, context) => {
|
|
14189
|
-
const projection = resolveForEachProjection(expression, context);
|
|
14190
|
-
return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
|
|
14191
|
-
};
|
|
14192
|
-
const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
|
|
14193
|
-
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
14194
|
-
const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
|
|
14195
|
-
if (capture !== null) return `capture:${String(capture)}`;
|
|
14196
|
-
if (!optionsNode) return null;
|
|
14197
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
14198
|
-
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
14199
|
-
const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
|
|
14200
|
-
return optionsKey ? `options:${optionsKey}` : null;
|
|
14201
|
-
}
|
|
14202
|
-
let captureKey = "capture:false";
|
|
14203
|
-
for (const property of unwrappedOptions.properties ?? []) {
|
|
14204
|
-
if (!isNodeOfType(property, "Property")) {
|
|
14205
|
-
captureKey = null;
|
|
14206
|
-
continue;
|
|
14207
|
-
}
|
|
14208
|
-
const propertyName = getStaticPropertyKeyName(property);
|
|
14209
|
-
if (propertyName === null || !property.computed && propertyName === "__proto__") {
|
|
14210
|
-
captureKey = null;
|
|
14211
|
-
continue;
|
|
14212
|
-
}
|
|
14213
|
-
if (propertyName === "capture") {
|
|
14214
|
-
const propertyValueKey = resolveResourceIdentityKey(property.value, context);
|
|
14215
|
-
captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
|
|
14216
|
-
}
|
|
14217
|
-
}
|
|
14218
|
-
return captureKey;
|
|
14219
|
-
};
|
|
14220
|
-
const resolveEventListenerCaptureProjection = (optionsNode, context) => {
|
|
14221
|
-
if (!optionsNode) return null;
|
|
14222
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
14223
|
-
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return resolveForEachProjection(unwrappedOptions, context);
|
|
14224
|
-
let captureProjection = null;
|
|
14225
|
-
for (const property of unwrappedOptions.properties ?? []) {
|
|
14226
|
-
if (!isNodeOfType(property, "Property")) {
|
|
14227
|
-
captureProjection = null;
|
|
14228
|
-
continue;
|
|
14229
|
-
}
|
|
14230
|
-
const propertyName = getStaticPropertyKeyName(property);
|
|
14231
|
-
if (propertyName === null || !property.computed && propertyName === "__proto__") {
|
|
14232
|
-
captureProjection = null;
|
|
14233
|
-
continue;
|
|
14234
|
-
}
|
|
14235
|
-
if (propertyName === "capture") captureProjection = resolveForEachProjection(property.value, context);
|
|
14236
|
-
}
|
|
14237
|
-
return captureProjection;
|
|
14238
|
-
};
|
|
14239
|
-
const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, context, allowOpaqueOptionsIdentity = false) => {
|
|
14240
|
-
const registrationCaptureKey = resolveEventListenerCaptureIdentityKey(registrationOptions, context, allowOpaqueOptionsIdentity);
|
|
14241
|
-
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
14242
|
-
};
|
|
14243
14004
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
14244
14005
|
let currentNode = resourceNode;
|
|
14245
14006
|
let parentNode = currentNode.parent;
|
|
@@ -14251,30 +14012,6 @@ const findAssignedResourceKey = (resourceNode, context) => {
|
|
|
14251
14012
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
14252
14013
|
return null;
|
|
14253
14014
|
};
|
|
14254
|
-
const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14255
|
-
if (!expression) return null;
|
|
14256
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
14257
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
14258
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
14259
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
|
|
14260
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
14261
|
-
if (!initializer) return `symbol:${symbol.id}`;
|
|
14262
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
14263
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
|
|
14264
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
14265
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
14266
|
-
return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
|
|
14267
|
-
}
|
|
14268
|
-
if (isFunctionLike$1(unwrappedExpression)) {
|
|
14269
|
-
const rangeStart = getRangeStart(unwrappedExpression);
|
|
14270
|
-
return rangeStart === null ? null : `function:${rangeStart}`;
|
|
14271
|
-
}
|
|
14272
|
-
return null;
|
|
14273
|
-
};
|
|
14274
|
-
const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
|
|
14275
|
-
const callee = stripParenExpression(callNode.callee);
|
|
14276
|
-
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");
|
|
14277
|
-
};
|
|
14278
14015
|
const getCallRegistrationDetails = (callNode, context) => {
|
|
14279
14016
|
const callee = stripParenExpression(callNode.callee);
|
|
14280
14017
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
|
|
@@ -14283,17 +14020,11 @@ const getCallRegistrationDetails = (callNode, context) => {
|
|
|
14283
14020
|
eventKey: null,
|
|
14284
14021
|
handlerKey: null
|
|
14285
14022
|
};
|
|
14286
|
-
if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
|
|
14287
|
-
receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
|
|
14288
|
-
registrationVerbName: callee.property.name,
|
|
14289
|
-
eventKey: null,
|
|
14290
|
-
handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
|
|
14291
|
-
};
|
|
14292
14023
|
return {
|
|
14293
|
-
receiverKey:
|
|
14024
|
+
receiverKey: resolveExpressionKey(callee.object, context),
|
|
14294
14025
|
registrationVerbName: callee.property.name,
|
|
14295
|
-
eventKey:
|
|
14296
|
-
handlerKey:
|
|
14026
|
+
eventKey: resolveExpressionKey(callNode.arguments?.[0], context),
|
|
14027
|
+
handlerKey: resolveExpressionKey(callNode.arguments?.[1], context)
|
|
14297
14028
|
};
|
|
14298
14029
|
};
|
|
14299
14030
|
const findSubscribeLikeUsages = (callback, context) => {
|
|
@@ -14393,6 +14124,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
14393
14124
|
}
|
|
14394
14125
|
return matchingBlocks.size > 0;
|
|
14395
14126
|
};
|
|
14127
|
+
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
14128
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
14129
|
+
if (!functionCfg) return false;
|
|
14130
|
+
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
14131
|
+
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
14132
|
+
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
14133
|
+
return matchingBlock ? [matchingBlock] : [];
|
|
14134
|
+
}));
|
|
14135
|
+
if (matchingBlocks.size === 0) return false;
|
|
14136
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
14137
|
+
const pendingBlocks = [functionCfg.entry];
|
|
14138
|
+
while (pendingBlocks.length > 0) {
|
|
14139
|
+
const currentBlock = pendingBlocks.pop();
|
|
14140
|
+
if (!currentBlock) break;
|
|
14141
|
+
if (matchingBlocks.has(currentBlock)) continue;
|
|
14142
|
+
for (const edge of currentBlock.successors) {
|
|
14143
|
+
if (edge.to === functionCfg.exit) return false;
|
|
14144
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
14145
|
+
visitedBlocks.add(edge.to);
|
|
14146
|
+
pendingBlocks.push(edge.to);
|
|
14147
|
+
}
|
|
14148
|
+
}
|
|
14149
|
+
return true;
|
|
14150
|
+
};
|
|
14396
14151
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
14397
14152
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
14398
14153
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -14635,6 +14390,18 @@ const findPushedResourceCollectionKey = (usage, context) => {
|
|
|
14635
14390
|
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14636
14391
|
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14637
14392
|
};
|
|
14393
|
+
const isWithinAssignmentTarget = (identifier) => {
|
|
14394
|
+
let currentNode = identifier;
|
|
14395
|
+
let parentNode = currentNode.parent;
|
|
14396
|
+
while (parentNode) {
|
|
14397
|
+
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14398
|
+
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14399
|
+
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14400
|
+
currentNode = parentNode;
|
|
14401
|
+
parentNode = currentNode.parent;
|
|
14402
|
+
}
|
|
14403
|
+
return false;
|
|
14404
|
+
};
|
|
14638
14405
|
const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14639
14406
|
if (!expression) return null;
|
|
14640
14407
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14682,8 +14449,8 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
14682
14449
|
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
14683
14450
|
};
|
|
14684
14451
|
const findEnclosingForEachCall = (node) => {
|
|
14685
|
-
const callbackNode =
|
|
14686
|
-
if (!callbackNode
|
|
14452
|
+
const callbackNode = findEnclosingFunction$1(node);
|
|
14453
|
+
if (!callbackNode) return null;
|
|
14687
14454
|
const callNode = callbackNode.parent;
|
|
14688
14455
|
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
|
|
14689
14456
|
const callee = stripParenExpression(callNode.callee);
|
|
@@ -14758,7 +14525,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14758
14525
|
const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
|
|
14759
14526
|
if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
|
|
14760
14527
|
});
|
|
14761
|
-
return didCleanupFunctionMatch ||
|
|
14528
|
+
return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
|
|
14762
14529
|
};
|
|
14763
14530
|
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
14764
14531
|
const callExpression = stripParenExpression(expression);
|
|
@@ -14787,7 +14554,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14787
14554
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
14788
14555
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
14789
14556
|
});
|
|
14790
|
-
return
|
|
14557
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14791
14558
|
};
|
|
14792
14559
|
const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
|
|
14793
14560
|
if (resolveExpressionKey(test, context) === expressionKey) return true;
|
|
@@ -14830,7 +14597,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
14830
14597
|
const helperFunction = resolveStableValue(child.callee, context);
|
|
14831
14598
|
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14832
14599
|
});
|
|
14833
|
-
return
|
|
14600
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
14834
14601
|
};
|
|
14835
14602
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
14836
14603
|
const componentFunction = findEnclosingFunction$1(callback);
|
|
@@ -15081,7 +14848,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
|
|
|
15081
14848
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15082
14849
|
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
15083
14850
|
const usageAssignment = usageExpression.parent;
|
|
15084
|
-
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) || !
|
|
14851
|
+
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;
|
|
15085
14852
|
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
15086
14853
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
15087
14854
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
@@ -15129,7 +14896,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
15129
14896
|
});
|
|
15130
14897
|
}
|
|
15131
14898
|
});
|
|
15132
|
-
if (!
|
|
14899
|
+
if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
15133
14900
|
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
15134
14901
|
}
|
|
15135
14902
|
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
@@ -15332,7 +15099,9 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
15332
15099
|
const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
|
|
15333
15100
|
const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
|
|
15334
15101
|
if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
|
|
15335
|
-
|
|
15102
|
+
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15103
|
+
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15104
|
+
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15336
15105
|
const releaseStart = getRangeStart(releaseCall);
|
|
15337
15106
|
const matchingOwnershipAssignments = [];
|
|
15338
15107
|
const usageFunctionBody = usageFunction.body;
|
|
@@ -15342,127 +15111,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
15342
15111
|
});
|
|
15343
15112
|
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15344
15113
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15345
|
-
return
|
|
15346
|
-
};
|
|
15347
|
-
const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
|
|
15348
|
-
let currentNode = findTransparentExpressionRoot(releaseNode);
|
|
15349
|
-
const visitedFunctions = /* @__PURE__ */ new Set();
|
|
15350
|
-
const replayedCollectionKeys = /* @__PURE__ */ new Set();
|
|
15351
|
-
while (true) {
|
|
15352
|
-
const ownerFunction = findEnclosingFunction$1(currentNode);
|
|
15353
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction) || visitedFunctions.has(ownerFunction)) return null;
|
|
15354
|
-
visitedFunctions.add(ownerFunction);
|
|
15355
|
-
const isDirectConciseBody = ownerFunction.body === currentNode;
|
|
15356
|
-
const statementNode = currentNode.parent;
|
|
15357
|
-
const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
|
|
15358
|
-
if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
|
|
15359
|
-
const forEachCall = findEnclosingForEachCall(ownerFunction);
|
|
15360
|
-
if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
|
|
15361
|
-
const forEachCallee = stripParenExpression(forEachCall.callee);
|
|
15362
|
-
if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
|
|
15363
|
-
const collectionKey = resolveExpressionKey(forEachCallee.object, context);
|
|
15364
|
-
if (!collectionKey || !requiredCollectionKeys.has(collectionKey)) return null;
|
|
15365
|
-
replayedCollectionKeys.add(collectionKey);
|
|
15366
|
-
currentNode = findTransparentExpressionRoot(forEachCall);
|
|
15367
|
-
}
|
|
15368
|
-
};
|
|
15369
|
-
const collectReplayOwnerFunctions = (usageNode) => {
|
|
15370
|
-
const ownerFunctions = /* @__PURE__ */ new Set();
|
|
15371
|
-
let currentNode = usageNode;
|
|
15372
|
-
while (true) {
|
|
15373
|
-
const ownerFunction = findEnclosingFunction$1(currentNode);
|
|
15374
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction) || ownerFunctions.has(ownerFunction)) break;
|
|
15375
|
-
ownerFunctions.add(ownerFunction);
|
|
15376
|
-
const forEachCall = findEnclosingForEachCall(ownerFunction);
|
|
15377
|
-
if (!forEachCall) break;
|
|
15378
|
-
currentNode = forEachCall;
|
|
15379
|
-
}
|
|
15380
|
-
return ownerFunctions;
|
|
15381
|
-
};
|
|
15382
|
-
const hasCollectionMutationBeforeRelease = (usageNode, releaseNode, collectionKeys, context) => {
|
|
15383
|
-
const usageStart = getRangeStart(usageNode);
|
|
15384
|
-
const releaseStart = getRangeStart(releaseNode);
|
|
15385
|
-
if (usageStart === null || releaseStart === null) return true;
|
|
15386
|
-
const setupOwnerFunctions = collectReplayOwnerFunctions(usageNode);
|
|
15387
|
-
const cleanupOwnerFunctions = collectReplayOwnerFunctions(releaseNode);
|
|
15388
|
-
let programNode = usageNode;
|
|
15389
|
-
while (programNode.parent) programNode = programNode.parent;
|
|
15390
|
-
let didFindMutation = false;
|
|
15391
|
-
walkAst(programNode, (child) => {
|
|
15392
|
-
if (didFindMutation) return false;
|
|
15393
|
-
const childStart = getRangeStart(child);
|
|
15394
|
-
if (childStart === null) return;
|
|
15395
|
-
const ownerFunction = context.cfg.enclosingFunction(child);
|
|
15396
|
-
if (!ownerFunction) return;
|
|
15397
|
-
const isAfterRegistration = setupOwnerFunctions.has(ownerFunction) && childStart > usageStart;
|
|
15398
|
-
const isBeforeRelease = cleanupOwnerFunctions.has(ownerFunction) && childStart < releaseStart;
|
|
15399
|
-
if (!isAfterRegistration && !isBeforeRelease) return;
|
|
15400
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
15401
|
-
const assignmentKey = resolveExpressionKey(child.left, context);
|
|
15402
|
-
const assignmentTarget = stripParenExpression(child.left);
|
|
15403
|
-
if (assignmentKey && [...collectionKeys].some((collectionKey) => assignmentKey === collectionKey || assignmentKey === `${collectionKey}.length`) || isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && collectionKeys.has(resolveExpressionKey(assignmentTarget.object, context) ?? "")) {
|
|
15404
|
-
didFindMutation = true;
|
|
15405
|
-
return false;
|
|
15406
|
-
}
|
|
15407
|
-
return;
|
|
15408
|
-
}
|
|
15409
|
-
if (isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
|
|
15410
|
-
const deletedMember = stripParenExpression(child.argument);
|
|
15411
|
-
if (!isNodeOfType(deletedMember, "MemberExpression")) return;
|
|
15412
|
-
if (collectionKeys.has(resolveExpressionKey(deletedMember.object, context) ?? "")) {
|
|
15413
|
-
didFindMutation = true;
|
|
15414
|
-
return false;
|
|
15415
|
-
}
|
|
15416
|
-
return;
|
|
15417
|
-
}
|
|
15418
|
-
if (isNodeOfType(child, "UpdateExpression")) {
|
|
15419
|
-
const updatedKey = resolveExpressionKey(child.argument, context);
|
|
15420
|
-
if (updatedKey && [...collectionKeys].some((collectionKey) => updatedKey === `${collectionKey}.length`)) {
|
|
15421
|
-
didFindMutation = true;
|
|
15422
|
-
return false;
|
|
15423
|
-
}
|
|
15424
|
-
return;
|
|
15425
|
-
}
|
|
15426
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15427
|
-
const callee = stripParenExpression(child.callee);
|
|
15428
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES.has(callee.property.name) && !REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES.has(callee.property.name) || !collectionKeys.has(resolveExpressionKey(callee.object, context) ?? "")) return;
|
|
15429
|
-
didFindMutation = true;
|
|
15430
|
-
return false;
|
|
15431
|
-
});
|
|
15432
|
-
return didFindMutation;
|
|
15433
|
-
};
|
|
15434
|
-
const usesUnaryListenerSignature = (registrationCall, releaseCall) => getCalleeName$1(registrationCall) === "addListener" && registrationCall.arguments?.length === 1 && releaseCall.arguments?.length === 1;
|
|
15435
|
-
const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context) => {
|
|
15436
|
-
const registrationCallee = stripParenExpression(registrationCall.callee);
|
|
15437
|
-
const releaseCallee = stripParenExpression(releaseCall.callee);
|
|
15438
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || !isNodeOfType(releaseCallee, "MemberExpression")) return true;
|
|
15439
|
-
const registrationVerbName = getCalleeName$1(registrationCall);
|
|
15440
|
-
const releaseVerbName = getCalleeName$1(releaseCall);
|
|
15441
|
-
const releaseHandler = releaseCall.arguments?.[usesUnaryListenerSignature(registrationCall, releaseCall) ? 0 : 1];
|
|
15442
|
-
const releaseFunction = findEnclosingFunction$1(releaseCall);
|
|
15443
|
-
const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
|
|
15444
|
-
const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
|
|
15445
|
-
const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
|
|
15446
|
-
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
|
|
15447
|
-
const projections = [
|
|
15448
|
-
registrationCallee.object,
|
|
15449
|
-
registrationCall.arguments?.[0],
|
|
15450
|
-
registrationCall.arguments?.[1],
|
|
15451
|
-
releaseCallee.object,
|
|
15452
|
-
releaseCall.arguments?.[0],
|
|
15453
|
-
releaseCall.arguments?.[1]
|
|
15454
|
-
].flatMap((expression) => {
|
|
15455
|
-
const projection = resolveForEachProjection(expression, context);
|
|
15456
|
-
return projection ? [projection] : [];
|
|
15457
|
-
});
|
|
15458
|
-
if (registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener") for (const optionsNode of [registrationCall.arguments?.[2], releaseCall.arguments?.[2]]) {
|
|
15459
|
-
const captureProjection = resolveEventListenerCaptureProjection(optionsNode, context);
|
|
15460
|
-
if (captureProjection) projections.push(captureProjection);
|
|
15461
|
-
}
|
|
15462
|
-
if (projections.length === 0) return true;
|
|
15463
|
-
const collectionKeys = new Set(projections.map((projection) => projection.collectionKey));
|
|
15464
|
-
if (!findDirectExhaustiveForEachCleanupFunction(releaseCall, collectionKeys, context)) return false;
|
|
15465
|
-
return !hasCollectionMutationBeforeRelease(registrationCall, releaseCall, collectionKeys, context);
|
|
15114
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15466
15115
|
};
|
|
15467
15116
|
const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
15468
15117
|
const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
@@ -15479,8 +15128,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15479
15128
|
const releaseVerbName = getReleaseVerbName(callNode);
|
|
15480
15129
|
if (!releaseVerbName) return false;
|
|
15481
15130
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
15482
|
-
const releaseReceiverKey =
|
|
15483
|
-
const releaseEventKey =
|
|
15131
|
+
const releaseReceiverKey = resolveExpressionKey(callee.object, context);
|
|
15132
|
+
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15484
15133
|
const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15485
15134
|
const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
|
|
15486
15135
|
const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
|
|
@@ -15490,13 +15139,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15490
15139
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15491
15140
|
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;
|
|
15492
15141
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15493
|
-
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;
|
|
15494
15142
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15495
|
-
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
15496
|
-
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
15497
|
-
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
15498
|
-
}
|
|
15499
|
-
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
15500
15143
|
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
15501
15144
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
15502
15145
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
@@ -15506,9 +15149,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15506
15149
|
if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
|
|
15507
15150
|
if (usage.eventKey !== null && releaseEventKey !== null && usage.eventKey !== releaseEventKey) {
|
|
15508
15151
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
15509
|
-
const registrationEventProjectionKey = resolveForEachProjectionKey(usage.node.arguments?.[0], context);
|
|
15510
|
-
const releaseEventProjectionKey = resolveForEachProjectionKey(callNode.arguments?.[0], context);
|
|
15511
|
-
if ((registrationEventProjectionKey !== null || releaseEventProjectionKey !== null) && registrationEventProjectionKey !== releaseEventProjectionKey) return false;
|
|
15512
15152
|
const usageForOfStatement = findForOfStatementForIteratorExpression(usageEventArgument, context);
|
|
15513
15153
|
const releaseForOfStatement = findForOfStatementForIteratorExpression(releaseEventArgument, context);
|
|
15514
15154
|
if (usageForOfStatement === null !== (releaseForOfStatement === null)) return false;
|
|
@@ -15522,7 +15162,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15522
15162
|
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
15523
15163
|
if (!isNodeOfType(registrationCallee, "MemberExpression")) return false;
|
|
15524
15164
|
if (!isStableLoopReceiver(registrationCallee.object, context) || !isStableLoopReceiver(callee.object, context) || registrationHandlerSymbolId === null || registrationHandlerSymbolId !== releaseHandlerSymbolId) return false;
|
|
15525
|
-
|
|
15165
|
+
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15166
|
+
const releaseCapture = resolveEventListenerCapture(callNode.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15167
|
+
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15526
15168
|
if (!isDirectExhaustiveForOfRelease(callNode, releaseForOfStatement)) return false;
|
|
15527
15169
|
}
|
|
15528
15170
|
}
|
|
@@ -15531,28 +15173,27 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15531
15173
|
return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
|
|
15532
15174
|
}
|
|
15533
15175
|
if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
|
|
15534
|
-
const
|
|
15535
|
-
const releaseHandler =
|
|
15176
|
+
const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
|
|
15177
|
+
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
15536
15178
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15537
|
-
const expectedHandlerKey =
|
|
15538
|
-
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[
|
|
15539
|
-
return expectedHandlerKey !== null &&
|
|
15179
|
+
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
15180
|
+
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
|
|
15181
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
15540
15182
|
}
|
|
15541
15183
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15542
15184
|
return true;
|
|
15543
15185
|
};
|
|
15544
15186
|
const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
|
|
15545
15187
|
const isReturnedEffectCleanupFunction = (functionNode, context) => {
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
return isReturned;
|
|
15188
|
+
let currentNode = functionNode;
|
|
15189
|
+
let parentNode = currentNode.parent;
|
|
15190
|
+
while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
|
|
15191
|
+
currentNode = parentNode;
|
|
15192
|
+
parentNode = currentNode.parent;
|
|
15193
|
+
}
|
|
15194
|
+
const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
|
|
15195
|
+
const effectCall = effectCallback?.parent;
|
|
15196
|
+
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
|
|
15556
15197
|
};
|
|
15557
15198
|
const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
15558
15199
|
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
|
|
@@ -15642,7 +15283,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
|
15642
15283
|
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15643
15284
|
matchingReturns.push(child);
|
|
15644
15285
|
});
|
|
15645
|
-
return
|
|
15286
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15646
15287
|
};
|
|
15647
15288
|
let didFindInvocation = false;
|
|
15648
15289
|
walkAst(componentFunction.body, (child) => {
|
|
@@ -15694,10 +15335,12 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
|
15694
15335
|
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15695
15336
|
};
|
|
15696
15337
|
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15697
|
-
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") || !
|
|
15338
|
+
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;
|
|
15339
|
+
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15698
15340
|
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15699
15341
|
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15700
|
-
|
|
15342
|
+
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15343
|
+
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15701
15344
|
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
15702
15345
|
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
15703
15346
|
const triggerRegistrations = [];
|
|
@@ -15922,7 +15565,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
15922
15565
|
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15923
15566
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
|
|
15924
15567
|
});
|
|
15925
|
-
return
|
|
15568
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15926
15569
|
};
|
|
15927
15570
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
15928
15571
|
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
@@ -15965,6 +15608,15 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
|
|
|
15965
15608
|
const returnStatement = expressionRoot.parent;
|
|
15966
15609
|
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
|
|
15967
15610
|
};
|
|
15611
|
+
const getFinalSequenceExpressionValue = (expression) => {
|
|
15612
|
+
let finalExpression = stripParenExpression(expression);
|
|
15613
|
+
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
15614
|
+
const sequenceResult = finalExpression.expressions.at(-1);
|
|
15615
|
+
if (!sequenceResult) break;
|
|
15616
|
+
finalExpression = stripParenExpression(sequenceResult);
|
|
15617
|
+
}
|
|
15618
|
+
return finalExpression;
|
|
15619
|
+
};
|
|
15968
15620
|
const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
|
|
15969
15621
|
if (!allowReturnedResourceEscape) return false;
|
|
15970
15622
|
let currentNode = resourceNode;
|
|
@@ -16213,7 +15865,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
|
|
|
16213
15865
|
if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
16214
15866
|
if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
|
|
16215
15867
|
});
|
|
16216
|
-
return
|
|
15868
|
+
return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
|
|
16217
15869
|
};
|
|
16218
15870
|
const matchingReturns = [];
|
|
16219
15871
|
walkInsideStatementBlocks(retainedFunction.body, (child) => {
|
|
@@ -19016,7 +18668,7 @@ const compileGlob = (pattern) => {
|
|
|
19016
18668
|
};
|
|
19017
18669
|
//#endregion
|
|
19018
18670
|
//#region src/plugin/rules/react-builtins/forbid-component-props.ts
|
|
19019
|
-
const resolveSettings$
|
|
18671
|
+
const resolveSettings$44 = (settings) => {
|
|
19020
18672
|
const reactDoctor = settings?.["react-doctor"];
|
|
19021
18673
|
return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
|
|
19022
18674
|
};
|
|
@@ -19068,7 +18720,7 @@ const forbidComponentProps = defineRule({
|
|
|
19068
18720
|
recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
|
|
19069
18721
|
category: "Architecture",
|
|
19070
18722
|
create: (context) => {
|
|
19071
|
-
const entries = resolveSettings$
|
|
18723
|
+
const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
|
|
19072
18724
|
return { JSXOpeningElement(node) {
|
|
19073
18725
|
if (entries.length === 0) return;
|
|
19074
18726
|
if (!isSupportedJsxName(node.name)) return;
|
|
@@ -19097,7 +18749,7 @@ const forbidComponentProps = defineRule({
|
|
|
19097
18749
|
//#endregion
|
|
19098
18750
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
19099
18751
|
const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
|
|
19100
|
-
const resolveSettings$
|
|
18752
|
+
const resolveSettings$43 = (settings) => {
|
|
19101
18753
|
const reactDoctor = settings?.["react-doctor"];
|
|
19102
18754
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
19103
18755
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19119,7 +18771,7 @@ const forbidDomProps = defineRule({
|
|
|
19119
18771
|
recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
|
|
19120
18772
|
category: "Architecture",
|
|
19121
18773
|
create: (context) => {
|
|
19122
|
-
const forbidMap = resolveSettings$
|
|
18774
|
+
const forbidMap = resolveSettings$43(context.settings);
|
|
19123
18775
|
return { JSXOpeningElement(node) {
|
|
19124
18776
|
if (forbidMap.size === 0) return;
|
|
19125
18777
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -19182,7 +18834,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
19182
18834
|
//#endregion
|
|
19183
18835
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
19184
18836
|
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.`;
|
|
19185
|
-
const resolveSettings$
|
|
18837
|
+
const resolveSettings$42 = (settings) => {
|
|
19186
18838
|
const reactDoctor = settings?.["react-doctor"];
|
|
19187
18839
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
19188
18840
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -19198,7 +18850,7 @@ const forbidElements = defineRule({
|
|
|
19198
18850
|
recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
|
|
19199
18851
|
category: "Architecture",
|
|
19200
18852
|
create: (context) => {
|
|
19201
|
-
const forbidMap = resolveSettings$
|
|
18853
|
+
const forbidMap = resolveSettings$42(context.settings);
|
|
19202
18854
|
return {
|
|
19203
18855
|
JSXOpeningElement(node) {
|
|
19204
18856
|
if (forbidMap.size === 0) return;
|
|
@@ -19307,7 +18959,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
19307
18959
|
"h5",
|
|
19308
18960
|
"h6"
|
|
19309
18961
|
];
|
|
19310
|
-
const resolveSettings$
|
|
18962
|
+
const resolveSettings$41 = (settings) => {
|
|
19311
18963
|
const reactDoctor = settings?.["react-doctor"];
|
|
19312
18964
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
19313
18965
|
return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
|
|
@@ -19320,7 +18972,7 @@ const headingHasContent = defineRule({
|
|
|
19320
18972
|
recommendation: "Put readable text in every heading.",
|
|
19321
18973
|
category: "Accessibility",
|
|
19322
18974
|
create: (context) => {
|
|
19323
|
-
const settings = resolveSettings$
|
|
18975
|
+
const settings = resolveSettings$41(context.settings);
|
|
19324
18976
|
return { JSXOpeningElement(node) {
|
|
19325
18977
|
const elementType = getElementType(node, context.settings);
|
|
19326
18978
|
if (!settings.headingTags.has(elementType)) return;
|
|
@@ -19341,7 +18993,7 @@ const headingHasContent = defineRule({
|
|
|
19341
18993
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
19342
18994
|
const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
|
|
19343
18995
|
const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
|
|
19344
|
-
const resolveSettings$
|
|
18996
|
+
const resolveSettings$40 = (settings) => {
|
|
19345
18997
|
const reactDoctor = settings?.["react-doctor"];
|
|
19346
18998
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
19347
18999
|
};
|
|
@@ -19368,7 +19020,7 @@ const hookUseState = defineRule({
|
|
|
19368
19020
|
recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
|
|
19369
19021
|
category: "Architecture",
|
|
19370
19022
|
create: (context) => {
|
|
19371
|
-
const { allowDestructuredState } = resolveSettings$
|
|
19023
|
+
const { allowDestructuredState } = resolveSettings$40(context.settings);
|
|
19372
19024
|
return { CallExpression(node) {
|
|
19373
19025
|
const callExpressionNode = node;
|
|
19374
19026
|
if (!isReactFunctionCall(callExpressionNode, "useState")) return;
|
|
@@ -19501,7 +19153,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19501
19153
|
//#endregion
|
|
19502
19154
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
19503
19155
|
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
19504
|
-
const resolveSettings$
|
|
19156
|
+
const resolveSettings$39 = (settings) => {
|
|
19505
19157
|
const reactDoctor = settings?.["react-doctor"];
|
|
19506
19158
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
19507
19159
|
};
|
|
@@ -19538,7 +19190,7 @@ const htmlHasLang = defineRule({
|
|
|
19538
19190
|
recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
|
|
19539
19191
|
category: "Accessibility",
|
|
19540
19192
|
create: (context) => {
|
|
19541
|
-
const settings = resolveSettings$
|
|
19193
|
+
const settings = resolveSettings$39(context.settings);
|
|
19542
19194
|
const tagSet = new Set(settings.htmlTags);
|
|
19543
19195
|
return { JSXOpeningElement(node) {
|
|
19544
19196
|
const tag = getElementType(node, context.settings);
|
|
@@ -20054,7 +19706,7 @@ const DEFAULT_REDUNDANT_WORDS = [
|
|
|
20054
19706
|
"photo",
|
|
20055
19707
|
"picture"
|
|
20056
19708
|
];
|
|
20057
|
-
const resolveSettings$
|
|
19709
|
+
const resolveSettings$38 = (settings) => {
|
|
20058
19710
|
const reactDoctor = settings?.["react-doctor"];
|
|
20059
19711
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
|
|
20060
19712
|
return {
|
|
@@ -20102,7 +19754,7 @@ const imgRedundantAlt = defineRule({
|
|
|
20102
19754
|
category: "Accessibility",
|
|
20103
19755
|
create: (context) => {
|
|
20104
19756
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
20105
|
-
const settings = resolveSettings$
|
|
19757
|
+
const settings = resolveSettings$38(context.settings);
|
|
20106
19758
|
return { JSXOpeningElement(node) {
|
|
20107
19759
|
const tag = getElementType(node, context.settings);
|
|
20108
19760
|
if (!settings.components.includes(tag)) return;
|
|
@@ -20410,7 +20062,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
|
|
|
20410
20062
|
"row",
|
|
20411
20063
|
"gridcell"
|
|
20412
20064
|
]);
|
|
20413
|
-
const resolveSettings$
|
|
20065
|
+
const resolveSettings$37 = (settings) => {
|
|
20414
20066
|
const reactDoctor = settings?.["react-doctor"];
|
|
20415
20067
|
return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
|
|
20416
20068
|
};
|
|
@@ -20422,7 +20074,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
20422
20074
|
recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
|
|
20423
20075
|
category: "Accessibility",
|
|
20424
20076
|
create: (context) => {
|
|
20425
|
-
const settings = resolveSettings$
|
|
20077
|
+
const settings = resolveSettings$37(context.settings);
|
|
20426
20078
|
const tabbableSet = new Set(settings.tabbable);
|
|
20427
20079
|
return { JSXOpeningElement(node) {
|
|
20428
20080
|
if (isLocalTestScaffoldJsx(node, context)) return;
|
|
@@ -22563,49 +22215,25 @@ const jsLengthCheckFirst = defineRule({
|
|
|
22563
22215
|
});
|
|
22564
22216
|
//#endregion
|
|
22565
22217
|
//#region src/plugin/utils/is-proven-global-namespace-reference.ts
|
|
22566
|
-
const GLOBAL_SELF_PROPERTY_NAMES = new Set([
|
|
22567
|
-
"global",
|
|
22568
|
-
"globalThis",
|
|
22569
|
-
"self",
|
|
22570
|
-
"window"
|
|
22571
|
-
]);
|
|
22572
22218
|
const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
const declaration = symbol.declarationNode;
|
|
22581
|
-
const initializer = declaration.init;
|
|
22582
|
-
if (!initializer) return false;
|
|
22583
|
-
const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
|
|
22584
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
22585
|
-
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
22586
|
-
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);
|
|
22587
|
-
if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
|
|
22588
|
-
visitedSymbolIds.add(symbol.id);
|
|
22589
|
-
currentExpression = initializer;
|
|
22590
|
-
}
|
|
22219
|
+
const strippedExpression = stripParenExpression(expression);
|
|
22220
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
22221
|
+
if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22222
|
+
const symbol = scopes.symbolFor(strippedExpression);
|
|
22223
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22224
|
+
visitedSymbolIds.add(symbol.id);
|
|
22225
|
+
return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
22591
22226
|
};
|
|
22592
22227
|
const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
const strippedExpression = stripParenExpression(currentExpression);
|
|
22596
|
-
if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22228
|
+
const strippedExpression = stripParenExpression(expression);
|
|
22229
|
+
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
22597
22230
|
if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22598
22231
|
const symbol = scopes.symbolFor(strippedExpression);
|
|
22599
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22232
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22600
22233
|
visitedSymbolIds.add(symbol.id);
|
|
22601
|
-
|
|
22602
|
-
const bindingIdentifier = symbol.bindingIdentifier;
|
|
22603
|
-
const bindingProperty = bindingIdentifier.parent;
|
|
22604
|
-
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);
|
|
22605
|
-
const directInitializer = getDirectConstInitializer(symbol);
|
|
22606
|
-
if (!directInitializer) return false;
|
|
22607
|
-
currentExpression = directInitializer;
|
|
22234
|
+
return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
|
|
22608
22235
|
}
|
|
22236
|
+
return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22609
22237
|
};
|
|
22610
22238
|
//#endregion
|
|
22611
22239
|
//#region src/plugin/rules/js-performance/js-min-max-loop.ts
|
|
@@ -23718,7 +23346,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
23718
23346
|
const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23719
23347
|
const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
|
|
23720
23348
|
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
|
|
23721
|
-
const resolveSettings$
|
|
23349
|
+
const resolveSettings$36 = (settings) => {
|
|
23722
23350
|
const reactDoctor = settings?.["react-doctor"];
|
|
23723
23351
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
23724
23352
|
return {
|
|
@@ -23736,7 +23364,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23736
23364
|
recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
|
|
23737
23365
|
category: "Architecture",
|
|
23738
23366
|
create: (context) => {
|
|
23739
|
-
const settings = resolveSettings$
|
|
23367
|
+
const settings = resolveSettings$36(context.settings);
|
|
23740
23368
|
const alwaysSet = new Set(settings.always);
|
|
23741
23369
|
const neverSet = new Set(settings.never);
|
|
23742
23370
|
return { JSXAttribute(node) {
|
|
@@ -23790,7 +23418,7 @@ const jsxBooleanValue = defineRule({
|
|
|
23790
23418
|
const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
|
|
23791
23419
|
const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
|
|
23792
23420
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
23793
|
-
const resolveSettings$
|
|
23421
|
+
const resolveSettings$35 = (settings) => {
|
|
23794
23422
|
const reactDoctor = settings?.["react-doctor"];
|
|
23795
23423
|
const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
|
|
23796
23424
|
if (isAllowedMode(ruleSettingsRaw)) return {
|
|
@@ -23886,7 +23514,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
23886
23514
|
recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
|
|
23887
23515
|
category: "Architecture",
|
|
23888
23516
|
create: (context) => {
|
|
23889
|
-
const settings = resolveSettings$
|
|
23517
|
+
const settings = resolveSettings$35(context.settings);
|
|
23890
23518
|
return {
|
|
23891
23519
|
JSXAttribute(node) {
|
|
23892
23520
|
const value = node.value;
|
|
@@ -23969,7 +23597,7 @@ const containsJsxElement = (root) => {
|
|
|
23969
23597
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
23970
23598
|
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.`;
|
|
23971
23599
|
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.`;
|
|
23972
|
-
const resolveSettings$
|
|
23600
|
+
const resolveSettings$34 = (settings) => {
|
|
23973
23601
|
const reactDoctor = settings?.["react-doctor"];
|
|
23974
23602
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
23975
23603
|
return {
|
|
@@ -23991,7 +23619,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
23991
23619
|
recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
|
|
23992
23620
|
category: "Architecture",
|
|
23993
23621
|
create: (context) => {
|
|
23994
|
-
const settings = resolveSettings$
|
|
23622
|
+
const settings = resolveSettings$34(context.settings);
|
|
23995
23623
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
23996
23624
|
const filename = normalizeFilename(context.filename ?? "fixture.tsx");
|
|
23997
23625
|
const extensionOnly = path.extname(filename).slice(1);
|
|
@@ -24032,7 +23660,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
24032
23660
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
24033
23661
|
const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
|
|
24034
23662
|
const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
|
|
24035
|
-
const resolveSettings$
|
|
23663
|
+
const resolveSettings$33 = (settings) => {
|
|
24036
23664
|
const reactDoctor = settings?.["react-doctor"];
|
|
24037
23665
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
24038
23666
|
};
|
|
@@ -24044,7 +23672,7 @@ const jsxFragments = defineRule({
|
|
|
24044
23672
|
recommendation: "Use one fragment style so identical wrappers do not look different across files.",
|
|
24045
23673
|
category: "Architecture",
|
|
24046
23674
|
create: (context) => {
|
|
24047
|
-
const { mode } = resolveSettings$
|
|
23675
|
+
const { mode } = resolveSettings$33(context.settings);
|
|
24048
23676
|
return {
|
|
24049
23677
|
JSXElement(node) {
|
|
24050
23678
|
if (mode !== "syntax") return;
|
|
@@ -24088,7 +23716,7 @@ const buildPropRegex = (handlerPropPrefix) => {
|
|
|
24088
23716
|
const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
24089
23717
|
return new RegExp(`^(${escaped})[A-Z].*$`);
|
|
24090
23718
|
};
|
|
24091
|
-
const resolveSettings$
|
|
23719
|
+
const resolveSettings$32 = (settings) => {
|
|
24092
23720
|
const reactDoctor = settings?.["react-doctor"];
|
|
24093
23721
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
|
|
24094
23722
|
let handlerPrefix = DEFAULT_HANDLER_PREFIX;
|
|
@@ -24164,7 +23792,7 @@ const jsxHandlerNames = defineRule({
|
|
|
24164
23792
|
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
|
|
24165
23793
|
category: "Architecture",
|
|
24166
23794
|
create: (context) => {
|
|
24167
|
-
const settings = resolveSettings$
|
|
23795
|
+
const settings = resolveSettings$32(context.settings);
|
|
24168
23796
|
return { JSXAttribute(node) {
|
|
24169
23797
|
if (settings.ignoreComponentNames.length > 0) {
|
|
24170
23798
|
const opening = node.parent;
|
|
@@ -24239,19 +23867,6 @@ const hasJsxKeyAttribute = (openingElement) => {
|
|
|
24239
23867
|
return false;
|
|
24240
23868
|
};
|
|
24241
23869
|
//#endregion
|
|
24242
|
-
//#region src/plugin/utils/is-component-function.ts
|
|
24243
|
-
const isFunctionAssignedToComponent = (functionNode) => {
|
|
24244
|
-
let cursor = functionNode.parent ?? null;
|
|
24245
|
-
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
24246
|
-
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
24247
|
-
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
24248
|
-
};
|
|
24249
|
-
const isComponentFunction$1 = (node) => {
|
|
24250
|
-
if (!isFunctionLike$1(node)) return false;
|
|
24251
|
-
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
24252
|
-
return isFunctionAssignedToComponent(node);
|
|
24253
|
-
};
|
|
24254
|
-
//#endregion
|
|
24255
23870
|
//#region src/plugin/utils/is-const-declared-binding.ts
|
|
24256
23871
|
const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
|
|
24257
23872
|
"VariableDeclarator",
|
|
@@ -24296,17 +23911,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
|
|
|
24296
23911
|
"flatMap",
|
|
24297
23912
|
"from"
|
|
24298
23913
|
]);
|
|
24299
|
-
const RENDERING_CALL_NAMES = new Set([
|
|
24300
|
-
"createPortal",
|
|
24301
|
-
"hydrate",
|
|
24302
|
-
"hydrateRoot",
|
|
24303
|
-
"render"
|
|
24304
|
-
]);
|
|
24305
23914
|
const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
|
|
24306
23915
|
const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
|
|
24307
|
-
const
|
|
23916
|
+
const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
|
|
24308
23917
|
const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
|
|
24309
|
-
const resolveSettings$
|
|
23918
|
+
const resolveSettings$31 = (settings) => {
|
|
24310
23919
|
const reactDoctor = settings?.["react-doctor"];
|
|
24311
23920
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
24312
23921
|
return {
|
|
@@ -24320,33 +23929,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
|
|
|
24320
23929
|
if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
|
|
24321
23930
|
return null;
|
|
24322
23931
|
};
|
|
24323
|
-
const isArrayNestedInObjectProperty = (arrayExpression) => {
|
|
24324
|
-
let current = findTransparentExpressionRoot(arrayExpression);
|
|
24325
|
-
while (current.parent) {
|
|
24326
|
-
const parent = current.parent;
|
|
24327
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
|
|
24328
|
-
current = findTransparentExpressionRoot(parent);
|
|
24329
|
-
continue;
|
|
24330
|
-
}
|
|
24331
|
-
return isNodeOfType(parent, "Property") && parent.value === current;
|
|
24332
|
-
}
|
|
24333
|
-
return false;
|
|
24334
|
-
};
|
|
24335
|
-
const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
|
|
24336
|
-
const transparentArray = findTransparentExpressionRoot(arrayExpression);
|
|
24337
|
-
const callExpression = transparentArray.parent;
|
|
24338
|
-
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
|
|
24339
|
-
const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
|
|
24340
|
-
if (isReactApiCall(callExpression, "createElement", scopes, {
|
|
24341
|
-
allowGlobalReactNamespace: true,
|
|
24342
|
-
allowUnboundBareCalls: true,
|
|
24343
|
-
resolveNamedAliases: true
|
|
24344
|
-
})) return argumentIndex < 2;
|
|
24345
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
24346
|
-
if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
|
|
24347
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
|
|
24348
|
-
return true;
|
|
24349
|
-
};
|
|
24350
23932
|
const isIdentityIteratorCallback = (callback) => {
|
|
24351
23933
|
const callbackFunction = stripParenExpression(callback);
|
|
24352
23934
|
if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
|
|
@@ -24443,7 +24025,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
|
|
|
24443
24025
|
namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
|
|
24444
24026
|
return iteratorCall;
|
|
24445
24027
|
};
|
|
24446
|
-
const findEnclosingIteratorContext = (jsxNode
|
|
24028
|
+
const findEnclosingIteratorContext = (jsxNode) => {
|
|
24447
24029
|
let current = jsxNode;
|
|
24448
24030
|
let isOutsideContainingFunction = false;
|
|
24449
24031
|
let didSeeReturnStatement = false;
|
|
@@ -24465,8 +24047,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
|
|
|
24465
24047
|
isOutsideContainingFunction = true;
|
|
24466
24048
|
} else if (isNodeOfType(parent, "ArrayExpression")) {
|
|
24467
24049
|
if (isOutsideContainingFunction) return null;
|
|
24468
|
-
if (isArrayNestedInObjectProperty(parent)) return null;
|
|
24469
|
-
if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
|
|
24470
24050
|
const arrayParent = parent.parent;
|
|
24471
24051
|
if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
|
|
24472
24052
|
if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
|
|
@@ -24548,15 +24128,9 @@ const isWithinChildrenToArray = (jsxNode) => {
|
|
|
24548
24128
|
const isKeyPropertyName = (propertyKey) => {
|
|
24549
24129
|
if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
|
|
24550
24130
|
if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
|
|
24551
|
-
return
|
|
24131
|
+
return true;
|
|
24552
24132
|
};
|
|
24553
24133
|
const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
|
|
24554
|
-
const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
|
|
24555
|
-
if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
|
|
24556
|
-
const firstParameter = functionNode.params?.[0];
|
|
24557
|
-
if (!firstParameter) return false;
|
|
24558
|
-
return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
|
|
24559
|
-
};
|
|
24560
24134
|
const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
24561
24135
|
const restElement = bindingIdentifier.parent;
|
|
24562
24136
|
if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
|
|
@@ -24566,7 +24140,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
|
|
|
24566
24140
|
const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
|
|
24567
24141
|
const functionNode = parameterNode.parent;
|
|
24568
24142
|
if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
|
|
24569
|
-
return
|
|
24143
|
+
return functionNode.params.some((param) => param === parameterNode);
|
|
24570
24144
|
};
|
|
24571
24145
|
const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
24572
24146
|
const restElement = bindingIdentifier.parent;
|
|
@@ -24579,14 +24153,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
|
|
|
24579
24153
|
}
|
|
24580
24154
|
return false;
|
|
24581
24155
|
};
|
|
24582
|
-
const
|
|
24583
|
-
let
|
|
24156
|
+
const isBindingObjectMutated = (scopeOwner, bindingName) => {
|
|
24157
|
+
let didFindMutation = false;
|
|
24584
24158
|
walkAst(scopeOwner, (node) => {
|
|
24585
|
-
if (
|
|
24159
|
+
if (didFindMutation) return false;
|
|
24586
24160
|
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
|
|
24587
24161
|
const assignedObject = stripParenExpression(node.left.object);
|
|
24588
|
-
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName
|
|
24589
|
-
|
|
24162
|
+
if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
|
|
24163
|
+
didFindMutation = true;
|
|
24590
24164
|
return false;
|
|
24591
24165
|
}
|
|
24592
24166
|
}
|
|
@@ -24594,63 +24168,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
|
|
|
24594
24168
|
const callee = node.callee;
|
|
24595
24169
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
|
|
24596
24170
|
const firstArgument = node.arguments[0];
|
|
24597
|
-
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName)
|
|
24598
|
-
|
|
24599
|
-
|
|
24600
|
-
if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
|
|
24601
|
-
didFindKeyAssignment = true;
|
|
24602
|
-
return false;
|
|
24603
|
-
}
|
|
24171
|
+
if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
|
|
24172
|
+
didFindMutation = true;
|
|
24173
|
+
return false;
|
|
24604
24174
|
}
|
|
24605
24175
|
}
|
|
24606
24176
|
}
|
|
24607
24177
|
});
|
|
24608
|
-
return
|
|
24178
|
+
return didFindMutation;
|
|
24609
24179
|
};
|
|
24610
|
-
const
|
|
24180
|
+
const spreadExpressionCanCarryKey = (expression, depth) => {
|
|
24611
24181
|
const inner = stripParenExpression(expression);
|
|
24612
24182
|
if (isNodeOfType(inner, "ObjectExpression")) {
|
|
24613
24183
|
for (const property of inner.properties) {
|
|
24614
24184
|
if (isNodeOfType(property, "SpreadElement")) {
|
|
24615
|
-
if (depth >= 3)
|
|
24616
|
-
if (
|
|
24185
|
+
if (depth >= 3) return true;
|
|
24186
|
+
if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
|
|
24617
24187
|
continue;
|
|
24618
24188
|
}
|
|
24619
|
-
if (!isNodeOfType(property, "Property"))
|
|
24189
|
+
if (!isNodeOfType(property, "Property")) return true;
|
|
24190
|
+
if (property.computed) return true;
|
|
24620
24191
|
if (isKeyPropertyName(property.key)) return true;
|
|
24621
24192
|
}
|
|
24622
24193
|
return false;
|
|
24623
24194
|
}
|
|
24624
24195
|
if (isNodeOfType(inner, "ConditionalExpression")) {
|
|
24625
|
-
if (depth >= 3) return
|
|
24626
|
-
return
|
|
24196
|
+
if (depth >= 3) return true;
|
|
24197
|
+
return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
|
|
24627
24198
|
}
|
|
24628
24199
|
if (isNodeOfType(inner, "LogicalExpression")) {
|
|
24629
|
-
if (depth >= 3) return
|
|
24630
|
-
if (inner.operator === "&&") return
|
|
24631
|
-
return
|
|
24200
|
+
if (depth >= 3) return true;
|
|
24201
|
+
if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24202
|
+
return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
|
|
24632
24203
|
}
|
|
24633
24204
|
if (isNodeOfType(inner, "Literal")) return false;
|
|
24634
24205
|
if (isThisPropsMember(inner)) return false;
|
|
24635
24206
|
if (isNodeOfType(inner, "Identifier")) {
|
|
24636
24207
|
if (inner.name === "undefined") return false;
|
|
24637
|
-
if (depth >= 3) return
|
|
24208
|
+
if (depth >= 3) return true;
|
|
24638
24209
|
const binding = findVariableInitializer(inner, inner.name);
|
|
24639
|
-
if (!binding) return
|
|
24210
|
+
if (!binding) return true;
|
|
24640
24211
|
if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
|
|
24641
24212
|
if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
|
|
24642
|
-
if (
|
|
24643
|
-
if (
|
|
24644
|
-
|
|
24645
|
-
return spreadExpressionHasKey(binding.initializer, depth + 1);
|
|
24213
|
+
if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
|
|
24214
|
+
if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
|
|
24215
|
+
return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
|
|
24646
24216
|
}
|
|
24647
|
-
return
|
|
24217
|
+
return true;
|
|
24648
24218
|
};
|
|
24649
|
-
const spreadCanOverwriteKey = (spreadAttribute) =>
|
|
24219
|
+
const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
|
|
24650
24220
|
const checkKeyBeforeSpread = (context, openingElement) => {
|
|
24651
24221
|
let keyIndex = null;
|
|
24652
24222
|
let keyAttribute = null;
|
|
24653
|
-
let
|
|
24223
|
+
let firstKeyCarryingSpreadIndex = null;
|
|
24654
24224
|
for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
|
|
24655
24225
|
const attribute = openingElement.attributes[attributeIndex];
|
|
24656
24226
|
if (isNodeOfType(attribute, "JSXAttribute")) {
|
|
@@ -24658,11 +24228,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
|
|
|
24658
24228
|
keyIndex = attributeIndex;
|
|
24659
24229
|
keyAttribute = attribute;
|
|
24660
24230
|
}
|
|
24661
|
-
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute))
|
|
24231
|
+
} else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
|
|
24662
24232
|
}
|
|
24663
|
-
if (keyIndex !== null &&
|
|
24233
|
+
if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
|
|
24664
24234
|
node: keyAttribute,
|
|
24665
|
-
message:
|
|
24235
|
+
message: KEY_AFTER_SPREAD
|
|
24666
24236
|
});
|
|
24667
24237
|
};
|
|
24668
24238
|
const getKeyAttributeValueString = (openingElement) => {
|
|
@@ -24706,7 +24276,7 @@ const jsxKey = defineRule({
|
|
|
24706
24276
|
severity: "error",
|
|
24707
24277
|
recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
|
|
24708
24278
|
create: (context) => {
|
|
24709
|
-
const settings = resolveSettings$
|
|
24279
|
+
const settings = resolveSettings$31(context.settings);
|
|
24710
24280
|
return {
|
|
24711
24281
|
JSXElement(node) {
|
|
24712
24282
|
const openingElement = node.openingElement;
|
|
@@ -24724,7 +24294,7 @@ const jsxKey = defineRule({
|
|
|
24724
24294
|
else seenKeys.add(keyValue.keyValue);
|
|
24725
24295
|
}
|
|
24726
24296
|
}
|
|
24727
|
-
const enclosingContext = findEnclosingIteratorContext(node
|
|
24297
|
+
const enclosingContext = findEnclosingIteratorContext(node);
|
|
24728
24298
|
if (!enclosingContext) return;
|
|
24729
24299
|
if (isWithinChildrenToArray(node)) return;
|
|
24730
24300
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
@@ -24760,7 +24330,7 @@ const jsxKey = defineRule({
|
|
|
24760
24330
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
24761
24331
|
const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
24762
24332
|
const DEFAULT_MAX_DEPTH = 14;
|
|
24763
|
-
const resolveSettings$
|
|
24333
|
+
const resolveSettings$30 = (settings) => {
|
|
24764
24334
|
const reactDoctor = settings?.["react-doctor"];
|
|
24765
24335
|
return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
|
|
24766
24336
|
};
|
|
@@ -24829,7 +24399,7 @@ const jsxMaxDepth = defineRule({
|
|
|
24829
24399
|
recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
|
|
24830
24400
|
category: "Architecture",
|
|
24831
24401
|
create: (context) => {
|
|
24832
|
-
const { max } = resolveSettings$
|
|
24402
|
+
const { max } = resolveSettings$30(context.settings);
|
|
24833
24403
|
const deepestLeafPerTree = /* @__PURE__ */ new Map();
|
|
24834
24404
|
const checkNode = (node) => {
|
|
24835
24405
|
if (!isLeafJsxNode(node)) return;
|
|
@@ -26801,7 +26371,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
26801
26371
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
26802
26372
|
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
26803
26373
|
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;
|
|
26804
|
-
const resolveSettings$
|
|
26374
|
+
const resolveSettings$29 = (settings) => {
|
|
26805
26375
|
const reactDoctor = settings?.["react-doctor"];
|
|
26806
26376
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
26807
26377
|
return reactDoctor.jsxNoScriptUrl ?? {};
|
|
@@ -26823,7 +26393,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26823
26393
|
recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
|
|
26824
26394
|
category: "Security",
|
|
26825
26395
|
create: (context) => {
|
|
26826
|
-
const options = resolveSettings$
|
|
26396
|
+
const options = resolveSettings$29(context.settings);
|
|
26827
26397
|
return { JSXOpeningElement(node) {
|
|
26828
26398
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26829
26399
|
const elementName = resolveJsxElementType(node);
|
|
@@ -26844,6 +26414,283 @@ const jsxNoScriptUrl = defineRule({
|
|
|
26844
26414
|
}
|
|
26845
26415
|
});
|
|
26846
26416
|
//#endregion
|
|
26417
|
+
//#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
|
|
26418
|
+
const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
|
|
26419
|
+
const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
|
|
26420
|
+
const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
|
|
26421
|
+
const resolveSettings$28 = (settings) => {
|
|
26422
|
+
const reactDoctor = settings?.["react-doctor"];
|
|
26423
|
+
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
|
|
26424
|
+
const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
|
|
26425
|
+
const linkComponents = /* @__PURE__ */ new Map();
|
|
26426
|
+
for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
|
|
26427
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26428
|
+
const linkAttribute = entry.linkAttribute ?? "href";
|
|
26429
|
+
linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
|
|
26430
|
+
}
|
|
26431
|
+
const formComponents = /* @__PURE__ */ new Map();
|
|
26432
|
+
for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
|
|
26433
|
+
else if (typeof entry === "object" && entry !== null) {
|
|
26434
|
+
const formAttribute = entry.formAttribute ?? "action";
|
|
26435
|
+
formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
|
|
26436
|
+
}
|
|
26437
|
+
return {
|
|
26438
|
+
enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
|
|
26439
|
+
warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
|
|
26440
|
+
allowReferrer: ruleSettings.allowReferrer ?? false,
|
|
26441
|
+
links: ruleSettings.links ?? true,
|
|
26442
|
+
forms: ruleSettings.forms ?? false,
|
|
26443
|
+
linkComponents,
|
|
26444
|
+
formComponents
|
|
26445
|
+
};
|
|
26446
|
+
};
|
|
26447
|
+
const isExternalLink = (href) => href.includes("//");
|
|
26448
|
+
const matchHrefExpression = (expression, state) => {
|
|
26449
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
|
|
26450
|
+
if (isExternalLink(expression.value)) state.isExternal = true;
|
|
26451
|
+
return;
|
|
26452
|
+
}
|
|
26453
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
26454
|
+
state.isDynamic = true;
|
|
26455
|
+
return;
|
|
26456
|
+
}
|
|
26457
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26458
|
+
matchHrefExpression(expression.consequent, state);
|
|
26459
|
+
matchHrefExpression(expression.alternate, state);
|
|
26460
|
+
}
|
|
26461
|
+
};
|
|
26462
|
+
const checkHref = (attributeValue, enforceDynamicLinks) => {
|
|
26463
|
+
const state = {
|
|
26464
|
+
isExternal: false,
|
|
26465
|
+
isDynamic: false
|
|
26466
|
+
};
|
|
26467
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
|
|
26468
|
+
else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
|
|
26469
|
+
if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
|
|
26470
|
+
return !(state.isExternal || state.isDynamic);
|
|
26471
|
+
};
|
|
26472
|
+
const checkRelValue = (text, allowReferrer) => {
|
|
26473
|
+
const tokens = text.split(/\s+/);
|
|
26474
|
+
if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
|
|
26475
|
+
return tokens.some((token) => token.toLowerCase() === "noreferrer");
|
|
26476
|
+
};
|
|
26477
|
+
const matchRelExpression = (expression, allowReferrer) => {
|
|
26478
|
+
const empty = {
|
|
26479
|
+
combined: false,
|
|
26480
|
+
testName: "",
|
|
26481
|
+
consequent: false,
|
|
26482
|
+
alternate: false
|
|
26483
|
+
};
|
|
26484
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26485
|
+
combined: checkRelValue(expression.value, allowReferrer),
|
|
26486
|
+
testName: "",
|
|
26487
|
+
consequent: false,
|
|
26488
|
+
alternate: false
|
|
26489
|
+
};
|
|
26490
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26491
|
+
const consequent = matchRelExpression(expression.consequent, allowReferrer);
|
|
26492
|
+
const alternate = matchRelExpression(expression.alternate, allowReferrer);
|
|
26493
|
+
const test = expression.test;
|
|
26494
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26495
|
+
combined: consequent.combined && alternate.combined,
|
|
26496
|
+
testName: test.name,
|
|
26497
|
+
consequent: consequent.combined,
|
|
26498
|
+
alternate: alternate.combined
|
|
26499
|
+
};
|
|
26500
|
+
return {
|
|
26501
|
+
combined: consequent.combined && alternate.combined,
|
|
26502
|
+
testName: "",
|
|
26503
|
+
consequent: consequent.combined,
|
|
26504
|
+
alternate: alternate.combined
|
|
26505
|
+
};
|
|
26506
|
+
}
|
|
26507
|
+
return empty;
|
|
26508
|
+
};
|
|
26509
|
+
const checkRel = (attributeValue, allowReferrer) => {
|
|
26510
|
+
const empty = {
|
|
26511
|
+
combined: false,
|
|
26512
|
+
testName: "",
|
|
26513
|
+
consequent: false,
|
|
26514
|
+
alternate: false
|
|
26515
|
+
};
|
|
26516
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26517
|
+
combined: checkRelValue(attributeValue.value, allowReferrer),
|
|
26518
|
+
testName: "",
|
|
26519
|
+
consequent: false,
|
|
26520
|
+
alternate: false
|
|
26521
|
+
};
|
|
26522
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26523
|
+
const expression = attributeValue.expression;
|
|
26524
|
+
if (expression.type === "JSXEmptyExpression") return empty;
|
|
26525
|
+
return matchRelExpression(expression, allowReferrer);
|
|
26526
|
+
}
|
|
26527
|
+
return empty;
|
|
26528
|
+
};
|
|
26529
|
+
const matchTargetExpression = (expression) => {
|
|
26530
|
+
const empty = {
|
|
26531
|
+
combined: false,
|
|
26532
|
+
testName: "",
|
|
26533
|
+
consequent: false,
|
|
26534
|
+
alternate: false
|
|
26535
|
+
};
|
|
26536
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
26537
|
+
combined: expression.value.toLowerCase() === "_blank",
|
|
26538
|
+
testName: "",
|
|
26539
|
+
consequent: false,
|
|
26540
|
+
alternate: false
|
|
26541
|
+
};
|
|
26542
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
26543
|
+
const consequent = matchTargetExpression(expression.consequent);
|
|
26544
|
+
const alternate = matchTargetExpression(expression.alternate);
|
|
26545
|
+
const test = expression.test;
|
|
26546
|
+
const combined = consequent.combined || alternate.combined;
|
|
26547
|
+
if (isNodeOfType(test, "Identifier")) return {
|
|
26548
|
+
combined,
|
|
26549
|
+
testName: test.name,
|
|
26550
|
+
consequent: consequent.combined,
|
|
26551
|
+
alternate: alternate.combined
|
|
26552
|
+
};
|
|
26553
|
+
return {
|
|
26554
|
+
combined,
|
|
26555
|
+
testName: "",
|
|
26556
|
+
consequent: consequent.combined,
|
|
26557
|
+
alternate: alternate.combined
|
|
26558
|
+
};
|
|
26559
|
+
}
|
|
26560
|
+
return empty;
|
|
26561
|
+
};
|
|
26562
|
+
const checkTarget = (attributeValue) => {
|
|
26563
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
26564
|
+
combined: attributeValue.value.toLowerCase() === "_blank",
|
|
26565
|
+
testName: "",
|
|
26566
|
+
consequent: false,
|
|
26567
|
+
alternate: false
|
|
26568
|
+
};
|
|
26569
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
26570
|
+
const expression = attributeValue.expression;
|
|
26571
|
+
if (expression.type === "JSXEmptyExpression") return {
|
|
26572
|
+
combined: false,
|
|
26573
|
+
testName: "",
|
|
26574
|
+
consequent: false,
|
|
26575
|
+
alternate: false
|
|
26576
|
+
};
|
|
26577
|
+
return matchTargetExpression(expression);
|
|
26578
|
+
}
|
|
26579
|
+
return {
|
|
26580
|
+
combined: false,
|
|
26581
|
+
testName: "",
|
|
26582
|
+
consequent: false,
|
|
26583
|
+
alternate: false
|
|
26584
|
+
};
|
|
26585
|
+
};
|
|
26586
|
+
const jsxNoTargetBlank = defineRule({
|
|
26587
|
+
id: "jsx-no-target-blank",
|
|
26588
|
+
title: "Unsafe target=_blank link",
|
|
26589
|
+
severity: "warn",
|
|
26590
|
+
recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
|
|
26591
|
+
category: "Security",
|
|
26592
|
+
create: (context) => {
|
|
26593
|
+
const settings = resolveSettings$28(context.settings);
|
|
26594
|
+
const isLink = (tagName) => {
|
|
26595
|
+
if (!settings.links) return false;
|
|
26596
|
+
if (tagName === "a") return true;
|
|
26597
|
+
return settings.linkComponents.has(tagName);
|
|
26598
|
+
};
|
|
26599
|
+
const isForm = (tagName) => {
|
|
26600
|
+
if (!settings.forms) return false;
|
|
26601
|
+
if (tagName === "form") return true;
|
|
26602
|
+
return settings.formComponents.has(tagName);
|
|
26603
|
+
};
|
|
26604
|
+
return { JSXOpeningElement(node) {
|
|
26605
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
26606
|
+
const tagName = resolveJsxElementType(node);
|
|
26607
|
+
if (!tagName) return;
|
|
26608
|
+
if (!isLink(tagName) && !isForm(tagName)) return;
|
|
26609
|
+
const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
|
|
26610
|
+
const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
|
|
26611
|
+
let targetTuple = {
|
|
26612
|
+
combined: false,
|
|
26613
|
+
testName: "",
|
|
26614
|
+
consequent: false,
|
|
26615
|
+
alternate: false
|
|
26616
|
+
};
|
|
26617
|
+
let relTuple = {
|
|
26618
|
+
combined: false,
|
|
26619
|
+
testName: "",
|
|
26620
|
+
consequent: false,
|
|
26621
|
+
alternate: false
|
|
26622
|
+
};
|
|
26623
|
+
let isHrefValid = true;
|
|
26624
|
+
let hasHrefValue = false;
|
|
26625
|
+
let warnSpread = false;
|
|
26626
|
+
let targetReportNode = node.name;
|
|
26627
|
+
let spreadReportNode = null;
|
|
26628
|
+
for (const attribute of node.attributes) {
|
|
26629
|
+
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
26630
|
+
if (settings.warnOnSpreadAttributes) {
|
|
26631
|
+
warnSpread = true;
|
|
26632
|
+
spreadReportNode = attribute;
|
|
26633
|
+
targetTuple = {
|
|
26634
|
+
combined: false,
|
|
26635
|
+
testName: "",
|
|
26636
|
+
consequent: false,
|
|
26637
|
+
alternate: false
|
|
26638
|
+
};
|
|
26639
|
+
relTuple = {
|
|
26640
|
+
combined: false,
|
|
26641
|
+
testName: "",
|
|
26642
|
+
consequent: false,
|
|
26643
|
+
alternate: false
|
|
26644
|
+
};
|
|
26645
|
+
isHrefValid = false;
|
|
26646
|
+
hasHrefValue = true;
|
|
26647
|
+
}
|
|
26648
|
+
continue;
|
|
26649
|
+
}
|
|
26650
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
26651
|
+
const attributeName = attribute.name;
|
|
26652
|
+
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
26653
|
+
const propName = attributeName.name;
|
|
26654
|
+
const value = attribute.value;
|
|
26655
|
+
if (propName === "target") {
|
|
26656
|
+
if (value) {
|
|
26657
|
+
targetTuple = checkTarget(value);
|
|
26658
|
+
targetReportNode = value;
|
|
26659
|
+
}
|
|
26660
|
+
} else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
|
|
26661
|
+
if (value) {
|
|
26662
|
+
hasHrefValue = true;
|
|
26663
|
+
isHrefValid = checkHref(value, settings.enforceDynamicLinks);
|
|
26664
|
+
}
|
|
26665
|
+
} else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
|
|
26666
|
+
}
|
|
26667
|
+
if (warnSpread) {
|
|
26668
|
+
if (hasHrefValue && isHrefValid || relTuple.combined) return;
|
|
26669
|
+
context.report({
|
|
26670
|
+
node: spreadReportNode ?? node,
|
|
26671
|
+
message: SPREAD_MESSAGE
|
|
26672
|
+
});
|
|
26673
|
+
return;
|
|
26674
|
+
}
|
|
26675
|
+
if (!isHrefValid) {
|
|
26676
|
+
if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
|
|
26677
|
+
const consequentBad = targetTuple.consequent && !relTuple.consequent;
|
|
26678
|
+
const alternateBad = targetTuple.alternate && !relTuple.alternate;
|
|
26679
|
+
if (consequentBad || alternateBad) context.report({
|
|
26680
|
+
node: targetReportNode,
|
|
26681
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26682
|
+
});
|
|
26683
|
+
return;
|
|
26684
|
+
}
|
|
26685
|
+
if (targetTuple.combined && !relTuple.combined) context.report({
|
|
26686
|
+
node: targetReportNode,
|
|
26687
|
+
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
26688
|
+
});
|
|
26689
|
+
}
|
|
26690
|
+
} };
|
|
26691
|
+
}
|
|
26692
|
+
});
|
|
26693
|
+
//#endregion
|
|
26847
26694
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
26848
26695
|
const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
26849
26696
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -28501,30 +28348,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
|
|
|
28501
28348
|
const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
|
|
28502
28349
|
//#endregion
|
|
28503
28350
|
//#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
|
|
28504
|
-
const unwrapAwaitedExpression = (node) => {
|
|
28505
|
-
let expression = stripParenExpression(node);
|
|
28506
|
-
while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
|
|
28507
|
-
return expression;
|
|
28508
|
-
};
|
|
28509
|
-
const isNextNavigationRedirectCall = (node, context) => {
|
|
28510
|
-
const expression = unwrapAwaitedExpression(node);
|
|
28511
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
28512
|
-
const callee = stripParenExpression(expression.callee);
|
|
28513
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
28514
|
-
if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
|
|
28515
|
-
const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
|
|
28516
|
-
return importedName === "redirect" || importedName === "permanentRedirect";
|
|
28517
|
-
};
|
|
28518
|
-
const isRedirectOnlyDefaultExport = (programNode, context) => {
|
|
28519
|
-
const defaultExport = findExportedValue(programNode, "default");
|
|
28520
|
-
if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
|
|
28521
|
-
if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
|
|
28522
|
-
if (defaultExport.body.body.length !== 1) return false;
|
|
28523
|
-
const onlyStatement = defaultExport.body.body[0];
|
|
28524
|
-
if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
|
|
28525
|
-
if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
|
|
28526
|
-
return false;
|
|
28527
|
-
};
|
|
28528
28351
|
const nextjsMissingMetadata = defineRule({
|
|
28529
28352
|
id: "nextjs-missing-metadata",
|
|
28530
28353
|
title: "Page missing metadata for search previews",
|
|
@@ -28544,7 +28367,6 @@ const nextjsMissingMetadata = defineRule({
|
|
|
28544
28367
|
if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
|
|
28545
28368
|
return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
|
|
28546
28369
|
})) return;
|
|
28547
|
-
if (isRedirectOnlyDefaultExport(programNode, context)) return;
|
|
28548
28370
|
if (hasAncestorMetadataLayout(context.filename ?? "")) return;
|
|
28549
28371
|
context.report({
|
|
28550
28372
|
node: programNode,
|
|
@@ -34673,19 +34495,6 @@ const noCascadingSetState = defineRetiredRule({
|
|
|
34673
34495
|
recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
|
|
34674
34496
|
});
|
|
34675
34497
|
//#endregion
|
|
34676
|
-
//#region src/plugin/utils/get-react-use-callback-call.ts
|
|
34677
|
-
const getReactUseCallbackCall = (expression, scopes) => {
|
|
34678
|
-
const identifier = stripParenExpression(expression);
|
|
34679
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
34680
|
-
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
34681
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
34682
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
34683
|
-
return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
|
|
34684
|
-
allowGlobalReactNamespace: true,
|
|
34685
|
-
resolveNamedAliases: true
|
|
34686
|
-
}) ? initializer : null;
|
|
34687
|
-
};
|
|
34688
|
-
//#endregion
|
|
34689
34498
|
//#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
|
|
34690
34499
|
const getRefCurrentSymbol = (node, context) => {
|
|
34691
34500
|
const expression = stripParenExpression(node);
|
|
@@ -35214,8 +35023,19 @@ const isBuiltinNamespaceCallee = (callee) => {
|
|
|
35214
35023
|
}
|
|
35215
35024
|
return false;
|
|
35216
35025
|
};
|
|
35026
|
+
const getReactUseCallbackSource = (reference, context) => {
|
|
35027
|
+
const identifier = reference.identifier;
|
|
35028
|
+
const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
|
|
35029
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
35030
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
35031
|
+
if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
|
|
35032
|
+
allowGlobalReactNamespace: true,
|
|
35033
|
+
resolveNamedAliases: true
|
|
35034
|
+
})) return initializer;
|
|
35035
|
+
return null;
|
|
35036
|
+
};
|
|
35217
35037
|
const getDependencyStateRefs = (analysis, context, dependencyReference) => {
|
|
35218
|
-
const useCallbackCall =
|
|
35038
|
+
const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
|
|
35219
35039
|
if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
35220
35040
|
const dependencyList = useCallbackCall.arguments?.[1];
|
|
35221
35041
|
if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
@@ -36803,6 +36623,19 @@ const noDefaultProps = defineRule({
|
|
|
36803
36623
|
} })
|
|
36804
36624
|
});
|
|
36805
36625
|
//#endregion
|
|
36626
|
+
//#region src/plugin/utils/is-component-function.ts
|
|
36627
|
+
const isFunctionAssignedToComponent = (functionNode) => {
|
|
36628
|
+
let cursor = functionNode.parent ?? null;
|
|
36629
|
+
while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
36630
|
+
if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
|
|
36631
|
+
return isNodeOfType(cursor, "ExportDefaultDeclaration");
|
|
36632
|
+
};
|
|
36633
|
+
const isComponentFunction$1 = (node) => {
|
|
36634
|
+
if (!isFunctionLike$1(node)) return false;
|
|
36635
|
+
if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
|
|
36636
|
+
return isFunctionAssignedToComponent(node);
|
|
36637
|
+
};
|
|
36638
|
+
//#endregion
|
|
36806
36639
|
//#region src/plugin/utils/create-component-prop-stack-tracker.ts
|
|
36807
36640
|
const extractDestructuredPropNames = (params) => {
|
|
36808
36641
|
const propNames = /* @__PURE__ */ new Set();
|
|
@@ -38716,17 +38549,6 @@ const noDynamicImportPath = defineRule({
|
|
|
38716
38549
|
})
|
|
38717
38550
|
});
|
|
38718
38551
|
//#endregion
|
|
38719
|
-
//#region src/plugin/utils/get-require-call-source.ts
|
|
38720
|
-
const getRequireCallSource = (expression) => {
|
|
38721
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
38722
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
38723
|
-
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
38724
|
-
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
38725
|
-
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
38726
|
-
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
38727
|
-
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
38728
|
-
};
|
|
38729
|
-
//#endregion
|
|
38730
38552
|
//#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
|
|
38731
38553
|
const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
|
|
38732
38554
|
"each",
|
|
@@ -38871,16 +38693,7 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
|
|
|
38871
38693
|
}
|
|
38872
38694
|
return dependencyStateSymbolIds;
|
|
38873
38695
|
};
|
|
38874
|
-
const
|
|
38875
|
-
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
38876
|
-
if (localFunction) return localFunction;
|
|
38877
|
-
const useCallbackCall = getReactUseCallbackCall(expression, scopes);
|
|
38878
|
-
if (!useCallbackCall) return null;
|
|
38879
|
-
const callback = useCallbackCall.arguments[0];
|
|
38880
|
-
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
38881
|
-
return resolveExactLocalFunction(callback, scopes);
|
|
38882
|
-
};
|
|
38883
|
-
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
|
|
38696
|
+
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38884
38697
|
const analysisFunctions = new Set([effectCallback]);
|
|
38885
38698
|
const pendingFunctions = [effectCallback];
|
|
38886
38699
|
while (pendingFunctions.length > 0) {
|
|
@@ -38888,9 +38701,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeSta
|
|
|
38888
38701
|
if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
|
|
38889
38702
|
walkInsideStatementBlocks(currentFunction.body, (child) => {
|
|
38890
38703
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38891
|
-
const invokedFunction =
|
|
38704
|
+
const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
38892
38705
|
if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
|
|
38893
|
-
if (isFunctionLike$1(invokedFunction) &&
|
|
38706
|
+
if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
|
|
38894
38707
|
analysisFunctions.add(invokedFunction);
|
|
38895
38708
|
pendingFunctions.push(invokedFunction);
|
|
38896
38709
|
});
|
|
@@ -38903,51 +38716,7 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
38903
38716
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
38904
38717
|
}
|
|
38905
38718
|
};
|
|
38906
|
-
const
|
|
38907
|
-
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
|
|
38908
|
-
const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
|
|
38909
|
-
return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
|
|
38910
|
-
};
|
|
38911
|
-
const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
|
|
38912
|
-
const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
|
|
38913
|
-
const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
|
|
38914
|
-
const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
|
|
38915
|
-
if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
|
|
38916
|
-
let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
|
|
38917
|
-
if (!isFunctionLike$1(analysisFunction)) return false;
|
|
38918
|
-
let hasExecutableDefault = false;
|
|
38919
|
-
for (const parameter of analysisFunction.params) {
|
|
38920
|
-
walkInsideStatementBlocks(parameter, (child) => {
|
|
38921
|
-
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;
|
|
38922
|
-
});
|
|
38923
|
-
if (hasExecutableDefault) return true;
|
|
38924
|
-
}
|
|
38925
|
-
return false;
|
|
38926
|
-
});
|
|
38927
|
-
const stableWrittenStateNames = /* @__PURE__ */ new Set();
|
|
38928
|
-
const allWrittenStateNames = /* @__PURE__ */ new Set();
|
|
38929
|
-
visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
|
|
38930
|
-
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
38931
|
-
if (writtenStateName) allWrittenStateNames.add(writtenStateName);
|
|
38932
|
-
});
|
|
38933
|
-
visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
|
|
38934
|
-
if (hasUnprovenObservableWork) return;
|
|
38935
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
38936
|
-
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
38937
|
-
if (writtenStateName) {
|
|
38938
|
-
stableWrittenStateNames.add(writtenStateName);
|
|
38939
|
-
return;
|
|
38940
|
-
}
|
|
38941
|
-
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
38942
|
-
if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
|
|
38943
|
-
hasUnprovenObservableWork = true;
|
|
38944
|
-
return;
|
|
38945
|
-
}
|
|
38946
|
-
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;
|
|
38947
|
-
});
|
|
38948
|
-
return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
|
|
38949
|
-
};
|
|
38950
|
-
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
38719
|
+
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38951
38720
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38952
38721
|
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
38953
38722
|
const literalValue = unwrappedExpression.value;
|
|
@@ -38955,49 +38724,46 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
|
|
|
38955
38724
|
return null;
|
|
38956
38725
|
}
|
|
38957
38726
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
38958
|
-
|
|
38959
|
-
if (symbol?.id === stateSymbolId) return stateValue;
|
|
38960
|
-
if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
|
|
38727
|
+
if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
|
|
38961
38728
|
if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
|
|
38962
|
-
if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
|
|
38963
38729
|
const immutableSymbol = scopes.symbolFor(unwrappedExpression);
|
|
38964
38730
|
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;
|
|
38965
|
-
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id)
|
|
38731
|
+
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
|
|
38966
38732
|
}
|
|
38967
38733
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
|
|
38968
38734
|
if (unwrappedExpression.operator === "void") return { value: void 0 };
|
|
38969
38735
|
if (unwrappedExpression.operator !== "!") return null;
|
|
38970
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38736
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38971
38737
|
return argumentValue ? { value: !argumentValue.value } : null;
|
|
38972
38738
|
}
|
|
38973
38739
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
38974
38740
|
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")) {
|
|
38975
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38741
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38976
38742
|
return argumentValue ? { value: Boolean(argumentValue.value) } : null;
|
|
38977
38743
|
}
|
|
38978
38744
|
return null;
|
|
38979
38745
|
}
|
|
38980
38746
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
38981
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38747
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38982
38748
|
if (!leftValue) return null;
|
|
38983
38749
|
if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
|
|
38984
38750
|
if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
|
|
38985
38751
|
if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
|
|
38986
|
-
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38752
|
+
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38987
38753
|
}
|
|
38988
38754
|
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
38989
|
-
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38755
|
+
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38990
38756
|
if (!testValue) return null;
|
|
38991
|
-
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38757
|
+
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38992
38758
|
}
|
|
38993
38759
|
if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
|
|
38994
|
-
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38760
|
+
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38995
38761
|
if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
|
|
38996
38762
|
return null;
|
|
38997
38763
|
}
|
|
38998
38764
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
38999
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
39000
|
-
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds
|
|
38765
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38766
|
+
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39001
38767
|
if (!leftValue || !rightValue) return null;
|
|
39002
38768
|
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
39003
38769
|
const areEqual = leftValue.value === rightValue.value;
|
|
@@ -39036,7 +38802,8 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39036
38802
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39037
38803
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39038
38804
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
39039
|
-
const
|
|
38805
|
+
const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
|
|
38806
|
+
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
39040
38807
|
if (!stateName) return;
|
|
39041
38808
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
39042
38809
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -39052,27 +38819,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39052
38819
|
const isGlobalBooleanCall = (node, scopes) => {
|
|
39053
38820
|
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
|
|
39054
38821
|
};
|
|
39055
|
-
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes
|
|
38822
|
+
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
|
|
39056
38823
|
let currentNode = workNode;
|
|
39057
38824
|
while (currentNode.parent) {
|
|
39058
38825
|
const parentNode = currentNode.parent;
|
|
39059
38826
|
if (isFunctionLike$1(parentNode)) break;
|
|
39060
38827
|
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
39061
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue
|
|
38828
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39062
38829
|
if (testValue) {
|
|
39063
38830
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39064
38831
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39065
38832
|
}
|
|
39066
38833
|
}
|
|
39067
38834
|
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
39068
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue
|
|
38835
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39069
38836
|
if (testValue) {
|
|
39070
38837
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39071
38838
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39072
38839
|
}
|
|
39073
38840
|
}
|
|
39074
38841
|
if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
|
|
39075
|
-
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue
|
|
38842
|
+
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
|
|
39076
38843
|
if (leftValue) {
|
|
39077
38844
|
if (parentNode.operator === "&&" && !leftValue.value) return false;
|
|
39078
38845
|
if (parentNode.operator === "||" && leftValue.value) return false;
|
|
@@ -39084,7 +38851,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39084
38851
|
if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
|
|
39085
38852
|
const earlierStatement = parentNode.body[index];
|
|
39086
38853
|
if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
|
|
39087
|
-
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue
|
|
38854
|
+
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
|
|
39088
38855
|
}
|
|
39089
38856
|
}
|
|
39090
38857
|
currentNode = parentNode;
|
|
@@ -39094,93 +38861,24 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39094
38861
|
const isReaderWorkNode = (node, analysisFunctions, scopes) => {
|
|
39095
38862
|
if (isNodeOfType(node, "CallExpression")) {
|
|
39096
38863
|
if (isGlobalBooleanCall(node, scopes)) return false;
|
|
39097
|
-
const invokedFunction =
|
|
38864
|
+
const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
|
|
39098
38865
|
return !invokedFunction || !analysisFunctions.has(invokedFunction);
|
|
39099
38866
|
}
|
|
39100
38867
|
return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
|
|
39101
38868
|
};
|
|
39102
|
-
const
|
|
39103
|
-
if (
|
|
39104
|
-
const
|
|
39105
|
-
|
|
39106
|
-
return knownStateValues;
|
|
39107
|
-
};
|
|
39108
|
-
const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
|
|
39109
|
-
if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
|
|
39110
|
-
const parameterStateValues = /* @__PURE__ */ new Map();
|
|
39111
|
-
const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
|
|
39112
|
-
for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
|
|
39113
|
-
const rawParameter = invokedFunction.params[parameterIndex];
|
|
39114
|
-
const rawArgument = invocation.arguments[parameterIndex];
|
|
39115
|
-
if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
|
|
39116
|
-
const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
|
|
39117
|
-
if (!isNodeOfType(parameter, "Identifier")) continue;
|
|
39118
|
-
const parameterSymbol = scopes.symbolFor(parameter);
|
|
39119
|
-
if (!parameterSymbol) continue;
|
|
39120
|
-
let argumentValue = null;
|
|
39121
|
-
if (rawArgument) {
|
|
39122
|
-
argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
|
|
39123
|
-
if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39124
|
-
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39125
|
-
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39126
|
-
}
|
|
39127
|
-
} else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39128
|
-
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39129
|
-
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39130
|
-
} else argumentValue = { value: void 0 };
|
|
39131
|
-
if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
|
|
39132
|
-
}
|
|
39133
|
-
return parameterStateValues;
|
|
39134
|
-
};
|
|
39135
|
-
const haveEqualParameterStateValues = (leftValues, rightValues) => {
|
|
39136
|
-
if (leftValues.size !== rightValues.size) return false;
|
|
39137
|
-
for (const [symbolId, leftValue] of leftValues) {
|
|
39138
|
-
const rightValue = rightValues.get(symbolId);
|
|
39139
|
-
if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
|
|
39140
|
-
}
|
|
39141
|
-
return true;
|
|
39142
|
-
};
|
|
39143
|
-
const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
39144
|
-
const pendingFrames = [{
|
|
39145
|
-
functionNode: readerEffect.callback,
|
|
39146
|
-
parameterStateValues: /* @__PURE__ */ new Map()
|
|
39147
|
-
}];
|
|
39148
|
-
const visitedParameterStateValues = /* @__PURE__ */ new Map();
|
|
39149
|
-
while (pendingFrames.length > 0) {
|
|
39150
|
-
const frame = pendingFrames.pop();
|
|
39151
|
-
if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
|
|
39152
|
-
const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
|
|
39153
|
-
if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
|
|
39154
|
-
functionParameterStateValues.push(frame.parameterStateValues);
|
|
39155
|
-
visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
|
|
39156
|
-
const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
|
|
38869
|
+
const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
|
|
38870
|
+
if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
|
|
38871
|
+
for (const writtenValue of writeInfo.values) {
|
|
38872
|
+
const stateValue = { value: writtenValue };
|
|
39157
38873
|
let didFindReachableWork = false;
|
|
39158
|
-
|
|
39159
|
-
if (didFindReachableWork) return;
|
|
39160
|
-
if (
|
|
39161
|
-
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
39162
|
-
if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
|
|
39163
|
-
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
|
|
39164
|
-
functionNode: invokedFunction,
|
|
39165
|
-
parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
|
|
39166
|
-
});
|
|
39167
|
-
return;
|
|
39168
|
-
}
|
|
39169
|
-
}
|
|
39170
|
-
if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
|
|
38874
|
+
visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
|
|
38875
|
+
if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
|
|
38876
|
+
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
|
|
39171
38877
|
});
|
|
39172
38878
|
if (didFindReachableWork) return true;
|
|
39173
38879
|
}
|
|
39174
38880
|
return false;
|
|
39175
38881
|
};
|
|
39176
|
-
const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
|
|
39177
|
-
const writeInfo = writerEffect.stateWrites.get(writtenStateName);
|
|
39178
|
-
const stateSymbolId = stateSymbolIds.get(writtenStateName);
|
|
39179
|
-
if (!writeInfo || stateSymbolId === void 0) return true;
|
|
39180
|
-
if (writeInfo.hasUnknownValue) return true;
|
|
39181
|
-
for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
|
|
39182
|
-
return false;
|
|
39183
|
-
};
|
|
39184
38882
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
39185
38883
|
const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
39186
38884
|
"clear",
|
|
@@ -39191,499 +38889,26 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
39191
38889
|
"keys",
|
|
39192
38890
|
"values"
|
|
39193
38891
|
]);
|
|
39194
|
-
const
|
|
39195
|
-
|
|
39196
|
-
|
|
39197
|
-
"
|
|
39198
|
-
|
|
39199
|
-
|
|
39200
|
-
|
|
39201
|
-
["got", new Set(["got"])],
|
|
39202
|
-
["wretch", new Set(["wretch"])],
|
|
39203
|
-
["ofetch", new Set(["ofetch"])],
|
|
39204
|
-
["setTimeout", new Set([
|
|
39205
|
-
"node:timers",
|
|
39206
|
-
"node:timers/promises",
|
|
39207
|
-
"timers",
|
|
39208
|
-
"timers/promises"
|
|
39209
|
-
])],
|
|
39210
|
-
["setInterval", new Set([
|
|
39211
|
-
"node:timers",
|
|
39212
|
-
"node:timers/promises",
|
|
39213
|
-
"timers",
|
|
39214
|
-
"timers/promises"
|
|
39215
|
-
])]
|
|
39216
|
-
]);
|
|
39217
|
-
const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
|
|
39218
|
-
["axios", "axios"],
|
|
39219
|
-
["cross-fetch", "fetch"],
|
|
39220
|
-
["got", "got"],
|
|
39221
|
-
["ky", "ky"],
|
|
39222
|
-
["node-fetch", "fetch"],
|
|
39223
|
-
["wretch", "wretch"]
|
|
39224
|
-
]);
|
|
39225
|
-
const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
|
|
39226
|
-
"axios",
|
|
39227
|
-
"cross-fetch",
|
|
39228
|
-
"got",
|
|
39229
|
-
"ky",
|
|
39230
|
-
"node-fetch",
|
|
39231
|
-
"ofetch",
|
|
39232
|
-
"undici",
|
|
39233
|
-
"wretch"
|
|
39234
|
-
]);
|
|
39235
|
-
const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
|
|
39236
|
-
"axios",
|
|
39237
|
-
"got",
|
|
39238
|
-
"ky",
|
|
39239
|
-
"ofetch",
|
|
39240
|
-
"wretch"
|
|
39241
|
-
]);
|
|
39242
|
-
const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
|
|
39243
|
-
...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
|
|
39244
|
-
"fetch",
|
|
39245
|
-
"patch",
|
|
39246
|
-
"post",
|
|
39247
|
-
"put",
|
|
39248
|
-
"request"
|
|
39249
|
-
]);
|
|
39250
|
-
const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
|
|
39251
|
-
const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
|
|
39252
|
-
const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
|
|
39253
|
-
...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
|
|
39254
|
-
...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
|
|
39255
|
-
"EventTarget",
|
|
39256
|
-
"XMLHttpRequest"
|
|
39257
|
-
]);
|
|
39258
|
-
const getDestructuredBindingDepth = (bindingIdentifier) => {
|
|
39259
|
-
let bindingNode = bindingIdentifier;
|
|
39260
|
-
let depth = 0;
|
|
39261
|
-
while (true) {
|
|
39262
|
-
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
39263
|
-
const property = bindingNode.parent;
|
|
39264
|
-
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
|
|
39265
|
-
depth += 1;
|
|
39266
|
-
bindingNode = property.parent;
|
|
39267
|
-
}
|
|
39268
|
-
};
|
|
39269
|
-
const isMutatedMemberExpression = (rawMemberExpression) => {
|
|
39270
|
-
let expression = findTransparentExpressionRoot(rawMemberExpression);
|
|
39271
|
-
while (expression.parent) {
|
|
39272
|
-
const parent = expression.parent;
|
|
39273
|
-
if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
|
|
39274
|
-
expression = findTransparentExpressionRoot(parent);
|
|
39275
|
-
continue;
|
|
39276
|
-
}
|
|
39277
|
-
if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
|
|
39278
|
-
expression = findTransparentExpressionRoot(parent);
|
|
39279
|
-
continue;
|
|
39280
|
-
}
|
|
39281
|
-
if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
|
|
39282
|
-
expression = findTransparentExpressionRoot(parent);
|
|
39283
|
-
continue;
|
|
39284
|
-
}
|
|
39285
|
-
if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
|
|
39286
|
-
expression = findTransparentExpressionRoot(parent);
|
|
39287
|
-
continue;
|
|
39288
|
-
}
|
|
39289
|
-
if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
|
|
39290
|
-
expression = findTransparentExpressionRoot(parent);
|
|
39291
|
-
continue;
|
|
39292
|
-
}
|
|
39293
|
-
break;
|
|
39294
|
-
}
|
|
39295
|
-
const parent = expression.parent;
|
|
39296
|
-
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);
|
|
39297
|
-
};
|
|
39298
|
-
const isReactHookDependencyArgument = (expression, scopes) => {
|
|
39299
|
-
const call = expression.parent;
|
|
39300
|
-
return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
|
|
39301
|
-
allowGlobalReactNamespace: true,
|
|
39302
|
-
allowUnboundBareCalls: true,
|
|
39303
|
-
resolveNamedAliases: true
|
|
39304
|
-
}));
|
|
39305
|
-
};
|
|
39306
|
-
const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
|
|
39307
|
-
const getContainingReactDependencyValue = (rawExpression, containerKind) => {
|
|
39308
|
-
const expression = findTransparentExpressionRoot(rawExpression);
|
|
39309
|
-
const parent = expression.parent;
|
|
39310
|
-
if (isNodeOfType(parent, "ArrayExpression")) return {
|
|
39311
|
-
expression: parent,
|
|
39312
|
-
containerKind: "array"
|
|
39313
|
-
};
|
|
39314
|
-
if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
|
|
39315
|
-
expression: parent.parent,
|
|
39316
|
-
containerKind: "array"
|
|
39317
|
-
};
|
|
39318
|
-
if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
|
|
39319
|
-
expression: parent.parent,
|
|
39320
|
-
containerKind: "object"
|
|
39321
|
-
};
|
|
39322
|
-
return null;
|
|
39323
|
-
};
|
|
39324
|
-
const getFullArrayRestCopySymbol = (initializer, scopes) => {
|
|
39325
|
-
const declaration = initializer.parent;
|
|
39326
|
-
if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
|
|
39327
|
-
const restElement = declaration.id.elements[0];
|
|
39328
|
-
if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
|
|
39329
|
-
const symbol = scopes.symbolFor(restElement.argument);
|
|
39330
|
-
return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
|
|
39331
|
-
};
|
|
39332
|
-
const isReactDependencyArrayReference = (initialExpression, scopes) => {
|
|
39333
|
-
const pendingPaths = [{
|
|
39334
|
-
expression: initialExpression,
|
|
39335
|
-
containerKind: null
|
|
39336
|
-
}];
|
|
39337
|
-
const visitedExpressions = /* @__PURE__ */ new Set();
|
|
39338
|
-
let didFindReactDependencyArgument = false;
|
|
39339
|
-
while (pendingPaths.length > 0) {
|
|
39340
|
-
const pendingPath = pendingPaths.pop();
|
|
39341
|
-
if (!pendingPath) continue;
|
|
39342
|
-
const expression = findTransparentExpressionRoot(pendingPath.expression);
|
|
39343
|
-
if (visitedExpressions.has(expression)) continue;
|
|
39344
|
-
visitedExpressions.add(expression);
|
|
39345
|
-
if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
|
|
39346
|
-
didFindReactDependencyArgument = true;
|
|
39347
|
-
continue;
|
|
39348
|
-
}
|
|
39349
|
-
const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
|
|
39350
|
-
if (containingValue) {
|
|
39351
|
-
pendingPaths.push(containingValue);
|
|
39352
|
-
continue;
|
|
39353
|
-
}
|
|
39354
|
-
const declaration = expression.parent;
|
|
39355
|
-
if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
|
|
39356
|
-
const symbol = scopes.symbolFor(declaration.id);
|
|
39357
|
-
if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
|
|
39358
|
-
if (symbol.references.length === 0) return false;
|
|
39359
|
-
for (const reference of symbol.references) pendingPaths.push({
|
|
39360
|
-
expression: reference.identifier,
|
|
39361
|
-
containerKind: pendingPath.containerKind
|
|
39362
|
-
});
|
|
39363
|
-
continue;
|
|
39364
|
-
}
|
|
39365
|
-
if (pendingPath.containerKind === "array") {
|
|
39366
|
-
const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
|
|
39367
|
-
if (restCopySymbol) {
|
|
39368
|
-
if (restCopySymbol.references.length === 0) return false;
|
|
39369
|
-
for (const reference of restCopySymbol.references) pendingPaths.push({
|
|
39370
|
-
expression: reference.identifier,
|
|
39371
|
-
containerKind: "array"
|
|
39372
|
-
});
|
|
39373
|
-
continue;
|
|
39374
|
-
}
|
|
39375
|
-
}
|
|
39376
|
-
return false;
|
|
39377
|
-
}
|
|
39378
|
-
return didFindReactDependencyArgument;
|
|
39379
|
-
};
|
|
39380
|
-
const hasSafeReceiverAliases = (rootSymbols, scopes) => {
|
|
39381
|
-
const pendingSymbols = [...rootSymbols];
|
|
39382
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
39383
|
-
while (pendingSymbols.length > 0) {
|
|
39384
|
-
const symbol = pendingSymbols.pop();
|
|
39385
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
|
|
39386
|
-
visitedSymbolIds.add(symbol.id);
|
|
39387
|
-
for (const reference of symbol.references) {
|
|
39388
|
-
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39389
|
-
const container = expression.parent;
|
|
39390
|
-
if (isReactDependencyArrayReference(expression, scopes)) continue;
|
|
39391
|
-
if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
|
|
39392
|
-
if (isMutatedMemberExpression(container)) return false;
|
|
39393
|
-
continue;
|
|
39394
|
-
}
|
|
39395
|
-
if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
|
|
39396
|
-
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
|
|
39397
|
-
if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
|
|
39398
|
-
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
|
|
39399
|
-
const aliasSymbol = scopes.symbolFor(container.id);
|
|
39400
|
-
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39401
|
-
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39402
|
-
pendingSymbols.push(aliasSymbol);
|
|
39403
|
-
continue;
|
|
39404
|
-
}
|
|
39405
|
-
return false;
|
|
38892
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
|
|
38893
|
+
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
38894
|
+
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
38895
|
+
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
38896
|
+
const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
|
|
38897
|
+
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
38898
|
+
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
39406
38899
|
}
|
|
38900
|
+
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39407
38901
|
}
|
|
39408
|
-
return true;
|
|
39409
|
-
};
|
|
39410
|
-
const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
|
|
39411
|
-
for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
|
|
38902
|
+
if (isNodeOfType(returnedValue, "Identifier")) return true;
|
|
39412
38903
|
return false;
|
|
39413
38904
|
};
|
|
39414
|
-
const
|
|
39415
|
-
|
|
39416
|
-
|
|
39417
|
-
if (
|
|
39418
|
-
return
|
|
39419
|
-
};
|
|
39420
|
-
const getUnshadowedRequireSource = (rawExpression, scopes) => {
|
|
39421
|
-
let expression = stripParenExpression(rawExpression);
|
|
39422
|
-
while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
|
|
39423
|
-
if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
|
|
39424
|
-
return getRequireCallSource(rawExpression);
|
|
39425
|
-
};
|
|
39426
|
-
const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
|
|
39427
|
-
let expression = stripParenExpression(rawExpression);
|
|
39428
|
-
let outermostExportedName = null;
|
|
39429
|
-
let exportedPropertyDepth = 0;
|
|
39430
|
-
let resolvedBinding = null;
|
|
39431
|
-
let isCommonJsModuleObject = false;
|
|
39432
|
-
const traversedSymbolIds = new Set(visitedSymbolIds);
|
|
39433
|
-
const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
|
|
39434
|
-
while (true) {
|
|
39435
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39436
|
-
const propertyName = getStaticPropertyName(expression);
|
|
39437
|
-
if (propertyName === null) return null;
|
|
39438
|
-
outermostExportedName ??= propertyName;
|
|
39439
|
-
exportedPropertyDepth += 1;
|
|
39440
|
-
expression = stripParenExpression(expression.object);
|
|
39441
|
-
continue;
|
|
39442
|
-
}
|
|
39443
|
-
const requireSource = getUnshadowedRequireSource(expression, scopes);
|
|
39444
|
-
if (requireSource !== null) {
|
|
39445
|
-
resolvedBinding = {
|
|
39446
|
-
source: requireSource,
|
|
39447
|
-
exportedName: null,
|
|
39448
|
-
isModuleObject: true
|
|
39449
|
-
};
|
|
39450
|
-
isCommonJsModuleObject = true;
|
|
39451
|
-
break;
|
|
39452
|
-
}
|
|
39453
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39454
|
-
const symbol = scopes.symbolFor(expression);
|
|
39455
|
-
if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
|
|
39456
|
-
if (symbol.kind === "import") {
|
|
39457
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
39458
|
-
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
|
|
39459
|
-
if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
|
|
39460
|
-
source: importDeclaration.source.value,
|
|
39461
|
-
exportedName: getImportedName(symbol.declarationNode) ?? null,
|
|
39462
|
-
isModuleObject: false
|
|
39463
|
-
};
|
|
39464
|
-
else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
|
|
39465
|
-
source: importDeclaration.source.value,
|
|
39466
|
-
exportedName: "default",
|
|
39467
|
-
isModuleObject: false
|
|
39468
|
-
};
|
|
39469
|
-
else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
|
|
39470
|
-
source: importDeclaration.source.value,
|
|
39471
|
-
exportedName: null,
|
|
39472
|
-
isModuleObject: true
|
|
39473
|
-
};
|
|
39474
|
-
break;
|
|
39475
|
-
}
|
|
39476
|
-
const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
|
|
39477
|
-
if (importEqualsSource !== null) {
|
|
39478
|
-
resolvedBinding = {
|
|
39479
|
-
source: importEqualsSource,
|
|
39480
|
-
exportedName: null,
|
|
39481
|
-
isModuleObject: true
|
|
39482
|
-
};
|
|
39483
|
-
isCommonJsModuleObject = true;
|
|
39484
|
-
commonJsModuleObjectSymbols.add(symbol);
|
|
39485
|
-
break;
|
|
39486
|
-
}
|
|
39487
|
-
const directInitializer = getDirectUnreassignedInitializer(symbol);
|
|
39488
|
-
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
39489
|
-
const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
|
|
39490
|
-
if (!initializer) return null;
|
|
39491
|
-
traversedSymbolIds.add(symbol.id);
|
|
39492
|
-
outermostExportedName ??= destructuredPropertyName;
|
|
39493
|
-
if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
|
|
39494
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
39495
|
-
if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
|
|
39496
|
-
expression = unwrappedInitializer;
|
|
39497
|
-
}
|
|
39498
|
-
if (!resolvedBinding) return null;
|
|
39499
|
-
if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
|
|
39500
|
-
if (outermostExportedName === null) return resolvedBinding;
|
|
39501
|
-
if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
|
|
39502
|
-
return {
|
|
39503
|
-
source: resolvedBinding.source,
|
|
39504
|
-
exportedName: outermostExportedName,
|
|
39505
|
-
isModuleObject: false
|
|
39506
|
-
};
|
|
39507
|
-
};
|
|
39508
|
-
const getCanonicalExternalSyncExportName = (binding) => {
|
|
39509
|
-
if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
|
|
39510
|
-
return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
|
|
39511
|
-
};
|
|
39512
|
-
const isKnownExternalSyncDirectModuleBinding = (binding) => {
|
|
39513
|
-
const exportedName = getCanonicalExternalSyncExportName(binding);
|
|
39514
|
-
return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
|
|
39515
|
-
};
|
|
39516
|
-
const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
|
|
39517
|
-
"Array",
|
|
39518
|
-
"BigInt",
|
|
39519
|
-
"Boolean",
|
|
39520
|
-
"Date",
|
|
39521
|
-
"Number",
|
|
39522
|
-
"String",
|
|
39523
|
-
"Symbol"
|
|
39524
|
-
]);
|
|
39525
|
-
const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
|
|
39526
|
-
"Array",
|
|
39527
|
-
"Boolean",
|
|
39528
|
-
"Date",
|
|
39529
|
-
"Map",
|
|
39530
|
-
"Number",
|
|
39531
|
-
"Promise",
|
|
39532
|
-
"RegExp",
|
|
39533
|
-
"Set",
|
|
39534
|
-
"String",
|
|
39535
|
-
"URL",
|
|
39536
|
-
"URLSearchParams",
|
|
39537
|
-
"WeakMap",
|
|
39538
|
-
"WeakSet"
|
|
39539
|
-
]);
|
|
39540
|
-
const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
|
|
39541
|
-
"all",
|
|
39542
|
-
"allSettled",
|
|
39543
|
-
"any",
|
|
39544
|
-
"race",
|
|
39545
|
-
"reject",
|
|
39546
|
-
"resolve",
|
|
39547
|
-
"withResolvers"
|
|
39548
|
-
]);
|
|
39549
|
-
const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39550
|
-
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
|
|
39551
|
-
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39552
|
-
const isNonCleanupValue = (returnValue) => {
|
|
39553
|
-
const expression = stripParenExpression(returnValue);
|
|
39554
|
-
if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
|
|
39555
|
-
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;
|
|
39556
|
-
if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
|
|
39557
|
-
if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
|
|
39558
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39559
|
-
const finalExpression = expression.expressions.at(-1);
|
|
39560
|
-
return Boolean(finalExpression && isNonCleanupValue(finalExpression));
|
|
39561
|
-
}
|
|
39562
|
-
if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39563
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
39564
|
-
if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39565
|
-
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;
|
|
39566
|
-
if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
|
|
39567
|
-
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39568
|
-
return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
|
|
39569
|
-
};
|
|
39570
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
|
|
39571
|
-
let returnsOnlyNonCleanup = true;
|
|
39572
|
-
walkAst(functionNode.body, (child) => {
|
|
39573
|
-
if (!returnsOnlyNonCleanup) return false;
|
|
39574
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39575
|
-
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39576
|
-
if (child.argument && !isNonCleanupValue(child.argument)) {
|
|
39577
|
-
returnsOnlyNonCleanup = false;
|
|
39578
|
-
return false;
|
|
39579
|
-
}
|
|
39580
|
-
});
|
|
39581
|
-
return returnsOnlyNonCleanup;
|
|
39582
|
-
};
|
|
39583
|
-
const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39584
|
-
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
|
|
39585
|
-
hasCleanup: false,
|
|
39586
|
-
isValid: false
|
|
39587
|
-
};
|
|
39588
|
-
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39589
|
-
const getCleanupReturnProof = (returnValue) => {
|
|
39590
|
-
const expression = stripParenExpression(returnValue);
|
|
39591
|
-
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
|
|
39592
|
-
hasCleanup: true,
|
|
39593
|
-
isValid: true
|
|
39594
|
-
};
|
|
39595
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
39596
|
-
if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
|
|
39597
|
-
hasCleanup: false,
|
|
39598
|
-
isValid: true
|
|
39599
|
-
};
|
|
39600
|
-
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
39601
|
-
return {
|
|
39602
|
-
hasCleanup: Boolean(localFunction),
|
|
39603
|
-
isValid: Boolean(localFunction)
|
|
39604
|
-
};
|
|
39605
|
-
}
|
|
39606
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
|
|
39607
|
-
hasCleanup: false,
|
|
39608
|
-
isValid: true
|
|
39609
|
-
};
|
|
39610
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
39611
|
-
const consequentProof = getCleanupReturnProof(expression.consequent);
|
|
39612
|
-
const alternateProof = getCleanupReturnProof(expression.alternate);
|
|
39613
|
-
return {
|
|
39614
|
-
hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
|
|
39615
|
-
isValid: consequentProof.isValid && alternateProof.isValid
|
|
39616
|
-
};
|
|
39617
|
-
}
|
|
39618
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
39619
|
-
const leftProof = getCleanupReturnProof(expression.left);
|
|
39620
|
-
const rightProof = getCleanupReturnProof(expression.right);
|
|
39621
|
-
return {
|
|
39622
|
-
hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
|
|
39623
|
-
isValid: leftProof.isValid && rightProof.isValid
|
|
39624
|
-
};
|
|
39625
|
-
}
|
|
39626
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39627
|
-
const finalExpression = expression.expressions.at(-1);
|
|
39628
|
-
return finalExpression ? getCleanupReturnProof(finalExpression) : {
|
|
39629
|
-
hasCleanup: false,
|
|
39630
|
-
isValid: false
|
|
39631
|
-
};
|
|
39632
|
-
}
|
|
39633
|
-
if (!isNodeOfType(expression, "CallExpression")) return {
|
|
39634
|
-
hasCleanup: false,
|
|
39635
|
-
isValid: false
|
|
39636
|
-
};
|
|
39637
|
-
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39638
|
-
return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
|
|
39639
|
-
hasCleanup: false,
|
|
39640
|
-
isValid: false
|
|
39641
|
-
};
|
|
39642
|
-
};
|
|
39643
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
|
|
39644
|
-
let cleanupReturnProof = {
|
|
39645
|
-
hasCleanup: false,
|
|
39646
|
-
isValid: true
|
|
39647
|
-
};
|
|
39648
|
-
walkAst(functionNode.body, (child) => {
|
|
39649
|
-
if (!cleanupReturnProof.isValid) return false;
|
|
39650
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39651
|
-
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39652
|
-
if (!child.argument) return;
|
|
39653
|
-
const returnProof = getCleanupReturnProof(child.argument);
|
|
39654
|
-
if (!returnProof.isValid) {
|
|
39655
|
-
cleanupReturnProof = {
|
|
39656
|
-
hasCleanup: false,
|
|
39657
|
-
isValid: false
|
|
39658
|
-
};
|
|
39659
|
-
return false;
|
|
39660
|
-
}
|
|
39661
|
-
cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
|
|
39662
|
-
});
|
|
39663
|
-
return cleanupReturnProof;
|
|
39664
|
-
};
|
|
39665
|
-
const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
|
|
39666
|
-
const unwrappedReturnedValue = stripParenExpression(returnedValue);
|
|
39667
|
-
if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
|
|
39668
|
-
if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
|
|
39669
|
-
if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
|
|
39670
|
-
const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
|
|
39671
|
-
if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
39672
|
-
}
|
|
39673
|
-
const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
|
|
39674
|
-
if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
|
|
39675
|
-
if (invokedFunction) {
|
|
39676
|
-
const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
|
|
39677
|
-
if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
|
|
39678
|
-
}
|
|
39679
|
-
if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
|
|
39680
|
-
return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39681
|
-
}
|
|
39682
|
-
if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
|
|
38905
|
+
const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
38906
|
+
const isBrowserStorageReceiver = (receiver) => {
|
|
38907
|
+
if (!receiver) return false;
|
|
38908
|
+
if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
|
|
38909
|
+
if (isNodeOfType(receiver, "MemberExpression")) return isNodeOfType(receiver.property, "Identifier") && STORAGE_GLOBAL_NAMES.has(receiver.property.name);
|
|
39683
38910
|
return false;
|
|
39684
38911
|
};
|
|
39685
|
-
const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
|
|
39686
|
-
const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
|
|
39687
38912
|
const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
|
|
39688
38913
|
const collectStorageHookSetterNames = (componentBody) => {
|
|
39689
38914
|
const setterNames = /* @__PURE__ */ new Set();
|
|
@@ -39715,16 +38940,23 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
|
|
|
39715
38940
|
});
|
|
39716
38941
|
return didFindOpaqueSetterCall;
|
|
39717
38942
|
};
|
|
39718
|
-
const
|
|
38943
|
+
const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
|
|
39719
38944
|
allowGlobalReactNamespace: true,
|
|
39720
38945
|
allowUnboundBareCalls: true,
|
|
39721
38946
|
resolveNamedAliases: true
|
|
39722
|
-
})
|
|
39723
|
-
const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
|
|
38947
|
+
}) || isReactApiCall(expression, "createRef", scopes, {
|
|
39724
38948
|
allowGlobalReactNamespace: true,
|
|
39725
38949
|
allowUnboundBareCalls: true,
|
|
39726
38950
|
resolveNamedAliases: true
|
|
39727
|
-
});
|
|
38951
|
+
}));
|
|
38952
|
+
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
38953
|
+
const expression = stripParenExpression(rawExpression);
|
|
38954
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
38955
|
+
const symbol = scopes.symbolFor(expression);
|
|
38956
|
+
if (!symbol) return null;
|
|
38957
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
38958
|
+
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
38959
|
+
};
|
|
39728
38960
|
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
39729
38961
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
39730
38962
|
const symbol = scopes.symbolFor(openingElement.name);
|
|
@@ -39732,33 +38964,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
|
39732
38964
|
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
39733
38965
|
};
|
|
39734
38966
|
const isDirectHostJsxRef = (symbol, scopes) => {
|
|
39735
|
-
let
|
|
39736
|
-
const
|
|
39737
|
-
|
|
39738
|
-
|
|
39739
|
-
|
|
39740
|
-
if (!
|
|
39741
|
-
|
|
39742
|
-
|
|
39743
|
-
|
|
39744
|
-
|
|
39745
|
-
|
|
39746
|
-
|
|
39747
|
-
|
|
39748
|
-
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39749
|
-
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39750
|
-
pendingSymbols.push(aliasSymbol);
|
|
39751
|
-
continue;
|
|
39752
|
-
}
|
|
39753
|
-
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
39754
|
-
const attribute = container.parent;
|
|
39755
|
-
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
39756
|
-
const openingElement = attribute.parent;
|
|
39757
|
-
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
39758
|
-
didFindHostRef = true;
|
|
39759
|
-
}
|
|
39760
|
-
}
|
|
39761
|
-
return didFindHostRef;
|
|
38967
|
+
let hostRefCount = 0;
|
|
38968
|
+
for (const reference of symbol.references) {
|
|
38969
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
38970
|
+
const container = expression.parent;
|
|
38971
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
38972
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
38973
|
+
const attribute = container.parent;
|
|
38974
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
38975
|
+
const openingElement = attribute.parent;
|
|
38976
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
38977
|
+
hostRefCount += 1;
|
|
38978
|
+
}
|
|
38979
|
+
return hostRefCount > 0;
|
|
39762
38980
|
};
|
|
39763
38981
|
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
39764
38982
|
const identifier = stripParenExpression(expression);
|
|
@@ -39768,18 +38986,7 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
|
39768
38986
|
const rawFirstParameter = callback.params?.[0];
|
|
39769
38987
|
const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
|
|
39770
38988
|
const symbol = scopes.symbolFor(identifier);
|
|
39771
|
-
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter
|
|
39772
|
-
if (reference.flag !== "read") return false;
|
|
39773
|
-
let referenceRoot = reference.identifier;
|
|
39774
|
-
while (referenceRoot.parent) {
|
|
39775
|
-
const parent = referenceRoot.parent;
|
|
39776
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
|
|
39777
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
|
|
39778
|
-
if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
|
|
39779
|
-
referenceRoot = parent;
|
|
39780
|
-
}
|
|
39781
|
-
return true;
|
|
39782
|
-
}));
|
|
38989
|
+
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
|
|
39783
38990
|
};
|
|
39784
38991
|
const getDirectReactRefCall = (symbol, scopes) => {
|
|
39785
38992
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
@@ -39787,38 +38994,15 @@ const getDirectReactRefCall = (symbol, scopes) => {
|
|
|
39787
38994
|
const expression = stripParenExpression(initializer);
|
|
39788
38995
|
return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
|
|
39789
38996
|
};
|
|
39790
|
-
const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
|
|
39791
|
-
const expression = stripParenExpression(rawExpression);
|
|
39792
|
-
return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
|
|
39793
|
-
};
|
|
39794
|
-
const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
|
|
39795
|
-
if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
|
|
39796
|
-
const [initialValue] = refCall.arguments;
|
|
39797
|
-
return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
|
|
39798
|
-
};
|
|
39799
|
-
const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
|
|
39800
|
-
const assignment = currentExpression.parent;
|
|
39801
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
|
|
39802
|
-
const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
39803
|
-
return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
|
|
39804
|
-
};
|
|
39805
38997
|
const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
39806
|
-
const
|
|
39807
|
-
|
|
39808
|
-
const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
|
|
39809
|
-
const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
|
|
39810
|
-
if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
|
|
38998
|
+
const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
|
|
38999
|
+
if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
|
|
39811
39000
|
let intrinsicValueWriteCount = 0;
|
|
39812
|
-
let lazyEmptyMapInitializationCount = 0;
|
|
39813
39001
|
for (const reference of symbol.references) {
|
|
39814
39002
|
const identifier = findTransparentExpressionRoot(reference.identifier);
|
|
39815
39003
|
const currentMember = identifier.parent;
|
|
39816
39004
|
if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
|
|
39817
39005
|
const currentExpression = findTransparentExpressionRoot(currentMember);
|
|
39818
|
-
if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
|
|
39819
|
-
lazyEmptyMapInitializationCount += 1;
|
|
39820
|
-
continue;
|
|
39821
|
-
}
|
|
39822
39006
|
const methodMember = currentExpression.parent;
|
|
39823
39007
|
if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
|
|
39824
39008
|
const methodName = getStaticPropertyName(methodMember);
|
|
@@ -39831,7 +39015,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
|
39831
39015
|
if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
|
|
39832
39016
|
intrinsicValueWriteCount += 1;
|
|
39833
39017
|
}
|
|
39834
|
-
return intrinsicValueWriteCount > 0
|
|
39018
|
+
return intrinsicValueWriteCount > 0;
|
|
39835
39019
|
};
|
|
39836
39020
|
const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39837
39021
|
const expression = stripParenExpression(rawExpression);
|
|
@@ -39845,11 +39029,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
|
|
|
39845
39029
|
}
|
|
39846
39030
|
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39847
39031
|
if (getStaticPropertyName(expression) === "current") {
|
|
39848
|
-
const symbol =
|
|
39849
|
-
allowUnboundBareCalls: true,
|
|
39850
|
-
includeCreateRef: true,
|
|
39851
|
-
resolveNamedAliases: true
|
|
39852
|
-
});
|
|
39032
|
+
const symbol = getDirectReactRefSymbol(expression.object, scopes);
|
|
39853
39033
|
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
39854
39034
|
}
|
|
39855
39035
|
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
@@ -39867,80 +39047,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
|
|
|
39867
39047
|
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
39868
39048
|
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
39869
39049
|
};
|
|
39870
|
-
const
|
|
39871
|
-
|
|
39872
|
-
if (isNodeOfType(
|
|
39873
|
-
const receiverSymbol = scopes.symbolFor(expression);
|
|
39874
|
-
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
39875
|
-
}
|
|
39876
|
-
if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
|
|
39877
|
-
const moduleBinding = resolveExternalModuleBinding(expression, scopes);
|
|
39878
|
-
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;
|
|
39879
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
39880
|
-
const callee = stripParenExpression(expression.callee);
|
|
39881
|
-
return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
|
|
39882
|
-
}
|
|
39883
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39884
|
-
const symbol = scopes.symbolFor(expression);
|
|
39885
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39886
|
-
if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
|
|
39887
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39888
|
-
return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39889
|
-
};
|
|
39890
|
-
const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39891
|
-
const expression = stripParenExpression(rawExpression);
|
|
39892
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
39893
|
-
const receiverSymbol = scopes.symbolFor(expression);
|
|
39894
|
-
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
39895
|
-
}
|
|
39896
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
39897
|
-
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
39898
|
-
return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
|
|
39899
|
-
}
|
|
39900
|
-
if (isNodeOfType(expression, "NewExpression")) {
|
|
39901
|
-
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
39902
|
-
return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
|
|
39903
|
-
}
|
|
39904
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39905
|
-
const symbol = scopes.symbolFor(expression);
|
|
39906
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39907
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39908
|
-
return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39909
|
-
};
|
|
39910
|
-
const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39911
|
-
const expression = stripParenExpression(rawExpression);
|
|
39912
|
-
if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
|
|
39913
|
-
if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
|
|
39914
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39915
|
-
const symbol = scopes.symbolFor(expression);
|
|
39916
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
39917
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39918
|
-
return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
39919
|
-
};
|
|
39920
|
-
const isProvenExternalSyncDirectCallee = (callee, scopes) => {
|
|
39921
|
-
if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
|
|
39922
|
-
const moduleBinding = resolveExternalModuleBinding(callee, scopes);
|
|
39923
|
-
return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
|
|
39924
|
-
};
|
|
39925
|
-
const isExternalSyncNode = (node, scopes) => {
|
|
39926
|
-
if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
|
|
39927
|
-
if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
|
|
39928
|
-
const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
|
|
39929
|
-
return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
|
|
39930
|
-
allowUnboundBareCalls: true,
|
|
39931
|
-
includeCreateRef: true,
|
|
39932
|
-
resolveNamedAliases: true
|
|
39933
|
-
}));
|
|
39934
|
-
}
|
|
39050
|
+
const isExternalSyncNode = (node) => {
|
|
39051
|
+
if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
|
|
39052
|
+
if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
|
|
39935
39053
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
39936
|
-
if (
|
|
39054
|
+
if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
|
|
39937
39055
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
39938
39056
|
const propertyName = getStaticPropertyName(node.callee);
|
|
39939
39057
|
if (propertyName === null) return false;
|
|
39940
|
-
if (
|
|
39941
|
-
if (
|
|
39942
|
-
if (
|
|
39943
|
-
|
|
39058
|
+
if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
|
|
39059
|
+
if (isBrowserStorageReceiver(node.callee.object)) return true;
|
|
39060
|
+
if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
|
|
39061
|
+
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39062
|
+
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39944
39063
|
};
|
|
39945
39064
|
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
|
|
39946
39065
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -39949,7 +39068,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
|
|
|
39949
39068
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
|
|
39950
39069
|
let didFindExternalCall = false;
|
|
39951
39070
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39952
|
-
if (isExternalSyncNode(child
|
|
39071
|
+
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39953
39072
|
});
|
|
39954
39073
|
return didFindExternalCall;
|
|
39955
39074
|
};
|
|
@@ -39971,11 +39090,11 @@ const noEffectChain = defineRule({
|
|
|
39971
39090
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39972
39091
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
39973
39092
|
const stateIdentifier = binding.declarator.id.elements[0];
|
|
39974
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39975
39093
|
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
39976
39094
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39977
39095
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39978
39096
|
}
|
|
39097
|
+
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39979
39098
|
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39980
39099
|
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39981
39100
|
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
@@ -39988,11 +39107,10 @@ const noEffectChain = defineRule({
|
|
|
39988
39107
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39989
39108
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39990
39109
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39991
|
-
const stateWrites = collectStateWritesInEffect(
|
|
39110
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
|
|
39992
39111
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39993
39112
|
effectInfos.push({
|
|
39994
39113
|
node: effectCall,
|
|
39995
|
-
callback,
|
|
39996
39114
|
dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
|
|
39997
39115
|
stateWrites,
|
|
39998
39116
|
analysisFunctions,
|
|
@@ -40009,10 +39127,10 @@ const noEffectChain = defineRule({
|
|
|
40009
39127
|
if (readerEffect.isExternalSync) continue;
|
|
40010
39128
|
if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
|
|
40011
39129
|
let chainedStateName = null;
|
|
40012
|
-
for (const writtenName of writerEffect.stateWrites
|
|
39130
|
+
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
40013
39131
|
const writtenStateSymbolId = stateSymbolIds.get(writtenName);
|
|
40014
39132
|
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
40015
|
-
if (!canStateWriteReachReaderWork(
|
|
39133
|
+
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
40016
39134
|
chainedStateName = writtenName;
|
|
40017
39135
|
break;
|
|
40018
39136
|
}
|
|
@@ -44797,6 +43915,16 @@ const noManyBooleanProps = defineRule({
|
|
|
44797
43915
|
}
|
|
44798
43916
|
});
|
|
44799
43917
|
//#endregion
|
|
43918
|
+
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
43919
|
+
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
43920
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43921
|
+
const callee = stripParenExpression(node.callee);
|
|
43922
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
43923
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
43924
|
+
const receiver = stripParenExpression(callee.object);
|
|
43925
|
+
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
43926
|
+
};
|
|
43927
|
+
//#endregion
|
|
44800
43928
|
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
44801
43929
|
const REACT_USE_STATE_OPTIONS = {
|
|
44802
43930
|
allowGlobalReactNamespace: true,
|
|
@@ -47186,103 +46314,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
47186
46314
|
"useVisibility",
|
|
47187
46315
|
"useWindowSize"
|
|
47188
46316
|
]);
|
|
47189
|
-
const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
|
|
47190
|
-
"useMatchMedia",
|
|
47191
|
-
"useMediaQuery",
|
|
47192
|
-
"useVisibility"
|
|
47193
|
-
]);
|
|
47194
|
-
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47195
|
-
const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
|
|
47196
|
-
const callee = stripParenExpression(rawCallee);
|
|
47197
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47198
|
-
const calleeRef = getRef(analysis, callee);
|
|
47199
|
-
if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
|
|
47200
|
-
const importBinding = getImportBindingForName(callee, callee.name);
|
|
47201
|
-
if (!importBinding || importBinding.isNamespace) return null;
|
|
47202
|
-
if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
|
|
47203
|
-
return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
|
|
47204
|
-
}
|
|
47205
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47206
|
-
const hookName = getStaticMemberPropertyName(callee);
|
|
47207
|
-
const namespaceIdentifier = stripParenExpression(callee.object);
|
|
47208
|
-
if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
47209
|
-
const namespaceRef = getRef(analysis, namespaceIdentifier);
|
|
47210
|
-
if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
|
|
47211
|
-
return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
|
|
47212
|
-
};
|
|
47213
|
-
const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
|
|
47214
|
-
let current = bindingIdentifier;
|
|
47215
|
-
let didCrossDestructuringBoundary = false;
|
|
47216
|
-
while (current !== bindingPattern) {
|
|
47217
|
-
const parent = current.parent;
|
|
47218
|
-
if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
|
|
47219
|
-
if (isNodeOfType(parent, "Property")) {
|
|
47220
|
-
if (parent.value !== current) return false;
|
|
47221
|
-
didCrossDestructuringBoundary = true;
|
|
47222
|
-
}
|
|
47223
|
-
if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
|
|
47224
|
-
current = parent;
|
|
47225
|
-
}
|
|
47226
|
-
return didCrossDestructuringBoundary;
|
|
47227
|
-
};
|
|
47228
|
-
const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
|
|
47229
|
-
const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
47230
|
-
if (visitedVariables.has(variable)) return false;
|
|
47231
|
-
visitedVariables.add(variable);
|
|
47232
|
-
return variable.references.some((candidateReference) => {
|
|
47233
|
-
if (candidateReference.init) return false;
|
|
47234
|
-
if (candidateReference.isWrite()) return true;
|
|
47235
|
-
let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
|
|
47236
|
-
while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
|
|
47237
|
-
const usageParent = usageRoot.parent;
|
|
47238
|
-
if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
|
|
47239
|
-
let usageAncestor = usageRoot.parent;
|
|
47240
|
-
while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
|
|
47241
|
-
if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
|
|
47242
|
-
const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
|
|
47243
|
-
return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
|
|
47244
|
-
}
|
|
47245
|
-
return false;
|
|
47246
|
-
});
|
|
47247
|
-
};
|
|
47248
|
-
const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
|
|
47249
|
-
return variables.length > 0 && variables.every((variable) => {
|
|
47250
|
-
const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
|
|
47251
|
-
return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
|
|
47252
|
-
});
|
|
47253
|
-
};
|
|
47254
|
-
const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
|
|
47255
|
-
const callee = stripParenExpression(rawCallee);
|
|
47256
|
-
const callExpression = callee.parent;
|
|
47257
|
-
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
|
|
47258
|
-
const initializer = findTransparentExpressionRoot(callExpression);
|
|
47259
|
-
const declarator = initializer.parent;
|
|
47260
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47261
|
-
const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
|
|
47262
|
-
if (relatedVariables.length === 0) return false;
|
|
47263
|
-
return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
|
|
47264
|
-
};
|
|
47265
|
-
const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
|
|
47266
|
-
const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
|
|
47267
|
-
return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
|
|
47268
|
-
};
|
|
47269
|
-
const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
47270
|
-
const declarator = def.node;
|
|
47271
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47272
|
-
const initializer = stripParenExpression(declarator.init);
|
|
47273
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
47274
|
-
const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
|
|
47275
|
-
if (!hookName) return false;
|
|
47276
|
-
if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47277
|
-
const bindingIdentifier = def.name;
|
|
47278
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47279
|
-
}));
|
|
47280
|
-
const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
|
|
47281
|
-
const unwrappedArgument = stripParenExpression(argument);
|
|
47282
|
-
if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
|
|
47283
|
-
const argumentRef = getRef(analysis, unwrappedArgument);
|
|
47284
|
-
return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
|
|
47285
|
-
};
|
|
47286
46317
|
const isCallbackPropReference = (analysis, ref) => {
|
|
47287
46318
|
if (!isProp(analysis, ref)) return false;
|
|
47288
46319
|
const identifier = ref.identifier;
|
|
@@ -47315,9 +46346,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
47315
46346
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
47316
46347
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
47317
46348
|
};
|
|
47318
|
-
const getLocalHookExternalStateProof = (analysis, ref
|
|
46349
|
+
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47319
46350
|
let hookFunction = resolveToFunction(ref);
|
|
47320
|
-
let didResolveThroughResultBinding = false;
|
|
47321
46351
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
47322
46352
|
const definitionNode = definition.node;
|
|
47323
46353
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -47328,27 +46358,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
|
47328
46358
|
const calleeReference = getRef(analysis, callee);
|
|
47329
46359
|
if (!calleeReference) continue;
|
|
47330
46360
|
hookFunction = resolveToFunction(calleeReference);
|
|
47331
|
-
if (hookFunction)
|
|
47332
|
-
didResolveThroughResultBinding = true;
|
|
47333
|
-
break;
|
|
47334
|
-
}
|
|
46361
|
+
if (hookFunction) break;
|
|
47335
46362
|
}
|
|
47336
46363
|
if (!hookFunction) return null;
|
|
47337
|
-
|
|
47338
|
-
if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
|
|
47339
|
-
const returnStatements = collectFunctionReturnStatements(hookFunction);
|
|
47340
|
-
if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
|
|
47341
|
-
const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46364
|
+
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
47342
46365
|
if (returnedReferences.length === 0) return null;
|
|
47343
46366
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
47344
46367
|
};
|
|
47345
|
-
const
|
|
47346
|
-
const
|
|
47347
|
-
if (!isNodeOfType(
|
|
47348
|
-
const
|
|
47349
|
-
if (
|
|
47350
|
-
|
|
46368
|
+
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
46369
|
+
const identifier = ref.identifier;
|
|
46370
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46371
|
+
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
46372
|
+
if (localHookProof !== null) return localHookProof;
|
|
46373
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46374
|
+
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46375
|
+
const node = def.node;
|
|
46376
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46377
|
+
const initializer = stripParenExpression(node.init);
|
|
46378
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46379
|
+
const callee = stripParenExpression(initializer.callee);
|
|
46380
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
46381
|
+
}));
|
|
47351
46382
|
};
|
|
46383
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47352
46384
|
const isCalleePosition = (identifier) => {
|
|
47353
46385
|
const parent = identifier.parent;
|
|
47354
46386
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -47409,35 +46441,25 @@ const noPassDataToParent = defineRule({
|
|
|
47409
46441
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
47410
46442
|
}
|
|
47411
46443
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47412
|
-
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
47413
46444
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
47414
46445
|
if (isNodeOfType(argument, "Identifier")) {
|
|
47415
46446
|
const argumentRef = getRef(analysis, argument);
|
|
47416
46447
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
47417
46448
|
}
|
|
47418
46449
|
return getDownstreamRefs(analysis, argument);
|
|
47419
|
-
}).flatMap((argumentRef) =>
|
|
47420
|
-
|
|
47421
|
-
const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
|
|
47422
|
-
return upstreamRefs.filter((upstreamRef) => {
|
|
47423
|
-
if (!isLeafRef(upstreamRef)) return false;
|
|
47424
|
-
return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
|
|
47425
|
-
});
|
|
47426
|
-
});
|
|
47427
|
-
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
|
|
47428
|
-
const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
|
|
47429
|
-
argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
|
|
47430
|
-
}
|
|
46450
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
46451
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
47431
46452
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
47432
|
-
|
|
47433
|
-
if (
|
|
46453
|
+
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46454
|
+
if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
|
|
47434
46455
|
if (isProp(analysis, argRef)) return false;
|
|
47435
|
-
if (isUseRefIdentifier(
|
|
46456
|
+
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
47436
46457
|
if (isRefCurrent(argRef)) return false;
|
|
47437
46458
|
if (isConstant(argRef)) return false;
|
|
47438
46459
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
47439
46460
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
47440
46461
|
if (resolveToFunction(argRef)) return false;
|
|
46462
|
+
const argIdentifier = argRef.identifier;
|
|
47441
46463
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
47442
46464
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
47443
46465
|
return true;
|
|
@@ -56428,6 +55450,17 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
56428
55450
|
}
|
|
56429
55451
|
});
|
|
56430
55452
|
//#endregion
|
|
55453
|
+
//#region src/plugin/utils/get-require-call-source.ts
|
|
55454
|
+
const getRequireCallSource = (expression) => {
|
|
55455
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
55456
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
55457
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
55458
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
55459
|
+
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
55460
|
+
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55461
|
+
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55462
|
+
};
|
|
55463
|
+
//#endregion
|
|
56431
55464
|
//#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
|
|
56432
55465
|
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
56433
55466
|
const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
|
|
@@ -59754,7 +58787,7 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
59754
58787
|
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);
|
|
59755
58788
|
//#endregion
|
|
59756
58789
|
//#region src/plugin/rules/security-scan/repository-secret-file.ts
|
|
59757
|
-
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(
|
|
58790
|
+
const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
|
|
59758
58791
|
const repositorySecretFile = defineRule({
|
|
59759
58792
|
id: "repository-secret-file",
|
|
59760
58793
|
title: "Secret file checked into repository",
|
|
@@ -61135,717 +60168,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
61135
60168
|
return found;
|
|
61136
60169
|
};
|
|
61137
60170
|
//#endregion
|
|
61138
|
-
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
61139
|
-
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
61140
|
-
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
61141
|
-
const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61142
|
-
"assign",
|
|
61143
|
-
"defineProperties",
|
|
61144
|
-
"defineProperty",
|
|
61145
|
-
"setPrototypeOf"
|
|
61146
|
-
]);
|
|
61147
|
-
const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61148
|
-
"defineProperty",
|
|
61149
|
-
"deleteProperty",
|
|
61150
|
-
"set",
|
|
61151
|
-
"setPrototypeOf"
|
|
61152
|
-
]);
|
|
61153
|
-
const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
|
|
61154
|
-
const containsGlobalLocationSnapshotRead = (node, scopes) => {
|
|
61155
|
-
let didFindLocationSnapshotRead = false;
|
|
61156
|
-
walkAst(node, (child) => {
|
|
61157
|
-
if (didFindLocationSnapshotRead) return false;
|
|
61158
|
-
if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61159
|
-
if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
|
|
61160
|
-
didFindLocationSnapshotRead = true;
|
|
61161
|
-
return false;
|
|
61162
|
-
});
|
|
61163
|
-
return didFindLocationSnapshotRead;
|
|
61164
|
-
};
|
|
61165
|
-
const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
|
|
61166
|
-
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
61167
|
-
if (isFunctionLike$1(localFunction)) return localFunction;
|
|
61168
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
61169
|
-
let callbackSource = unwrappedExpression;
|
|
61170
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
61171
|
-
const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
|
|
61172
|
-
if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
|
|
61173
|
-
callbackSource = callbackSymbol.initializer;
|
|
61174
|
-
}
|
|
61175
|
-
const unwrappedCallbackSource = stripParenExpression(callbackSource);
|
|
61176
|
-
if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
|
|
61177
|
-
const callback = unwrappedCallbackSource.arguments?.[0];
|
|
61178
|
-
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
61179
|
-
return resolveExactLocalFunction(stripParenExpression(callback), scopes);
|
|
61180
|
-
};
|
|
61181
|
-
const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61182
|
-
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
61183
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
61184
|
-
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;
|
|
61185
|
-
const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
|
|
61186
|
-
const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
|
|
61187
|
-
if (!initializer) return false;
|
|
61188
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61189
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
61190
|
-
if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
|
|
61191
|
-
let didFindAliasedLocationSnapshotRead = false;
|
|
61192
|
-
walkAst(initializer, (child) => {
|
|
61193
|
-
if (didFindAliasedLocationSnapshotRead) return false;
|
|
61194
|
-
if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61195
|
-
if (!isNodeOfType(child, "Identifier")) return;
|
|
61196
|
-
if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
|
|
61197
|
-
didFindAliasedLocationSnapshotRead = true;
|
|
61198
|
-
return false;
|
|
61199
|
-
}
|
|
61200
|
-
});
|
|
61201
|
-
return didFindAliasedLocationSnapshotRead;
|
|
61202
|
-
};
|
|
61203
|
-
const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
|
|
61204
|
-
if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
|
|
61205
|
-
for (const statement of componentBody.body ?? []) {
|
|
61206
|
-
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
|
|
61207
|
-
if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
|
|
61208
|
-
continue;
|
|
61209
|
-
}
|
|
61210
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
61211
|
-
for (const declarator of statement.declarations ?? []) {
|
|
61212
|
-
if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
61213
|
-
if (!directRenderNames.has(declarator.id.name)) continue;
|
|
61214
|
-
if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
|
|
61215
|
-
}
|
|
61216
|
-
}
|
|
61217
|
-
return false;
|
|
61218
|
-
};
|
|
61219
|
-
const isGlobalHistoryLocationMutation = (node, scopes) => {
|
|
61220
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
61221
|
-
const callee = stripParenExpression(node.callee);
|
|
61222
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61223
|
-
const methodName = getStaticPropertyName(callee);
|
|
61224
|
-
return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
|
|
61225
|
-
};
|
|
61226
|
-
const getStaticLocationChangeEvent = (node) => {
|
|
61227
|
-
if (!node) return null;
|
|
61228
|
-
const eventNameNode = stripParenExpression(node);
|
|
61229
|
-
if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
|
|
61230
|
-
return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
|
|
61231
|
-
};
|
|
61232
|
-
const addToSetIndex = (index, key, value) => {
|
|
61233
|
-
const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
61234
|
-
indexedValues.add(value);
|
|
61235
|
-
index.set(key, indexedValues);
|
|
61236
|
-
};
|
|
61237
|
-
const getSynchronousInvocationExpression = (functionNode, scopes) => {
|
|
61238
|
-
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61239
|
-
if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
|
|
61240
|
-
const parent = functionExpressionRoot.parent;
|
|
61241
|
-
return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
|
|
61242
|
-
};
|
|
61243
|
-
const getLocationListenerOperation = (callExpression, scopes) => {
|
|
61244
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61245
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
61246
|
-
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
61247
|
-
if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
|
|
61248
|
-
if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
|
|
61249
|
-
const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
|
|
61250
|
-
if (!eventName) return null;
|
|
61251
|
-
const listenerExpression = callExpression.arguments?.[1];
|
|
61252
|
-
if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
|
|
61253
|
-
const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
|
|
61254
|
-
if (!isFunctionLike$1(listenerFunction)) return null;
|
|
61255
|
-
const captureArgument = callExpression.arguments?.[2];
|
|
61256
|
-
const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
|
|
61257
|
-
allowComputedString: true,
|
|
61258
|
-
allowIndeterminateEntries: true
|
|
61259
|
-
});
|
|
61260
|
-
return {
|
|
61261
|
-
operation: methodName === "addEventListener" ? "add" : "remove",
|
|
61262
|
-
registration: {
|
|
61263
|
-
callExpression,
|
|
61264
|
-
listenerFunction,
|
|
61265
|
-
capture,
|
|
61266
|
-
eventName
|
|
61267
|
-
}
|
|
61268
|
-
};
|
|
61269
|
-
};
|
|
61270
|
-
const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
|
|
61271
|
-
const index = {
|
|
61272
|
-
componentFunction,
|
|
61273
|
-
context,
|
|
61274
|
-
effectCallbacks: /* @__PURE__ */ new Set(),
|
|
61275
|
-
expressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61276
|
-
historyMutationsByOwner: /* @__PURE__ */ new Map(),
|
|
61277
|
-
awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61278
|
-
callSitesByFunction: /* @__PURE__ */ new Map(),
|
|
61279
|
-
calledFunctionByExpression: /* @__PURE__ */ new Map(),
|
|
61280
|
-
synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
|
|
61281
|
-
synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
|
|
61282
|
-
callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
|
|
61283
|
-
identifierCalls: /* @__PURE__ */ new Set(),
|
|
61284
|
-
listenerRegistrations: [],
|
|
61285
|
-
listenerRemovals: [],
|
|
61286
|
-
mountedListenerFunctions: /* @__PURE__ */ new Set(),
|
|
61287
|
-
mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
|
|
61288
|
-
synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
|
|
61289
|
-
};
|
|
61290
|
-
walkAst(componentBody, (child) => {
|
|
61291
|
-
if (isFunctionLike$1(child)) {
|
|
61292
|
-
const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
|
|
61293
|
-
if (invocationExpression) {
|
|
61294
|
-
addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
|
|
61295
|
-
addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
|
|
61296
|
-
}
|
|
61297
|
-
return;
|
|
61298
|
-
}
|
|
61299
|
-
const owner = context.cfg.enclosingFunction(child);
|
|
61300
|
-
if (!owner) return;
|
|
61301
|
-
if (isNodeOfType(child, "AwaitExpression")) {
|
|
61302
|
-
addToSetIndex(index.awaitExpressionsByOwner, owner, child);
|
|
61303
|
-
return;
|
|
61304
|
-
}
|
|
61305
|
-
if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
|
|
61306
|
-
addToSetIndex(index.expressionsByOwner, owner, child);
|
|
61307
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
61308
|
-
if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
|
|
61309
|
-
if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
|
|
61310
|
-
const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
|
|
61311
|
-
if (isFunctionLike$1(calledFunction)) {
|
|
61312
|
-
addToSetIndex(index.callSitesByFunction, calledFunction, child);
|
|
61313
|
-
index.calledFunctionByExpression.set(child, calledFunction);
|
|
61314
|
-
}
|
|
61315
|
-
if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
61316
|
-
allowGlobalReactNamespace: true,
|
|
61317
|
-
allowUnboundBareCalls: true,
|
|
61318
|
-
resolveNamedAliases: true
|
|
61319
|
-
})) {
|
|
61320
|
-
const effectCallback = getEffectCallback(child, context.scopes);
|
|
61321
|
-
if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
|
|
61322
|
-
}
|
|
61323
|
-
const listenerOperation = getLocationListenerOperation(child, context.scopes);
|
|
61324
|
-
if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
|
|
61325
|
-
else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
|
|
61326
|
-
}
|
|
61327
|
-
});
|
|
61328
|
-
return index;
|
|
61329
|
-
};
|
|
61330
|
-
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61331
|
-
let current = descendant;
|
|
61332
|
-
while (current && current !== ancestor) {
|
|
61333
|
-
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61334
|
-
current = current.parent;
|
|
61335
|
-
}
|
|
61336
|
-
return current === ancestor;
|
|
61337
|
-
};
|
|
61338
|
-
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
61339
|
-
const firstBranches = /* @__PURE__ */ new Map();
|
|
61340
|
-
let current = firstNode;
|
|
61341
|
-
while (current?.parent) {
|
|
61342
|
-
const parent = current.parent;
|
|
61343
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
|
|
61344
|
-
if (current !== firstNode && isFunctionLike$1(current)) break;
|
|
61345
|
-
current = parent;
|
|
61346
|
-
}
|
|
61347
|
-
current = secondNode;
|
|
61348
|
-
while (current?.parent) {
|
|
61349
|
-
const parent = current.parent;
|
|
61350
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
|
|
61351
|
-
const firstBranch = firstBranches.get(parent);
|
|
61352
|
-
if (firstBranch && firstBranch !== current) return true;
|
|
61353
|
-
}
|
|
61354
|
-
if (current !== secondNode && isFunctionLike$1(current)) break;
|
|
61355
|
-
current = parent;
|
|
61356
|
-
}
|
|
61357
|
-
return false;
|
|
61358
|
-
};
|
|
61359
|
-
const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
|
|
61360
|
-
const constraintsBySymbolId = /* @__PURE__ */ new Map();
|
|
61361
|
-
const recordConstraint = (test, requiredTestValue) => {
|
|
61362
|
-
let expression = stripParenExpression(test);
|
|
61363
|
-
let requiredValue = requiredTestValue;
|
|
61364
|
-
while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
61365
|
-
requiredValue = !requiredValue;
|
|
61366
|
-
expression = stripParenExpression(expression.argument);
|
|
61367
|
-
}
|
|
61368
|
-
if (!isNodeOfType(expression, "Identifier")) return;
|
|
61369
|
-
const symbol = scopes.symbolFor(expression);
|
|
61370
|
-
if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
61371
|
-
const existingConstraint = constraintsBySymbolId.get(symbol.id);
|
|
61372
|
-
constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
|
|
61373
|
-
};
|
|
61374
|
-
let current = node;
|
|
61375
|
-
while (current?.parent && current !== functionBoundary) {
|
|
61376
|
-
const parent = current.parent;
|
|
61377
|
-
if (isNodeOfType(parent, "IfStatement")) {
|
|
61378
|
-
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61379
|
-
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61380
|
-
} else if (isNodeOfType(parent, "ConditionalExpression")) {
|
|
61381
|
-
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61382
|
-
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61383
|
-
} else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
|
|
61384
|
-
if (parent.operator === "&&") recordConstraint(parent.left, true);
|
|
61385
|
-
if (parent.operator === "||") recordConstraint(parent.left, false);
|
|
61386
|
-
}
|
|
61387
|
-
if (current !== node && isFunctionLike$1(current)) break;
|
|
61388
|
-
current = parent;
|
|
61389
|
-
}
|
|
61390
|
-
return constraintsBySymbolId;
|
|
61391
|
-
};
|
|
61392
|
-
const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
|
|
61393
|
-
const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
|
|
61394
|
-
const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
|
|
61395
|
-
for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
|
|
61396
|
-
for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
|
|
61397
|
-
for (const [symbolId, firstRequiredValue] of firstConstraints) {
|
|
61398
|
-
const secondRequiredValue = secondConstraints.get(symbolId);
|
|
61399
|
-
if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
|
|
61400
|
-
}
|
|
61401
|
-
return false;
|
|
61402
|
-
};
|
|
61403
|
-
const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
|
|
61404
|
-
let current = node;
|
|
61405
|
-
while (current && current !== functionBoundary) {
|
|
61406
|
-
if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
|
|
61407
|
-
current = current.parent;
|
|
61408
|
-
}
|
|
61409
|
-
return false;
|
|
61410
|
-
};
|
|
61411
|
-
const getTransparentExpressionBindingIdentifier = (expression) => {
|
|
61412
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
61413
|
-
const parent = expressionRoot.parent;
|
|
61414
|
-
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
61415
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
|
|
61416
|
-
return null;
|
|
61417
|
-
};
|
|
61418
|
-
const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
|
|
61419
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
61420
|
-
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61421
|
-
const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
|
|
61422
|
-
if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
|
|
61423
|
-
const callbackCall = functionExpressionRoot.parent;
|
|
61424
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61425
|
-
return getTransparentExpressionBindingIdentifier(callbackCall);
|
|
61426
|
-
};
|
|
61427
|
-
const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
|
|
61428
|
-
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61429
|
-
if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
|
|
61430
|
-
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61431
|
-
return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
|
|
61432
|
-
};
|
|
61433
|
-
const canNodeReachNode = (sourceNode, targetNode, index) => {
|
|
61434
|
-
const { context } = index;
|
|
61435
|
-
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
61436
|
-
if (!isNodeReachableWithinFunction(targetNode, context)) return false;
|
|
61437
|
-
const sourceOwner = context.cfg.enclosingFunction(sourceNode);
|
|
61438
|
-
const targetOwner = context.cfg.enclosingFunction(targetNode);
|
|
61439
|
-
if (!sourceOwner || sourceOwner !== targetOwner) return false;
|
|
61440
|
-
if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
|
|
61441
|
-
if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
|
|
61442
|
-
if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
|
|
61443
|
-
if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
|
|
61444
|
-
const functionCfg = context.cfg.cfgFor(sourceOwner);
|
|
61445
|
-
const sourceBlock = functionCfg?.blockOf(sourceNode);
|
|
61446
|
-
const targetBlock = functionCfg?.blockOf(targetNode);
|
|
61447
|
-
if (!functionCfg || !sourceBlock || !targetBlock) return false;
|
|
61448
|
-
if (sourceBlock === targetBlock) {
|
|
61449
|
-
const sourceStart = getRangeStart(sourceNode);
|
|
61450
|
-
const targetStart = getRangeStart(targetNode);
|
|
61451
|
-
return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
|
|
61452
|
-
}
|
|
61453
|
-
const visitedBlocks = new Set([sourceBlock]);
|
|
61454
|
-
const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
|
|
61455
|
-
while (pendingBlocks.length > 0) {
|
|
61456
|
-
const block = pendingBlocks.pop();
|
|
61457
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61458
|
-
if (block === targetBlock) return true;
|
|
61459
|
-
visitedBlocks.add(block);
|
|
61460
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61461
|
-
}
|
|
61462
|
-
return false;
|
|
61463
|
-
};
|
|
61464
|
-
const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
|
|
61465
|
-
let currentNode = functionNode;
|
|
61466
|
-
while (currentNode) {
|
|
61467
|
-
if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
|
|
61468
|
-
const openingElement = currentNode.parent;
|
|
61469
|
-
return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
|
|
61470
|
-
}
|
|
61471
|
-
if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
|
|
61472
|
-
currentNode = currentNode.parent;
|
|
61473
|
-
}
|
|
61474
|
-
return null;
|
|
61475
|
-
};
|
|
61476
|
-
const isProvenNonEscapingVoidRead = (identifier) => {
|
|
61477
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
61478
|
-
return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
|
|
61479
|
-
};
|
|
61480
|
-
const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
|
|
61481
|
-
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61482
|
-
const parent = referenceRoot.parent;
|
|
61483
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
|
|
61484
|
-
const aliasSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61485
|
-
const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
|
|
61486
|
-
const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
|
|
61487
|
-
return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
|
|
61488
|
-
}
|
|
61489
|
-
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61490
|
-
const callbackResultRoot = findTransparentExpressionRoot(parent);
|
|
61491
|
-
const resultParent = callbackResultRoot.parent;
|
|
61492
|
-
if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
|
|
61493
|
-
const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
|
|
61494
|
-
return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
|
|
61495
|
-
};
|
|
61496
|
-
const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
|
|
61497
|
-
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61498
|
-
const parent = referenceRoot.parent;
|
|
61499
|
-
if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
|
|
61500
|
-
const memberRoot = findTransparentExpressionRoot(parent);
|
|
61501
|
-
const callExpression = memberRoot.parent;
|
|
61502
|
-
const methodName = getStaticPropertyName(parent);
|
|
61503
|
-
if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
|
|
61504
|
-
}
|
|
61505
|
-
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
|
|
61506
|
-
const callee = stripParenExpression(parent.callee);
|
|
61507
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61508
|
-
const methodName = getStaticPropertyName(callee);
|
|
61509
|
-
if (methodName === null) return false;
|
|
61510
|
-
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);
|
|
61511
|
-
};
|
|
61512
|
-
const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61513
|
-
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61514
|
-
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
|
|
61515
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61516
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
61517
|
-
const escapeAnchors = [];
|
|
61518
|
-
for (const reference of symbol.references) {
|
|
61519
|
-
if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
|
|
61520
|
-
const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
|
|
61521
|
-
if (!aliasSymbol) {
|
|
61522
|
-
escapeAnchors.push(reference.identifier);
|
|
61523
|
-
continue;
|
|
61524
|
-
}
|
|
61525
|
-
const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
|
|
61526
|
-
if (!aliasEscapeAnchors) return null;
|
|
61527
|
-
escapeAnchors.push(...aliasEscapeAnchors);
|
|
61528
|
-
}
|
|
61529
|
-
return escapeAnchors.length > 0 ? escapeAnchors : null;
|
|
61530
|
-
};
|
|
61531
|
-
const findReadonlyJsxAggregateRoot = (jsxElement) => {
|
|
61532
|
-
let aggregateRoot = findTransparentExpressionRoot(jsxElement);
|
|
61533
|
-
while (aggregateRoot.parent) {
|
|
61534
|
-
const parent = aggregateRoot.parent;
|
|
61535
|
-
if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
|
|
61536
|
-
aggregateRoot = parent;
|
|
61537
|
-
continue;
|
|
61538
|
-
}
|
|
61539
|
-
if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
|
|
61540
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61541
|
-
continue;
|
|
61542
|
-
}
|
|
61543
|
-
if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
|
|
61544
|
-
aggregateRoot = findTransparentExpressionRoot(parent.parent);
|
|
61545
|
-
continue;
|
|
61546
|
-
}
|
|
61547
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
|
|
61548
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61549
|
-
continue;
|
|
61550
|
-
}
|
|
61551
|
-
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
|
|
61552
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61553
|
-
continue;
|
|
61554
|
-
}
|
|
61555
|
-
if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
|
|
61556
|
-
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61557
|
-
continue;
|
|
61558
|
-
}
|
|
61559
|
-
break;
|
|
61560
|
-
}
|
|
61561
|
-
return aggregateRoot;
|
|
61562
|
-
};
|
|
61563
|
-
const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
|
|
61564
|
-
const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
|
|
61565
|
-
const parent = jsxValueRoot.parent;
|
|
61566
|
-
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
|
|
61567
|
-
if (!isNodeOfType(parent.id, "Identifier")) return null;
|
|
61568
|
-
const bindingSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61569
|
-
if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
|
|
61570
|
-
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
|
|
61571
|
-
};
|
|
61572
|
-
const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
|
|
61573
|
-
if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
|
|
61574
|
-
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61575
|
-
if (!bindingIdentifier) {
|
|
61576
|
-
const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
|
|
61577
|
-
return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
|
|
61578
|
-
}
|
|
61579
|
-
return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
|
|
61580
|
-
};
|
|
61581
|
-
const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
|
|
61582
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
61583
|
-
if (!bindingIdentifier) return null;
|
|
61584
|
-
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61585
|
-
if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
|
|
61586
|
-
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
|
|
61587
|
-
};
|
|
61588
|
-
const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
61589
|
-
if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
|
|
61590
|
-
const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
|
|
61591
|
-
const executionOwner = index.context.cfg.enclosingFunction(executionNode);
|
|
61592
|
-
if (!aliasOwner || !executionOwner) return false;
|
|
61593
|
-
if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
|
|
61594
|
-
if (visitedFunctionNodes.has(executionOwner)) return false;
|
|
61595
|
-
const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
|
|
61596
|
-
nextVisitedFunctionNodes.add(executionOwner);
|
|
61597
|
-
if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
|
|
61598
|
-
const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
|
|
61599
|
-
if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
|
|
61600
|
-
const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
|
|
61601
|
-
if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
|
|
61602
|
-
const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
|
|
61603
|
-
return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
|
|
61604
|
-
};
|
|
61605
|
-
const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
|
|
61606
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61607
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
61608
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
61609
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
61610
|
-
let symbol = index.context.scopes.symbolFor(callee);
|
|
61611
|
-
while (symbol) {
|
|
61612
|
-
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61613
|
-
visitedSymbolIds.add(symbol.id);
|
|
61614
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
61615
|
-
if (!initializer) return symbol.id;
|
|
61616
|
-
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
|
|
61617
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
61618
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
|
|
61619
|
-
const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
|
|
61620
|
-
if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
|
|
61621
|
-
symbol = initializerSymbol;
|
|
61622
|
-
}
|
|
61623
|
-
return null;
|
|
61624
|
-
};
|
|
61625
|
-
const collectExactReadonlyCalleeCalls = (index) => {
|
|
61626
|
-
for (const callExpression of index.identifierCalls) {
|
|
61627
|
-
const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
|
|
61628
|
-
if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
|
|
61629
|
-
}
|
|
61630
|
-
};
|
|
61631
|
-
const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
61632
|
-
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61633
|
-
const sourceBlock = functionCfg?.blockOf(node);
|
|
61634
|
-
if (!functionCfg || !sourceBlock) return false;
|
|
61635
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61636
|
-
const pendingBlocks = [sourceBlock];
|
|
61637
|
-
while (pendingBlocks.length > 0) {
|
|
61638
|
-
const block = pendingBlocks.pop();
|
|
61639
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61640
|
-
visitedBlocks.add(block);
|
|
61641
|
-
for (const edge of block.successors) {
|
|
61642
|
-
if (edge.kind === "throw") continue;
|
|
61643
|
-
if (edge.to === functionCfg.exit) return true;
|
|
61644
|
-
pendingBlocks.push(edge.to);
|
|
61645
|
-
}
|
|
61646
|
-
}
|
|
61647
|
-
return false;
|
|
61648
|
-
};
|
|
61649
|
-
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
61650
|
-
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
61651
|
-
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61652
|
-
const targetBlock = functionCfg?.blockOf(node);
|
|
61653
|
-
if (!functionCfg || !targetBlock) return false;
|
|
61654
|
-
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
61655
|
-
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
61656
|
-
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
61657
|
-
if (!awaitBlock) continue;
|
|
61658
|
-
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
61659
|
-
blockAwaits.push(awaitExpression);
|
|
61660
|
-
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
61661
|
-
}
|
|
61662
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61663
|
-
const pendingBlocks = [functionCfg.entry];
|
|
61664
|
-
const targetStart = getRangeStart(node);
|
|
61665
|
-
while (pendingBlocks.length > 0) {
|
|
61666
|
-
const block = pendingBlocks.pop();
|
|
61667
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61668
|
-
visitedBlocks.add(block);
|
|
61669
|
-
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
61670
|
-
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
61671
|
-
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
61672
|
-
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
61673
|
-
const awaitStart = getRangeStart(awaitExpression);
|
|
61674
|
-
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
61675
|
-
});
|
|
61676
|
-
if (blockAwaits.length > 0) continue;
|
|
61677
|
-
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61678
|
-
}
|
|
61679
|
-
return false;
|
|
61680
|
-
};
|
|
61681
|
-
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);
|
|
61682
|
-
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
61683
|
-
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
61684
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
61685
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61686
|
-
if (visitingFunctions.has(functionNode)) {
|
|
61687
|
-
let didReachCycleEntry = false;
|
|
61688
|
-
for (const visitingFunction of visitingFunctions) {
|
|
61689
|
-
if (visitingFunction === functionNode) didReachCycleEntry = true;
|
|
61690
|
-
if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
|
|
61691
|
-
}
|
|
61692
|
-
return false;
|
|
61693
|
-
}
|
|
61694
|
-
visitingFunctions.add(functionNode);
|
|
61695
|
-
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
61696
|
-
visitingFunctions.delete(functionNode);
|
|
61697
|
-
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
61698
|
-
return doesMutateSynchronously;
|
|
61699
|
-
};
|
|
61700
|
-
const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
|
|
61701
|
-
const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
|
|
61702
|
-
if (cachedExecutions) return cachedExecutions;
|
|
61703
|
-
const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
|
|
61704
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61705
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61706
|
-
if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61707
|
-
for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61708
|
-
}
|
|
61709
|
-
if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
|
|
61710
|
-
return mutationExecutions;
|
|
61711
|
-
};
|
|
61712
|
-
const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
|
|
61713
|
-
const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
|
|
61714
|
-
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61715
|
-
if (visitingFunctions.has(functionNode)) return false;
|
|
61716
|
-
const nextVisitingFunctions = new Set(visitingFunctions);
|
|
61717
|
-
nextVisitingFunctions.add(functionNode);
|
|
61718
|
-
const removalExecutions = [];
|
|
61719
|
-
for (const removal of index.listenerRemovals) {
|
|
61720
|
-
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
61721
|
-
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
61722
|
-
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
61723
|
-
}
|
|
61724
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61725
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61726
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
61727
|
-
}
|
|
61728
|
-
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
61729
|
-
};
|
|
61730
|
-
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
61731
|
-
const removalExecutions = /* @__PURE__ */ new Set();
|
|
61732
|
-
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);
|
|
61733
|
-
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61734
|
-
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61735
|
-
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
61736
|
-
}
|
|
61737
|
-
return removalExecutions;
|
|
61738
|
-
};
|
|
61739
|
-
const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
|
|
61740
|
-
const expressionBoundaries = [];
|
|
61741
|
-
let currentChild = node;
|
|
61742
|
-
let currentParent = currentChild.parent ?? null;
|
|
61743
|
-
while (currentParent && currentParent !== owner) {
|
|
61744
|
-
if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
|
|
61745
|
-
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
61746
|
-
if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
|
|
61747
|
-
}
|
|
61748
|
-
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
|
|
61749
|
-
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
61750
|
-
if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
|
|
61751
|
-
}
|
|
61752
|
-
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);
|
|
61753
|
-
currentChild = currentParent;
|
|
61754
|
-
currentParent = currentChild.parent ?? null;
|
|
61755
|
-
}
|
|
61756
|
-
return expressionBoundaries;
|
|
61757
|
-
};
|
|
61758
|
-
const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
|
|
61759
|
-
if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
|
|
61760
|
-
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61761
|
-
if (!owner) return false;
|
|
61762
|
-
const functionCfg = index.context.cfg.cfgFor(owner);
|
|
61763
|
-
const sourceBlock = functionCfg?.blockOf(executionNode);
|
|
61764
|
-
if (!functionCfg || !sourceBlock) return false;
|
|
61765
|
-
const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
|
|
61766
|
-
const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
|
|
61767
|
-
for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
|
|
61768
|
-
const removalBlock = functionCfg.blockOf(removalExecution);
|
|
61769
|
-
if (!removalBlock) continue;
|
|
61770
|
-
const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
|
|
61771
|
-
blockRemovals.push(removalExecution);
|
|
61772
|
-
matchingRemovalsByBlock.set(removalBlock, blockRemovals);
|
|
61773
|
-
}
|
|
61774
|
-
const sourceStart = getRangeStart(executionNode);
|
|
61775
|
-
const hasRemovalAfterExecution = (block) => {
|
|
61776
|
-
const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
|
|
61777
|
-
if (block !== sourceBlock) return true;
|
|
61778
|
-
const removalStart = getRangeStart(removal);
|
|
61779
|
-
return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
|
|
61780
|
-
});
|
|
61781
|
-
if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
|
|
61782
|
-
if (block !== sourceBlock) return false;
|
|
61783
|
-
return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
|
|
61784
|
-
};
|
|
61785
|
-
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61786
|
-
const pendingBlocks = [sourceBlock];
|
|
61787
|
-
while (pendingBlocks.length > 0) {
|
|
61788
|
-
const block = pendingBlocks.pop();
|
|
61789
|
-
if (!block || visitedBlocks.has(block)) continue;
|
|
61790
|
-
visitedBlocks.add(block);
|
|
61791
|
-
if (hasRemovalAfterExecution(block)) continue;
|
|
61792
|
-
for (const edge of block.successors) {
|
|
61793
|
-
if (edge.kind === "throw") continue;
|
|
61794
|
-
if (edge.to === functionCfg.exit) return true;
|
|
61795
|
-
pendingBlocks.push(edge.to);
|
|
61796
|
-
}
|
|
61797
|
-
}
|
|
61798
|
-
return false;
|
|
61799
|
-
};
|
|
61800
|
-
const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61801
|
-
if (visitedExecutions.has(executionNode)) return false;
|
|
61802
|
-
if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
|
|
61803
|
-
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61804
|
-
if (!owner) return false;
|
|
61805
|
-
if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
|
|
61806
|
-
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61807
|
-
nextVisitedExecutions.add(executionNode);
|
|
61808
|
-
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
|
|
61809
|
-
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
|
|
61810
|
-
return false;
|
|
61811
|
-
};
|
|
61812
|
-
const collectMountedListenerFunctions = (index) => {
|
|
61813
|
-
for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
|
|
61814
|
-
};
|
|
61815
|
-
const setterArgumentMutatesLocation = (setterCall, index) => {
|
|
61816
|
-
if (!isNodeOfType(setterCall, "CallExpression")) return false;
|
|
61817
|
-
return (setterCall.arguments ?? []).some((argument) => {
|
|
61818
|
-
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
61819
|
-
const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
|
|
61820
|
-
return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
|
|
61821
|
-
});
|
|
61822
|
-
};
|
|
61823
|
-
const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
61824
|
-
if (visitedExecutions.has(executionAnchor)) return false;
|
|
61825
|
-
if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
|
|
61826
|
-
const owner = index.context.cfg.enclosingFunction(executionAnchor);
|
|
61827
|
-
if (!owner) return false;
|
|
61828
|
-
if (index.mountedListenerFunctions.has(owner)) return true;
|
|
61829
|
-
if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
|
|
61830
|
-
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
61831
|
-
nextVisitedExecutions.add(executionAnchor);
|
|
61832
|
-
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
|
|
61833
|
-
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
|
|
61834
|
-
return false;
|
|
61835
|
-
};
|
|
61836
|
-
const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
|
|
61837
|
-
const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
|
|
61838
|
-
if (!setterSymbol) return false;
|
|
61839
|
-
return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
|
|
61840
|
-
};
|
|
61841
|
-
const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
|
|
61842
|
-
if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
|
|
61843
|
-
const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
|
|
61844
|
-
collectMountedListenerFunctions(locationInvalidationIndex);
|
|
61845
|
-
collectExactReadonlyCalleeCalls(locationInvalidationIndex);
|
|
61846
|
-
return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
|
|
61847
|
-
};
|
|
61848
|
-
//#endregion
|
|
61849
60171
|
//#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
|
|
61850
60172
|
const isInsideConditionTest = (identifier, stopAt) => {
|
|
61851
60173
|
let current = identifier;
|
|
@@ -61967,8 +60289,7 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
61967
60289
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
61968
60290
|
const bindings = collectUseStateBindings(componentBody, context.scopes);
|
|
61969
60291
|
if (bindings.length === 0) return;
|
|
61970
|
-
|
|
61971
|
-
if (renderReachableExpressions.length === 0) return;
|
|
60292
|
+
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
61972
60293
|
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
|
|
61973
60294
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
61974
60295
|
const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
|
|
@@ -61982,22 +60303,12 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
61982
60303
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
61983
60304
|
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
|
|
61984
60305
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
61985
|
-
const componentFunction = context.cfg.enclosingFunction(componentBody);
|
|
61986
|
-
const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
|
|
61987
|
-
componentBody,
|
|
61988
|
-
componentFunction,
|
|
61989
|
-
context,
|
|
61990
|
-
directRenderNames,
|
|
61991
|
-
renderReachableExpressions
|
|
61992
|
-
}) : () => false;
|
|
61993
60306
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
61994
60307
|
walkAst(componentBody, (child) => {
|
|
61995
60308
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
61996
60309
|
});
|
|
61997
60310
|
for (const binding of bindings) {
|
|
61998
60311
|
if (renderReachableNames.has(binding.valueName)) continue;
|
|
61999
|
-
const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
|
|
62000
|
-
if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
|
|
62001
60312
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
62002
60313
|
const setterSuffix = binding.setterName.slice(3);
|
|
62003
60314
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|
|
@@ -64904,66 +63215,6 @@ const roleHasRequiredAriaProps = defineRule({
|
|
|
64904
63215
|
} })
|
|
64905
63216
|
});
|
|
64906
63217
|
//#endregion
|
|
64907
|
-
//#region src/plugin/constants/global-aria-properties.ts
|
|
64908
|
-
const GLOBAL_ARIA_PROPERTIES = new Set([
|
|
64909
|
-
"aria-atomic",
|
|
64910
|
-
"aria-braillelabel",
|
|
64911
|
-
"aria-brailleroledescription",
|
|
64912
|
-
"aria-busy",
|
|
64913
|
-
"aria-controls",
|
|
64914
|
-
"aria-current",
|
|
64915
|
-
"aria-describedby",
|
|
64916
|
-
"aria-description",
|
|
64917
|
-
"aria-details",
|
|
64918
|
-
"aria-disabled",
|
|
64919
|
-
"aria-dropeffect",
|
|
64920
|
-
"aria-errormessage",
|
|
64921
|
-
"aria-flowto",
|
|
64922
|
-
"aria-grabbed",
|
|
64923
|
-
"aria-haspopup",
|
|
64924
|
-
"aria-hidden",
|
|
64925
|
-
"aria-invalid",
|
|
64926
|
-
"aria-keyshortcuts",
|
|
64927
|
-
"aria-label",
|
|
64928
|
-
"aria-labelledby",
|
|
64929
|
-
"aria-live",
|
|
64930
|
-
"aria-owns",
|
|
64931
|
-
"aria-relevant",
|
|
64932
|
-
"aria-roledescription"
|
|
64933
|
-
]);
|
|
64934
|
-
//#endregion
|
|
64935
|
-
//#region src/plugin/constants/prohibited-aria-properties-by-role.ts
|
|
64936
|
-
const ACCESSIBLE_NAME_PROPERTIES = new Set([
|
|
64937
|
-
"aria-braillelabel",
|
|
64938
|
-
"aria-label",
|
|
64939
|
-
"aria-labelledby"
|
|
64940
|
-
]);
|
|
64941
|
-
const PROHIBITED_ARIA_PROPERTIES_BY_ROLE = {
|
|
64942
|
-
caption: ACCESSIBLE_NAME_PROPERTIES,
|
|
64943
|
-
code: ACCESSIBLE_NAME_PROPERTIES,
|
|
64944
|
-
definition: ACCESSIBLE_NAME_PROPERTIES,
|
|
64945
|
-
deletion: ACCESSIBLE_NAME_PROPERTIES,
|
|
64946
|
-
emphasis: ACCESSIBLE_NAME_PROPERTIES,
|
|
64947
|
-
generic: new Set([
|
|
64948
|
-
"aria-braillelabel",
|
|
64949
|
-
"aria-brailleroledescription",
|
|
64950
|
-
"aria-label",
|
|
64951
|
-
"aria-labelledby",
|
|
64952
|
-
"aria-roledescription"
|
|
64953
|
-
]),
|
|
64954
|
-
insertion: ACCESSIBLE_NAME_PROPERTIES,
|
|
64955
|
-
mark: ACCESSIBLE_NAME_PROPERTIES,
|
|
64956
|
-
none: ACCESSIBLE_NAME_PROPERTIES,
|
|
64957
|
-
paragraph: ACCESSIBLE_NAME_PROPERTIES,
|
|
64958
|
-
presentation: ACCESSIBLE_NAME_PROPERTIES,
|
|
64959
|
-
strong: ACCESSIBLE_NAME_PROPERTIES,
|
|
64960
|
-
subscript: ACCESSIBLE_NAME_PROPERTIES,
|
|
64961
|
-
superscript: ACCESSIBLE_NAME_PROPERTIES,
|
|
64962
|
-
term: ACCESSIBLE_NAME_PROPERTIES,
|
|
64963
|
-
time: ACCESSIBLE_NAME_PROPERTIES,
|
|
64964
|
-
tooltip: ACCESSIBLE_NAME_PROPERTIES
|
|
64965
|
-
};
|
|
64966
|
-
//#endregion
|
|
64967
63218
|
//#region src/plugin/constants/role-supports-aria-props.ts
|
|
64968
63219
|
const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
64969
63220
|
alert: new Set([
|
|
@@ -68064,12 +66315,6 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
|
|
|
68064
66315
|
])
|
|
68065
66316
|
};
|
|
68066
66317
|
//#endregion
|
|
68067
|
-
//#region src/plugin/utils/is-aria-property-supported-by-role.ts
|
|
68068
|
-
const isAriaPropertySupportedByRole = (role, property) => {
|
|
68069
|
-
if (PROHIBITED_ARIA_PROPERTIES_BY_ROLE[role]?.has(property)) return false;
|
|
68070
|
-
return ROLE_SUPPORTS_ARIA_PROPS[role]?.has(property) === true || GLOBAL_ARIA_PROPERTIES.has(property);
|
|
68071
|
-
};
|
|
68072
|
-
//#endregion
|
|
68073
66318
|
//#region src/plugin/rules/a11y/role-supports-aria-props.ts
|
|
68074
66319
|
const buildMessageDefault = (roles, propName) => {
|
|
68075
66320
|
return `Screen reader users get no help from \`${propName}\` because role ${roles.map((role) => `\`${role}\``).join(" / ")} ignores it, so remove it or change the role.`;
|
|
@@ -68106,10 +66351,16 @@ const roleSupportsAriaProps = defineRule({
|
|
|
68106
66351
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
68107
66352
|
const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType, context.scopes)].filter((role) => role !== null);
|
|
68108
66353
|
if (roleCandidates === null || roleCandidates.length === 0) return;
|
|
68109
|
-
|
|
66354
|
+
const supportedSets = [];
|
|
66355
|
+
for (const role of roleCandidates) {
|
|
66356
|
+
if (!VALID_ARIA_ROLES.has(role)) return;
|
|
66357
|
+
const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
|
|
66358
|
+
if (!supported) return;
|
|
66359
|
+
supportedSets.push(supported);
|
|
66360
|
+
}
|
|
68110
66361
|
const isImplicit = !roleAttribute;
|
|
68111
66362
|
for (const { attribute, propName } of ariaAttributes) {
|
|
68112
|
-
if (
|
|
66363
|
+
if (supportedSets.some((supported) => supported.has(propName))) continue;
|
|
68113
66364
|
context.report({
|
|
68114
66365
|
node: attribute,
|
|
68115
66366
|
message: isImplicit ? buildMessageImplicit(roleCandidates[0], propName, elementType) : buildMessageDefault(roleCandidates, propName)
|
|
@@ -73173,6 +71424,18 @@ const reactDoctorRules = [
|
|
|
73173
71424
|
requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
|
|
73174
71425
|
}
|
|
73175
71426
|
},
|
|
71427
|
+
{
|
|
71428
|
+
key: "react-doctor/jsx-no-target-blank",
|
|
71429
|
+
id: "jsx-no-target-blank",
|
|
71430
|
+
source: "react-doctor",
|
|
71431
|
+
originallyExternal: true,
|
|
71432
|
+
rule: {
|
|
71433
|
+
...jsxNoTargetBlank,
|
|
71434
|
+
framework: "global",
|
|
71435
|
+
category: "Security",
|
|
71436
|
+
requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
|
|
71437
|
+
}
|
|
71438
|
+
},
|
|
73176
71439
|
{
|
|
73177
71440
|
key: "react-doctor/jsx-no-undef",
|
|
73178
71441
|
id: "jsx-no-undef",
|
|
@@ -76879,7 +75142,7 @@ const EXTERNAL_RULES = [
|
|
|
76879
75142
|
{
|
|
76880
75143
|
key: "react-hooks-js/set-state-in-effect",
|
|
76881
75144
|
source: "react-compiler",
|
|
76882
|
-
severity: "
|
|
75145
|
+
severity: "error"
|
|
76883
75146
|
},
|
|
76884
75147
|
{
|
|
76885
75148
|
key: "react-hooks-js/globals",
|