From 055dceadd36b1919e5271dc512fa660035e1170f Mon Sep 17 00:00:00 2001 From: Arthur Geron <3487334+arthurgeron@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:45:54 -0300 Subject: [PATCH] fix(#6052): treat unstable_cache as a function wrapper, not an un-emitted const --- internal/extractors/javascript/extractor.go | 9 ++++++++- .../extractors/javascript/extractor_test.go | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/extractors/javascript/extractor.go b/internal/extractors/javascript/extractor.go index 37f875a61..801cd02da 100644 --- a/internal/extractors/javascript/extractor.go +++ b/internal/extractors/javascript/extractor.go @@ -2046,7 +2046,14 @@ func (x *extractor) isFunctionWrapperCall(n ts.Node) bool { // also take a function but are imperative side-effects, // not values bound to a name — the `const cleanup = // useEffect(...)` shape is not idiomatic. - "useCallback", "useMemo": + "useCallback", "useMemo", + // Next.js data cache (next/cache): `const getItems = + // unstable_cache(async (...) => {...}, [key], {revalidate})` + // returns the cached wrapper around its first argument, so the + // bound name is a function, the same shape as the hook wrappers + // above. Without this the declaration falls through every + // branch and no entity is emitted for the name at all. + "unstable_cache": return true } // Issue #2859 — generic Higher-Order Component naming convention. diff --git a/internal/extractors/javascript/extractor_test.go b/internal/extractors/javascript/extractor_test.go index 7cab6e451..22dddb74b 100644 --- a/internal/extractors/javascript/extractor_test.go +++ b/internal/extractors/javascript/extractor_test.go @@ -759,6 +759,26 @@ func TestConstExportReactWrappers(t *testing.T) { assertKind(t, entities, "Connected", "SCOPE.Operation") } +const constExportNextUnstableCacheSrc = ` +import { unstable_cache } from "next/cache"; + +export const getItems = unstable_cache( + async (ownerId) => fetchItems(ownerId), + ["items"], + { revalidate: 60 }, +); +` + +// unstable_cache returns the cached wrapper around its first argument, so the +// bound name is callable. Same shape as useCallback/useMemo. +func TestConstExportNextUnstableCache(t *testing.T) { + src := []byte(constExportNextUnstableCacheSrc) + tree := parseJS(t, src) + entities := extract(t, src, "javascript", tree) + + assertKind(t, entities, "getItems", "SCOPE.Operation") +} + const constExportHookAliasSrc = ` import { useSelector, useDispatch } from "react-redux";