From c1e2414fb085dc63fa89a8d6e9df5a44c44d4a72 Mon Sep 17 00:00:00 2001 From: lorenzozanee Date: Mon, 12 Oct 2026 00:19:31 +0800 Subject: [PATCH 1/2] fix(pipeline): resolve parent-relative non-code imports Signed-off-by: lorenzozanee --- src/pipeline/fqn.c | 3 ++ src/pipeline/pass_pkgmap.c | 16 +++++++ tests/test_edge_imports.c | 98 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/pipeline/fqn.c b/src/pipeline/fqn.c index 96e959a0b..cb2c49a5b 100644 --- a/src/pipeline/fqn.c +++ b/src/pipeline/fqn.c @@ -317,6 +317,9 @@ static char *resolve_js_relative(char *buf, size_t buf_size, const char *module_ continue; } if (seg_len == FQN_DOTDOT_LEN && seg_start[0] == '.' && seg_start[FQN_SEP_LEN] == '.') { + if (!buf[0]) { + return NULL; + } path_pop_segment(buf); continue; } diff --git a/src/pipeline/pass_pkgmap.c b/src/pipeline/pass_pkgmap.c index f12dd8d4e..c934c9829 100644 --- a/src/pipeline/pass_pkgmap.c +++ b/src/pipeline/pass_pkgmap.c @@ -2305,6 +2305,22 @@ const cbm_gbuf_node_t *cbm_pipeline_resolve_import_node(const cbm_pipeline_ctx_t } } + /* Relative non-module files can have a File QN that does not match the + * module QN above. Resolve the exact file after module and sibling lookup. */ + if (imp->module_path[0] == '.' && + (imp->module_path[1] == '/' || + (imp->module_path[1] == '.' && imp->module_path[2] == '/'))) { + char *resolved = cbm_pipeline_resolve_relative_import(source_rel, imp->module_path); + if (resolved) { + const cbm_gbuf_node_t *file = + cbm_pipeline_file_node(ctx->gbuf, ctx->project_name, resolved); + free(resolved); + if (file) { + return file; + } + } + } + /* Strategy 2: namespace map. `using App.Utils`, `import com.example.Foo`, * `use App\Utils\Helper` name a NAMESPACE (or a member of it) that the * path-based QN cannot express. Try the full module path and progressively diff --git a/tests/test_edge_imports.c b/tests/test_edge_imports.c index 685cad0fb..fbeb90942 100644 --- a/tests/test_edge_imports.c +++ b/tests/test_edge_imports.c @@ -266,6 +266,102 @@ static int ei_edge_present(const EILangFile *files, int nfiles, const char *edge return got >= floor; } +typedef struct { + const char *importer; + const char *target; +} EIFileEdgeExpect; + +static int ei_file_edges_match(const EILangFile *files, int nfiles, const EIFileEdgeExpect *want, + int nwant, int expected_count) { + EILangProj lp; + cbm_store_t *store = ei_index_files(&lp, files, nfiles); + int ok = store && cbm_store_count_edges_by_type(store, lp.project, "IMPORTS") == expected_count; + if (store && !ok) { + int got = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); + fprintf(stderr, " [IMPORTS] FAIL count=%d expected==%d\n", got, expected_count); + } + for (int i = 0; store && i < nwant; i++) { + int64_t source_id = ei_node_id_for_file_label(store, lp.project, want[i].importer, "File"); + cbm_edge_t *edges = NULL; + int edge_count = 0; + int found = 0; + if (source_id > 0 && + cbm_store_find_edges_by_source_type(store, source_id, "IMPORTS", &edges, &edge_count) == + CBM_STORE_OK) { + for (int j = 0; j < edge_count; j++) { + cbm_node_t target; + memset(&target, 0, sizeof(target)); + if (cbm_store_find_node_by_id(store, edges[j].target_id, &target) == CBM_STORE_OK) { + found = target.file_path && strcmp(target.file_path, want[i].target) == 0; + cbm_node_free_fields(&target); + } + if (found) { + break; + } + } + } + cbm_store_free_edges(edges, edge_count); + if (!found) { + fprintf(stderr, " [IMPORTS] FAIL %s -> %s\n", want[i].importer, want[i].target); + ok = 0; + } + } + ei_cleanup(&lp, store); + return ok; +} + +TEST(ei_relative_astro_css_imports_issue2558) { + static const EILangFile f[] = { + {"Badge.astro", "outside\n"}, + {"src/components/shared/Button.astro", + "---\nexport function buttonLabel(t: string): string { return t; }\n---\n