@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.
- package/dist/{chunk-IF3V4IQF.js → chunk-4PJEULOI.js} +5 -4
- package/dist/chunk-4PJEULOI.js.map +1 -0
- package/dist/{chunk-5IOPY65Q.js → chunk-V6U7TQCD.js} +110 -30
- package/dist/chunk-V6U7TQCD.js.map +1 -0
- package/dist/eslint-plugin.cjs +109 -29
- package/dist/eslint-plugin.cjs.map +1 -1
- package/dist/eslint-plugin.js +1 -1
- package/dist/index.cjs +113 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/rules-manifest.cjs +4 -3
- package/dist/rules-manifest.cjs.map +1 -1
- package/dist/rules-manifest.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5IOPY65Q.js.map +0 -1
- package/dist/chunk-IF3V4IQF.js.map +0 -1
|
@@ -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}}'
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
7680
|
+
result.conditionalMountTag = conditionalTag;
|
|
7681
|
+
result.suspenseDistance = componentLevels;
|
|
7627
7682
|
}
|
|
7628
|
-
|
|
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 (
|
|
7637
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7693
|
-
if (
|
|
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:
|
|
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-
|
|
40310
|
+
//# sourceMappingURL=chunk-V6U7TQCD.js.map
|