@drskillissue/ganko 0.1.22 → 0.1.23

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.
@@ -7566,7 +7566,8 @@ var CONDITIONAL_MOUNT_TAGS = /* @__PURE__ */ new Set([
7566
7566
  ]);
7567
7567
  var messages16 = {
7568
7568
  loadingMismatch: "createResource '{{name}}' has no initialValue but uses manual loading checks ({{name}}.loading). Without initialValue, Suspense intercepts before your loading UI renders. Add initialValue to the options: createResource(fetcher, { initialValue: ... })",
7569
- conditionalSuspense: "createResource '{{name}}' has no initialValue and is rendered inside a conditional mount point ({{mountTag}}). This will trigger a distant Suspense boundary and unmount the entire subtree. Add initialValue to the options: createResource(fetcher, { initialValue: ... })"
7569
+ conditionalSuspense: "createResource '{{name}}' is rendered inside a conditional mount point ({{mountTag}}) with a distant Suspense boundary. When the fetcher's Promise is pending, the SuspenseContext increment fires and unmounts the entire subtree. initialValue does NOT prevent this \u2014 it only prevents the accessor from returning undefined.",
7570
+ missingErrorBoundary: "createResource '{{name}}' has no <ErrorBoundary> between its component and the nearest <Suspense>. When the fetcher throws (network error, 401/403/503, timeout), the error propagates to Suspense which absorbs it and stays in its fallback state permanently. Wrap the component in <ErrorBoundary fallback={...}> or catch errors inside the fetcher."
7570
7571
  };
7571
7572
  var options16 = {};
7572
7573
  function hasInitialValue(call) {
@@ -7609,35 +7610,92 @@ function hasLoadingRead(resourceVariable) {
7609
7610
  }
7610
7611
  return false;
7611
7612
  }
