Skip to content

Commit 548927a

Browse files
authored
Merge pull request DeusData#1106 from DeusData/fix/1085-jsx-alias-calls
fix(pipeline): restore parallel CALLS resolution for LSP-unresolvable targets
2 parents ef14822 + 8c39465 commit 548927a

2 files changed

Lines changed: 72 additions & 5 deletions

File tree

‎src/pipeline/pass_parallel.c‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,10 +2274,7 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
22742274
if (lsp) {
22752275
/* Canonicalise to the gbuf node's QN so res.qualified_name matches
22762276
* the gbuf even when the cross-file fallback had to prefix the
2277-
* project name. If neither lookup hits, leave res.qualified_name
2278-
* empty — the LSP was confident but its target isn't in the gbuf
2279-
* (external/unindexed), so drop the edge rather than fall back to
2280-
* the registry resolver, matching prior single-lookup semantics. */
2277+
* project name. */
22812278
lsp_target = cbm_pipeline_lsp_target_node(rc->main_gbuf, rc->project_name,
22822279
lsp->callee_qn, allow_tail);
22832280
if (lsp_target) {
@@ -2287,7 +2284,19 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
22872284
res.candidate_count = 1;
22882285
ws->lsp_overrides++;
22892286
}
2290-
} else {
2287+
}
2288+
/* #1085: fall back to the registry resolver whenever the LSP did not
2289+
* yield a gbuf-resolvable target — whether no LSP resolution existed,
2290+
* OR the LSP was confident but its callee_qn isn't a node in the gbuf
2291+
* (the JSX-via-tsconfig-alias case: the TS LSP resolves the element
2292+
* ref to an alias-path QN that never matches a def node, so lsp_target
2293+
* is NULL). The old `else` ran the registry ONLY when lsp was null, so
2294+
* an LSP-with-unresolvable-target dropped the edge outright — silently
2295+
* losing every alias-imported JSX component edge on the parallel path
2296+
* (~21% of a Next.js call graph) while the sequential pass, which falls
2297+
* THROUGH to the registry here, kept them. This restores seq/parallel
2298+
* parity via the import_map / unique_name resolution. */
2299+
if (!res.qualified_name || !res.qualified_name[0]) {
22912300
res = cbm_registry_resolve(rc->registry, call->callee_name, module_qn, imp_keys,
22922301
imp_vals, imp_count);
22932302
}

‎tests/test_edge_types_probe.c‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,63 @@ static cbm_store_t *et_index_parallel(EtProj *lp, const EtFile *meaningful, int
222222
return et_index_files(lp, files, n);
223223
}
224224

225+
/* #1085: count CALLS edges whose target node has `name`, indexing via the
226+
* PARALLEL path (et_index_parallel pads to >50 files). The parallel resolver
227+
* used to drop the edge whenever the LSP resolved a callee but its target QN
228+
* wasn't a gbuf node (JSX component imported through a tsconfig `paths` alias:
229+
* the TS LSP resolves the element ref to an alias-path QN that never matches a
230+
* def node) — sequential kept the edge via the registry import_map fallback,
231+
* so the two pipelines disagreed and ~21% of a Next.js call graph vanished on
232+
* the default (parallel) path. Needs >50 files to reproduce. */
233+
static int et_calls_to_name_parallel(const EtFile *meaningful, int n_mean, const char *name) {
234+
EtProj lp;
235+
cbm_store_t *store = et_index_parallel(&lp, meaningful, n_mean);
236+
int hits = 0;
237+
if (store) {
238+
cbm_edge_t *edges = NULL;
239+
int n = 0;
240+
if (cbm_store_find_edges_by_type(store, lp.project, "CALLS", &edges, &n) == CBM_STORE_OK) {
241+
for (int i = 0; i < n; i++) {
242+
cbm_node_t tgt;
243+
if (cbm_store_find_node_by_id(store, edges[i].target_id, &tgt) != CBM_STORE_OK)
244+
continue;
245+
if (tgt.name && strcmp(tgt.name, name) == 0)
246+
hits++;
247+
cbm_node_free_fields(&tgt);
248+
}
249+
cbm_store_free_edges(edges, n);
250+
}
251+
}
252+
et_cleanup(&lp, store);
253+
return hits;
254+
}
255+
256+
TEST(calls_jsx_component_via_tsconfig_alias_parallel_issue1085) {
257+
static const EtFile f[] = {
258+
{"tsconfig.json",
259+
"{ \"compilerOptions\": { \"baseUrl\": \".\", "
260+
"\"paths\": { \"@/*\": [\"./src/*\"] } } }\n"},
261+
{"src/components/ui/kpi-card.tsx",
262+
"export function KpiCard({ label }: { label: string }) {\n"
263+
" return <div>{label}</div>;\n}\n"},
264+
{"src/app/dashboard-a.tsx",
265+
"import { KpiCard } from \"@/components/ui/kpi-card\";\n"
266+
"export function DashboardA() {\n return <KpiCard label=\"a\" />;\n}\n"},
267+
{"src/app/dashboard-b.tsx",
268+
"import { KpiCard } from \"@/components/ui/kpi-card\";\n"
269+
"export function DashboardB() {\n return <KpiCard label=\"b\" />;\n}\n"}};
270+
/* RED before the fix: 0 (parallel drops alias-JSX). GREEN: both renders
271+
* resolve, exactly as the sequential path already does. */
272+
int hits = et_calls_to_name_parallel(f, 4, "KpiCard");
273+
if (hits < 2) {
274+
fprintf(stderr, " [1085] FAIL CALLS->KpiCard on parallel path = %d (expected >= 2); "
275+
"alias-imported JSX component edges dropped\n",
276+
hits);
277+
}
278+
ASSERT_TRUE(hits >= 2);
279+
PASS();
280+
}
281+
225282
/* ══════════════════════════════════════════════════════════════════
226283
* HANDLES — route→handler across web frameworks
227284
*
@@ -1514,6 +1571,7 @@ TEST(override_go_interface) {
15141571

15151572
SUITE(edge_types_probe) {
15161573
/* HANDLES — route→handler across web frameworks */
1574+
RUN_TEST(calls_jsx_component_via_tsconfig_alias_parallel_issue1085);
15171575
RUN_TEST(handles_flask_python);
15181576
RUN_TEST(handles_fastapi_python);
15191577
RUN_TEST(handles_drf_action_python);

0 commit comments

Comments
 (0)