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