mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 08:13:37 +08:00
Merge pull request #2595 from lorenzozanee/fix/relative-non-code-imports
fix(pipeline): resolve parent-relative Astro and CSS imports
This commit is contained in:
@@ -329,6 +329,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;
|
||||
}
|
||||
|
||||
@@ -2115,6 +2115,37 @@ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx
|
||||
return found;
|
||||
}
|
||||
|
||||
/* The File an explicit relative import (`./x`, `../x`) names exactly, for
|
||||
* files whose File QN differs from the module QN Strategy 1 tries
|
||||
* (`../styles/global.css`, `../layouts/Layout.astro`, #2558). Resolved in
|
||||
* stack buffers by cbm_repo_rel_join, which refuses a path above the
|
||||
* repository root. */
|
||||
static const cbm_gbuf_node_t *resolve_relative_file(const cbm_pipeline_ctx_t *ctx,
|
||||
const char *source_rel, const char *module_path,
|
||||
bool php_only) {
|
||||
if (!source_rel || !module_path || module_path[0] != '.' ||
|
||||
!(module_path[1] == '/' || (module_path[1] == '.' && module_path[2] == '/'))) {
|
||||
return NULL;
|
||||
}
|
||||
char dir[PKGMAP_PATH_BUF];
|
||||
int dir_len = snprintf(dir, sizeof(dir), "%s", source_rel);
|
||||
if (dir_len < 0 || (size_t)dir_len >= sizeof(dir)) {
|
||||
return NULL;
|
||||
}
|
||||
char *slash = strrchr(dir, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
} else {
|
||||
dir[0] = '\0';
|
||||
}
|
||||
char path[PKGMAP_PATH_BUF];
|
||||
if (!cbm_repo_rel_join(dir, module_path, path, sizeof(path))) {
|
||||
return NULL;
|
||||
}
|
||||
const cbm_gbuf_node_t *file = cbm_pipeline_file_node(ctx->gbuf, ctx->project_name, path);
|
||||
return file && (!php_only || node_in_php_file(file)) ? file : NULL;
|
||||
}
|
||||
|
||||
/* ── PHP PSR-4 class imports (#1186) ──────────────────────────────── */
|
||||
|
||||
typedef enum {
|
||||
@@ -2389,6 +2420,15 @@ static const cbm_gbuf_node_t *resolve_import_strategies(
|
||||
}
|
||||
}
|
||||
|
||||
/* Strategy 1c: 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. */
|
||||
const cbm_gbuf_node_t *rel_file =
|
||||
resolve_relative_file(ctx, source_rel, imp->module_path, php_only);
|
||||
if (rel_file) {
|
||||
return rel_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
|
||||
|
||||
@@ -267,6 +267,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", "<span>outside</span>\n"},
|
||||
{"src/components/shared/Button.astro",
|
||||
"---\nexport function buttonLabel(t: string): string { return t; }\n---\n<button />\n"},
|
||||
{"src/components/Badge.astro", "<span>badge</span>\n"},
|
||||
{"src/components/Card.astro",
|
||||
"---\nimport Badge from './Badge.astro';\n"
|
||||
"import Button from './shared/Button.astro';\n---\n<div><Badge /><Button /></div>\n"},
|
||||
{"src/layouts/local.css", "body { margin: 0; }\n"},
|
||||
{"src/styles/global.css", "body { color: black; }\n"},
|
||||
{"src/layouts/Layout.astro",
|
||||
"---\nimport '../styles/global.css';\nimport './local.css';\n---\n"
|
||||
"<html><body><slot /></body></html>\n"},
|
||||
{"src/lib/format.ts", "export function formatTitle(t: string): string { return t.trim(); }\n"},
|
||||
{"src/lib/registry.ts",
|
||||
"import Badge from '../components/Badge.astro';\n"
|
||||
"import '../styles/global.css';\n"
|
||||
"import { formatTitle } from './format';\n"
|
||||
"export function registry(): unknown[] { return [Badge, formatTitle]; }\n"},
|
||||
{"src/lib/sub/deep.ts",
|
||||
"import { formatTitle } from '../format';\n"
|
||||
"import { registry } from '../registry.ts';\n"
|
||||
"export function deep(): string { registry(); return formatTitle('x'); }\n"},
|
||||
{"src/pages/index.astro",
|
||||
"---\nimport Layout from '../layouts/Layout.astro';\n"
|
||||
"import Card from '../components/Card.astro';\n"
|
||||
"import Outside from '../../../Badge.astro';\n"
|
||||
"import { formatTitle } from '../lib/format';\n"
|
||||
"const title = formatTitle(' Home ');\n---\n"
|
||||
"<Layout><h1>{title}</h1><Card /></Layout>\n"},
|
||||
};
|
||||
static const EIFileEdgeExpect want[] = {
|
||||
{"src/components/Card.astro", "src/components/Badge.astro"},
|
||||
{"src/components/Card.astro", "src/components/shared/Button.astro"},
|
||||
{"src/layouts/Layout.astro", "src/styles/global.css"},
|
||||
{"src/layouts/Layout.astro", "src/layouts/local.css"},
|
||||
{"src/lib/registry.ts", "src/components/Badge.astro"},
|
||||
{"src/lib/registry.ts", "src/styles/global.css"},
|
||||
{"src/lib/registry.ts", "src/lib/format.ts"},
|
||||
{"src/lib/sub/deep.ts", "src/lib/format.ts"},
|
||||
{"src/lib/sub/deep.ts", "src/lib/registry.ts"},
|
||||
{"src/pages/index.astro", "src/layouts/Layout.astro"},
|
||||
{"src/pages/index.astro", "src/components/Card.astro"},
|
||||
{"src/pages/index.astro", "src/lib/format.ts"},
|
||||
};
|
||||
|
||||
ASSERT_TRUE(ei_file_edges_match(f, (int)(sizeof(f) / sizeof(f[0])), want,
|
||||
(int)(sizeof(want) / sizeof(want[0])), 12));
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* GREEN GUARD — Python
|
||||
*
|
||||
@@ -2299,6 +2395,8 @@ TEST(ei_php_psr4_missing_class_leaves_type_evidence_unknown) {
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
SUITE(edge_imports) {
|
||||
RUN_TEST(ei_relative_astro_css_imports_issue2558);
|
||||
|
||||
/* ── GREEN GUARDS — Python (must stay passing) ── */
|
||||
RUN_TEST(ei_python_relative_from_import);
|
||||
RUN_TEST(ei_python_absolute_import);
|
||||
|
||||
Reference in New Issue
Block a user