@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
package/dist/eslint-plugin.cjs
CHANGED
|
@@ -11732,7 +11732,8 @@ var CONDITIONAL_MOUNT_TAGS = /* @__PURE__ */ new Set([
|
|
|
11732
11732
|
]);
|
|
11733
11733
|
var messages16 = {
|
|
11734
11734
|
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: ... })",
|
|
11735
|
-
conditionalSuspense: "createResource '{{name}}'
|
|
11735
|
+
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.",
|
|
11736
|
+
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."
|
|
11736
11737
|
};
|
|
11737
11738
|
var options16 = {};
|
|
11738
11739
|
function hasInitialValue(call) {
|
|
@@ -11775,35 +11776,92 @@ function hasLoadingRead(resourceVariable) {
|
|
|
11775
11776
|
}
|
|
11776
11777
|
return false;
|
|
11777
11778
|
}
|
|
11778
|
-
function
|
|
11779
|
+
function resolveFetcherFunction(graph, call) {
|
|
11780
|
+
const args = call.node.arguments;
|
|
11781
|
+
if (args.length === 0) return null;
|
|
11782
|
+
let fetcherNode;
|
|
11783
|
+
if (args.length === 1) {
|
|
11784
|
+
fetcherNode = args[0];
|
|
11785
|
+
} else if (args.length === 2) {
|
|
11786
|
+
const lastArg = args[1];
|
|
11787
|
+
fetcherNode = lastArg && lastArg.type === "ObjectExpression" ? args[0] : args[1];
|
|
11788
|
+
} else {
|
|
11789
|
+
fetcherNode = args[1];
|
|
11790
|
+
}
|
|
11791
|
+
if (!fetcherNode) return null;
|
|
11792
|
+
if (fetcherNode.type === "ArrowFunctionExpression" || fetcherNode.type === "FunctionExpression") {
|
|
11793
|
+
return graph.functionsByNode.get(fetcherNode) ?? null;
|
|
11794
|
+
}
|
|
11795
|
+
if (fetcherNode.type === "Identifier") {
|
|
11796
|
+
const fns = graph.functionsByName.get(fetcherNode.name);
|
|
11797
|
+
if (fns && fns.length > 0) {
|
|
11798
|
+
const fn = fns[0];
|
|
11799
|
+
if (fn) return fn;
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
return null;
|
|
11803
|
+
}
|
|
11804
|
+
function fetcherCanThrow(graph, fn, visited) {
|
|
11805
|
+
if (visited.has(fn.id)) return false;
|
|
11806
|
+
visited.add(fn.id);
|
|
11807
|
+
if (fn.async && fn.awaitRanges.length > 0) return true;
|
|
11808
|
+
if (fn.hasThrowStatement) return true;
|
|
11809
|
+
const callSites = fn.callSites;
|
|
11810
|
+
for (let i = 0, len = callSites.length; i < len; i++) {
|
|
11811
|
+
const callSite = callSites[i];
|
|
11812
|
+
if (!callSite) continue;
|
|
11813
|
+
if (!callSite.resolvedTarget) return true;
|
|
11814
|
+
if (fetcherCanThrow(graph, callSite.resolvedTarget, visited)) return true;
|
|
11815
|
+
}
|
|
11816
|
+
return false;
|
|
11817
|
+
}
|
|
11818
|
+
function analyzeComponentBoundaries(graph, componentName) {
|
|
11819
|
+
const result = {
|
|
11820
|
+
conditionalMountTag: null,
|
|
11821
|
+
suspenseDistance: 0,
|
|
11822
|
+
lacksErrorBoundary: false
|
|
11823
|
+
};
|
|
11779
11824
|
const usages = graph.jsxByTag.get(componentName) ?? [];
|
|
11825
|
+
if (usages.length === 0) return result;
|
|
11780
11826
|
for (let i = 0, len = usages.length; i < len; i++) {
|
|
11781
11827
|
const usage = usages[i];
|
|
11782
11828
|
if (!usage) continue;
|
|
11783
11829
|
let current = usage.parent;
|
|
11784
11830
|
let conditionalTag = null;
|
|
11785
11831
|
let componentLevels = 0;
|
|
11832
|
+
let foundErrorBoundary = false;
|
|
11833
|
+
let foundSuspense = false;
|
|
11786
11834
|
while (current) {
|
|
11787
11835
|
const tag = current.tag;
|
|
11788
11836
|
if (tag && !current.isDomElement) {
|
|
11789
11837
|
componentLevels++;
|
|
11790
|
-
if (tag === "
|
|
11838
|
+
if (tag === "ErrorBoundary") {
|
|
11839
|
+
foundErrorBoundary = true;
|
|
11840
|
+
} else if (tag === "Suspense") {
|
|
11841
|
+
foundSuspense = true;
|
|
11842
|
+
if (!foundErrorBoundary) {
|
|
11843
|
+
result.lacksErrorBoundary = true;
|
|
11844
|
+
}
|
|
11791
11845
|
if (conditionalTag !== null && componentLevels > 1) {
|
|
11792
|
-
|
|
11846
|
+
result.conditionalMountTag = conditionalTag;
|
|
11847
|
+
result.suspenseDistance = componentLevels;
|
|
11793
11848
|
}
|
|
11794
|
-
|
|
11795
|
-
}
|
|
11796
|
-
if (conditionalTag === null && CONDITIONAL_MOUNT_TAGS.has(tag)) {
|
|
11849
|
+
break;
|
|
11850
|
+
} else if (conditionalTag === null && CONDITIONAL_MOUNT_TAGS.has(tag)) {
|
|
11797
11851
|
conditionalTag = tag;
|
|
11798
11852
|
}
|
|
11799
11853
|
}
|
|
11800
11854
|
current = current.parent;
|
|
11801
11855
|
}
|
|
11802
|
-
if (
|
|
11803
|
-
|
|
11856
|
+
if (!foundSuspense && !foundErrorBoundary) {
|
|
11857
|
+
result.lacksErrorBoundary = true;
|
|
11858
|
+
if (conditionalTag !== null) {
|
|
11859
|
+
result.conditionalMountTag = conditionalTag;
|
|
11860
|
+
result.suspenseDistance = componentLevels;
|
|
11861
|
+
}
|
|
11804
11862
|
}
|
|
11805
11863
|
}
|
|
11806
|
-
return
|
|
11864
|
+
return result;
|
|
11807
11865
|
}
|
|
11808
11866
|
function getContainingComponentName(graph, call) {
|
|
11809
11867
|
const selfComponent = graph.componentScopes.get(call.scope);
|
|
@@ -11825,7 +11883,7 @@ var resourceImplicitSuspense = defineSolidRule({
|
|
|
11825
11883
|
severity: "warn",
|
|
11826
11884
|
messages: messages16,
|
|
11827
11885
|
meta: {
|
|
11828
|
-
description: "Detect createResource
|
|
11886
|
+
description: "Detect createResource that implicitly triggers or permanently breaks Suspense boundaries.",
|
|
11829
11887
|
fixable: false,
|
|
11830
11888
|
category: "reactivity"
|
|
11831
11889
|
},
|
|
@@ -11833,30 +11891,37 @@ var resourceImplicitSuspense = defineSolidRule({
|
|
|
11833
11891
|
check(graph, emit) {
|
|
11834
11892
|
const resourceCalls = getCallsByPrimitive(graph, "createResource");
|
|
11835
11893
|
if (resourceCalls.length === 0) return;
|
|
11894
|
+
const throwVisited = /* @__PURE__ */ new Set();
|
|
11895
|
+
const boundaryCache = /* @__PURE__ */ new Map();
|
|
11836
11896
|
for (let i = 0, len = resourceCalls.length; i < len; i++) {
|
|
11837
11897
|
const call = resourceCalls[i];
|
|
11838
11898
|
if (!call) continue;
|
|
11839
|
-
if (hasInitialValue(call)) continue;
|
|
11840
11899
|
const resourceName = getResourceVariableName(call);
|
|
11841
11900
|
if (!resourceName) continue;
|
|
11842
|
-
const
|
|
11843
|
-
if (resourceVariable && hasLoadingRead(resourceVariable)) {
|
|
11844
|
-
emit(
|
|
11845
|
-
createDiagnostic(
|
|
11846
|
-
graph.file,
|
|
11847
|
-
call.node,
|
|
11848
|
-
"resource-implicit-suspense",
|
|
11849
|
-
"loadingMismatch",
|
|
11850
|
-
resolveMessage(messages16.loadingMismatch, { name: resourceName }),
|
|
11851
|
-
"warn"
|
|
11852
|
-
)
|
|
11853
|
-
);
|
|
11854
|
-
continue;
|
|
11855
|
-
}
|
|
11901
|
+
const hasInitial = hasInitialValue(call);
|
|
11856
11902
|
const componentName = getContainingComponentName(graph, call);
|
|
11903
|
+
if (!hasInitial) {
|
|
11904
|
+
const resourceVariable = findResourceVariable(graph, resourceName);
|
|
11905
|
+
if (resourceVariable && hasLoadingRead(resourceVariable)) {
|
|
11906
|
+
emit(
|
|
11907
|
+
createDiagnostic(
|
|
11908
|
+
graph.file,
|
|
11909
|
+
call.node,
|
|
11910
|
+
"resource-implicit-suspense",
|
|
11911
|
+
"loadingMismatch",
|
|
11912
|
+
resolveMessage(messages16.loadingMismatch, { name: resourceName }),
|
|
11913
|
+
"warn"
|
|
11914
|
+
)
|
|
11915
|
+
);
|
|
11916
|
+
}
|
|
11917
|
+
}
|
|
11857
11918
|
if (!componentName) continue;
|
|
11858
|
-
|
|
11859
|
-
if (
|
|
11919
|
+
let analysis = boundaryCache.get(componentName);
|
|
11920
|
+
if (!analysis) {
|
|
11921
|
+
analysis = analyzeComponentBoundaries(graph, componentName);
|
|
11922
|
+
boundaryCache.set(componentName, analysis);
|
|
11923
|
+
}
|
|
11924
|
+
if (analysis.conditionalMountTag) {
|
|
11860
11925
|
emit(
|
|
11861
11926
|
createDiagnostic(
|
|
11862
11927
|
graph.file,
|
|
@@ -11865,12 +11930,27 @@ var resourceImplicitSuspense = defineSolidRule({
|
|
|
11865
11930
|
"conditionalSuspense",
|
|
11866
11931
|
resolveMessage(messages16.conditionalSuspense, {
|
|
11867
11932
|
name: resourceName,
|
|
11868
|
-
mountTag:
|
|
11933
|
+
mountTag: analysis.conditionalMountTag
|
|
11869
11934
|
}),
|
|
11870
11935
|
"error"
|
|
11871
11936
|
)
|
|
11872
11937
|
);
|
|
11873
11938
|
}
|
|
11939
|
+
if (analysis.lacksErrorBoundary) {
|
|
11940
|
+
const fetcherFn = resolveFetcherFunction(graph, call);
|
|
11941
|
+
if (fetcherFn && fetcherCanThrow(graph, fetcherFn, throwVisited)) {
|
|
11942
|
+
emit(
|
|
11943
|
+
createDiagnostic(
|
|
11944
|
+
graph.file,
|
|
11945
|
+
call.node,
|
|
11946
|
+
"resource-implicit-suspense",
|
|
11947
|
+
"missingErrorBoundary",
|
|
11948
|
+
resolveMessage(messages16.missingErrorBoundary, { name: resourceName }),
|
|
11949
|
+
"error"
|
|
11950
|
+
)
|
|
11951
|
+
);
|
|
11952
|
+
}
|
|
11953
|
+
}
|
|
11874
11954
|
}
|
|
11875
11955
|
}
|
|
11876
11956
|
});
|