mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-11 09:25:35 +08:00
Merge pull request #1106 from DeusData/fix/1085-jsx-alias-calls
fix(pipeline): restore parallel CALLS resolution for LSP-unresolvable targets
This commit is contained in:
@@ -2274,10 +2274,7 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
if (lsp) {
|
||||
/* Canonicalise to the gbuf node's QN so res.qualified_name matches
|
||||
* the gbuf even when the cross-file fallback had to prefix the
|
||||
* project name. If neither lookup hits, leave res.qualified_name
|
||||
* empty — the LSP was confident but its target isn't in the gbuf
|
||||
* (external/unindexed), so drop the edge rather than fall back to
|
||||
* the registry resolver, matching prior single-lookup semantics. */
|
||||
* project name. */
|
||||
lsp_target = cbm_pipeline_lsp_target_node(rc->main_gbuf, rc->project_name,
|
||||
lsp->callee_qn, allow_tail);
|
||||
if (lsp_target) {
|
||||
@@ -2287,7 +2284,19 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
res.candidate_count = 1;
|
||||
ws->lsp_overrides++;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
/* #1085: fall back to the registry resolver whenever the LSP did not
|
||||
* yield a gbuf-resolvable target — whether no LSP resolution existed,
|
||||
* OR the LSP was confident but its callee_qn isn't a node in the gbuf
|
||||
* (the JSX-via-tsconfig-alias case: the TS LSP resolves the element
|
||||
* ref to an alias-path QN that never matches a def node, so lsp_target
|
||||
* is NULL). The old `else` ran the registry ONLY when lsp was null, so
|
||||
* an LSP-with-unresolvable-target dropped the edge outright — silently
|
||||
* losing every alias-imported JSX component edge on the parallel path
|
||||
* (~21% of a Next.js call graph) while the sequential pass, which falls
|
||||
* THROUGH to the registry here, kept them. This restores seq/parallel
|
||||
* parity via the import_map / unique_name resolution. */
|
||||
if (!res.qualified_name || !res.qualified_name[0]) {
|
||||
res = cbm_registry_resolve(rc->registry, call->callee_name, module_qn, imp_keys,
|
||||
imp_vals, imp_count);
|
||||
}
|
||||
|
||||
@@ -222,6 +222,63 @@ static cbm_store_t *et_index_parallel(EtProj *lp, const EtFile *meaningful, int
|
||||
return et_index_files(lp, files, n);
|
||||
}
|
||||
|
||||
/* #1085: count CALLS edges whose target node has `name`, indexing via the
|
||||
* PARALLEL path (et_index_parallel pads to >50 files). The parallel resolver
|
||||
* used to drop the edge whenever the LSP resolved a callee but its target QN
|
||||
* wasn't a gbuf node (JSX component imported through a tsconfig `paths` alias:
|
||||
* the TS LSP resolves the element ref to an alias-path QN that never matches a
|
||||
* def node) — sequential kept the edge via the registry import_map fallback,
|
||||
* so the two pipelines disagreed and ~21% of a Next.js call graph vanished on
|
||||
* the default (parallel) path. Needs >50 files to reproduce. */
|
||||
static int et_calls_to_name_parallel(const EtFile *meaningful, int n_mean, const char *name) {
|
||||
EtProj lp;
|
||||
cbm_store_t *store = et_index_parallel(&lp, meaningful, n_mean);
|
||||
int hits = 0;
|
||||
if (store) {
|
||||
cbm_edge_t *edges = NULL;
|
||||
int n = 0;
|
||||
if (cbm_store_find_edges_by_type(store, lp.project, "CALLS", &edges, &n) == CBM_STORE_OK) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
cbm_node_t tgt;
|
||||
if (cbm_store_find_node_by_id(store, edges[i].target_id, &tgt) != CBM_STORE_OK)
|
||||
continue;
|
||||
if (tgt.name && strcmp(tgt.name, name) == 0)
|
||||
hits++;
|
||||
cbm_node_free_fields(&tgt);
|
||||
}
|
||||
cbm_store_free_edges(edges, n);
|
||||
}
|
||||
}
|
||||
et_cleanup(&lp, store);
|
||||
return hits;
|
||||
}
|
||||
|
||||
TEST(calls_jsx_component_via_tsconfig_alias_parallel_issue1085) {
|
||||
static const EtFile f[] = {
|
||||
{"tsconfig.json",
|
||||
"{ \"compilerOptions\": { \"baseUrl\": \".\", "
|
||||
"\"paths\": { \"@/*\": [\"./src/*\"] } } }\n"},
|
||||
{"src/components/ui/kpi-card.tsx",
|
||||
"export function KpiCard({ label }: { label: string }) {\n"
|
||||
" return <div>{label}</div>;\n}\n"},
|
||||
{"src/app/dashboard-a.tsx",
|
||||
"import { KpiCard } from \"@/components/ui/kpi-card\";\n"
|
||||
"export function DashboardA() {\n return <KpiCard label=\"a\" />;\n}\n"},
|
||||
{"src/app/dashboard-b.tsx",
|
||||
"import { KpiCard } from \"@/components/ui/kpi-card\";\n"
|
||||
"export function DashboardB() {\n return <KpiCard label=\"b\" />;\n}\n"}};
|
||||
/* RED before the fix: 0 (parallel drops alias-JSX). GREEN: both renders
|
||||
* resolve, exactly as the sequential path already does. */
|
||||
int hits = et_calls_to_name_parallel(f, 4, "KpiCard");
|
||||
if (hits < 2) {
|
||||
fprintf(stderr, " [1085] FAIL CALLS->KpiCard on parallel path = %d (expected >= 2); "
|
||||
"alias-imported JSX component edges dropped\n",
|
||||
hits);
|
||||
}
|
||||
ASSERT_TRUE(hits >= 2);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
* HANDLES — route→handler across web frameworks
|
||||
*
|
||||
@@ -1514,6 +1571,7 @@ TEST(override_go_interface) {
|
||||
|
||||
SUITE(edge_types_probe) {
|
||||
/* HANDLES — route→handler across web frameworks */
|
||||
RUN_TEST(calls_jsx_component_via_tsconfig_alias_parallel_issue1085);
|
||||
RUN_TEST(handles_flask_python);
|
||||
RUN_TEST(handles_fastapi_python);
|
||||
RUN_TEST(handles_drf_action_python);
|
||||
|
||||
Reference in New Issue
Block a user