7612
- function findConditionalMountAncestor(graph, componentName) {
7613
+ function resolveFetcherFunction(graph, call) {
7614
+ const args = call.node.arguments;
7615
+ if (args.length === 0) return null;
7616
+ let fetcherNode;
7617
+ if (args.length === 1) {
7618
+ fetcherNode = args[0];
7619
+ } else if (args.length === 2) {
7620
+ const lastArg = args[1];
7621
+ fetcherNode = lastArg && lastArg.type === "ObjectExpression" ? args[0] : args[1];
7622
+ } else {
7623
+ fetcherNode = args[1];
7624
+ }
7625
+ if (!fetcherNode) return null;
7626
+ if (fetcherNode.type === "ArrowFunctionExpression" || fetcherNode.type === "FunctionExpression") {
7627
+ return graph.functionsByNode.get(fetcherNode) ?? null;
7628
+ }
7629
+ if (fetcherNode.type === "Identifier") {
7630
+ const fns = graph.functionsByName.get(fetcherNode.name);
7631
+ if (fns && fns.length > 0) {
7632
+ const fn = fns[0];
7633
+ if (fn) return fn;
7634
+ }
7635
+ }
7636
+ return null;
7637
+ }
7638
+ function fetcherCanThrow(graph, fn, visited) {
7639
+ if (visited.has(fn.id)) return false;
7640
+ visited.add(fn.id);
7641
+ if (fn.async && fn.awaitRanges.length > 0) return true;
7642
+ if (fn.hasThrowStatement) return true;
7643
+ const callSites = fn.callSites;
7644
+ for (let i = 0, len = callSites.length; i < len; i++) {
7645
+ const callSite = callSites[i];
7646
+ if (!callSite) continue;
7647
+ if (!callSite.resolvedTarget) return true;
7648
+ if (fetcherCanThrow(graph, callSite.resolvedTarget, visited)) return true;
7649
+ }
7650
+ return false;
7651
+ }
7652
+ function analyzeComponentBoundaries(graph, componentName) {
7653
+ const result = {
7654
+ conditionalMountTag: null,
7655
+ suspenseDistance: 0,
7656
+ lacksErrorBoundary: false
7657
+ };
7613
7658
  const usages = graph.jsxByTag.get(componentName) ?? [];
7659
+ if (usages.length === 0) return result;
7614
7660
  for (let i = 0, len = usages.length; i < len; i++) {
7615
7661
  const usage = usages[i];
7616
7662
  if (!usage) continue;
7617
7663
  let current = usage.parent;
7618
7664
  let conditionalTag = null;
7619
7665
  let componentLevels = 0;
7666
+ let foundErrorBoundary = false;
7667
+ let foundSuspense = false;
7620
7668
  while (current) {
7621
7669
  const tag = current.tag;
7622
7670
  if (tag && !current.isDomElement) {
7623
7671
  componentLevels++;
7624
- if (tag === "Suspense") {
7672
+ if (tag === "ErrorBoundary") {
7673
+ foundErrorBoundary = true;
7674
+ } else if (tag === "Suspense") {
7675
+ foundSuspense = true;
7676
+ if (!foundErrorBoundary) {
7677
+ result.lacksErrorBoundary = true;
7678
+ }
7625
7679
  if (conditionalTag !== null && componentLevels > 1) {
7626
- return { tag: conditionalTag, suspenseDistance: componentLevels };
7680
+ result.conditionalMountTag = conditionalTag;
7681
+ result.suspenseDistance = componentLevels;
7627
7682
  }
7628
- return null;
7629
- }
7630
- if (conditionalTag === null && CONDITIONAL_MOUNT_TAGS.has(tag)) {
7683
+ break;
7684
+ } else if (conditionalTag === null && CONDITIONAL_MOUNT_TAGS.has(tag)) {
7631
7685
  conditionalTag = tag;
7632
7686
  }
7633
7687
  }
7634
7688
  current = current.parent;
7635
7689
  }
7636
- if (conditionalTag !== null) {
7637
- return { tag: conditionalTag, suspenseDistance: componentLevels };
7690
+ if (!foundSuspense && !foundErrorBoundary) {
7691
+ result.lacksErrorBoundary = true;
7692
+ if (conditionalTag !== null) {
7693
+ result.conditionalMountTag = conditionalTag;
7694
+ result.suspenseDistance = componentLevels;
7695
+ }
7638
7696
  }
7639
7697
  }
7640
- return null;
7698
+ return result;
7641
7699
  }
7642
7700
  function getContainingComponentName(graph, call) {
7643
7701
  const selfComponent = graph.componentScopes.get(call.scope);
@@ -7659,7 +7717,7 @@ var resourceImplicitSuspense = defineSolidRule({
7659
7717
  severity: "warn",
7660
7718
  messages: messages16,
7661
7719
  meta: {
7662
- description: "Detect createResource without initialValue that implicitly triggers Suspense boundaries.",
7720
+ description: "Detect createResource that implicitly triggers or permanently breaks Suspense boundaries.",
7663
7721
  fixable: false,
7664
7722
  category: "reactivity"
7665
7723
  },
@@ -7667,30 +7725,37 @@ var resourceImplicitSuspense = defineSolidRule({
7667
7725
  check(graph, emit) {
7668
7726
  const resourceCalls = getCallsByPrimitive(graph, "createResource");
7669
7727
  if (resourceCalls.length === 0) return;
7728
+ const throwVisited = /* @__PURE__ */ new Set();
7729
+ const boundaryCache = /* @__PURE__ */ new Map();
7670
7730
  for (let i = 0, len = resourceCalls.length; i < len; i++) {
7671
7731
  const call = resourceCalls[i];
7672
7732
  if (!call) continue;
7673
- if (hasInitialValue(call)) continue;
7674
7733
  const resourceName = getResourceVariableName(call);
7675
7734
  if (!resourceName) continue;
7676
- const resourceVariable = findResourceVariable(graph, resourceName);
7677
- if (resourceVariable && hasLoadingRead(resourceVariable)) {
7678
- emit(
7679
- createDiagnostic(
7680
- graph.file,
7681
- call.node,
7682
- "resource-implicit-suspense",
7683
- "loadingMismatch",
7684
- resolveMessage(messages16.loadingMismatch, { name: resourceName }),
7685
- "warn"
7686
- )
7687
- );
7688
- continue;
7689
- }
7735
+ const hasInitial = hasInitialValue(call);
7690
7736
  const componentName = getContainingComponentName(graph, call);
7737
+ if (!hasInitial) {
7738
+ const resourceVariable = findResourceVariable(graph, resourceName);
7739
+ if (resourceVariable && hasLoadingRead(resourceVariable)) {
7740
+ emit(
7741
+ createDiagnostic(
7742
+ graph.file,
7743
+ call.node,
7744
+ "resource-implicit-suspense",
7745
+ "loadingMismatch",
7746
+ resolveMessage(messages16.loadingMismatch, { name: resourceName }),
7747
+ "warn"
7748
+ )
7749
+ );
7750
+ }
7751
+ }
7691
7752
  if (!componentName) continue;
7692
- const conditional = findConditionalMountAncestor(graph, componentName);
7693
- if (conditional) {
7753
+ let analysis = boundaryCache.get(componentName);
7754
+ if (!analysis) {
7755
+ analysis = analyzeComponentBoundaries(graph, componentName);
7756
+ boundaryCache.set(componentName, analysis);
7757
+ }
7758
+ if (analysis.conditionalMountTag) {
7694
7759
  emit(
7695
7760
  createDiagnostic(
7696
7761
  graph.file,
@@ -7699,12 +7764,27 @@ var resourceImplicitSuspense = defineSolidRule({
7699
7764
  "conditionalSuspense",
7700
7765
  resolveMessage(messages16.conditionalSuspense, {
7701
7766
  name: resourceName,
7702
- mountTag: conditional.tag
7767
+ mountTag: analysis.conditionalMountTag
7703
7768
  }),
7704
7769
  "error"
7705
7770
  )
7706
7771
  );
7707
7772
  }
7773
+ if (analysis.lacksErrorBoundary) {
7774
+ const fetcherFn = resolveFetcherFunction(graph, call);
7775
+ if (fetcherFn && fetcherCanThrow(graph, fetcherFn, throwVisited)) {
7776
+ emit(
7777
+ createDiagnostic(
7778
+ graph.file,
7779
+ call.node,
7780
+ "resource-implicit-suspense",
7781
+ "missingErrorBoundary",
7782
+ resolveMessage(messages16.missingErrorBoundary, { name: resourceName }),
7783
+ "error"
7784
+ )
7785
+ );
7786
+ }
7787
+ }
7708
7788
  }
7709
7789
  }
7710
7790
  });
@@ -40227,4 +40307,4 @@ export {
40227
40307
  rules3,
40228
40308
  runCrossFileRules
40229
40309
  };
40230
- //# sourceMappingURL=chunk-5IOPY65Q.js.map
40310
+ //# sourceMappingURL=chunk-V6U7TQCD.js.map