mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(pipeline): never guess a same-named sibling for explicit ./ and ../ imports (#2797)
Signed-off-by: harshitaajoshi <j.harshitaa06@gmail.com>
This commit is contained in:
1 parent
31ba586de4
commit
2d06fe300a
2 files changed
+93
-34
No files matched your search
+46
-34
@@ -2037,6 +2037,49 @@ static const cbm_gbuf_node_t *resolve_header_include(const cbm_pipeline_ctx_t *c
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool explicit_relative_path(const char *p) {
|
||||
return strncmp(p, "./", PAIR_LEN) == 0 || strncmp(p, "../", PAIR_LEN + SKIP_ONE) == 0;
|
||||
}
|
||||
|
||||
enum { SIBLING_MAX_CANDS = 4 };
|
||||
|
||||
/* The paths resolve_sibling_file tries for module_path, relative to the
|
||||
* importer's directory `dir`, in priority order. Returns how many it wrote. */
|
||||
static int sibling_file_candidates(const char *dir, const char *module_path,
|
||||
char cands[SIBLING_MAX_CANDS][PKGMAP_PATH_BUF]) {
|
||||
int ncand = 0;
|
||||
const char *sep = dir[0] ? "/" : "";
|
||||
const char *base = module_path;
|
||||
/* Skip a leading "./". */
|
||||
if (strncmp(base, "./", PAIR_LEN) == 0) {
|
||||
base += PAIR_LEN;
|
||||
}
|
||||
const char *slash = strrchr(base, '/');
|
||||
/* 1. Direct sibling: dir/<module_path>. */
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s", dir, sep, base);
|
||||
/* 2. SCSS partial: dir/[subdir/]_<basename>.scss (underscore-prefixed). */
|
||||
{
|
||||
const char *bn = slash ? slash + SKIP_ONE : base;
|
||||
char *dpart = slash ? cbm_strndup(base, (size_t)(slash - base)) : strdup("");
|
||||
if (dpart && bn[0] != '_') {
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s%s_%s.scss", dir, sep, dpart,
|
||||
dpart[0] ? "/" : "", bn);
|
||||
}
|
||||
free(dpart);
|
||||
}
|
||||
/* 3. Meson subdir: dir/<module_path>/meson.build. */
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s/meson.build", dir, sep, base);
|
||||
/* 4. Basename sibling: dir/<basename(module_path)>. Covers include paths
|
||||
* that carry a non-relative prefix (Hyprlang `source = ~/.config/.../x.conf`,
|
||||
* absolute include paths) but reference a file sitting beside the importer.
|
||||
* An explicit `./` or `../` path names one location, which Strategy 1c
|
||||
* resolves exactly; its basename beside the importer is another file (#2797). */
|
||||
if (slash && slash[SKIP_ONE] && !explicit_relative_path(module_path)) {
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s", dir, sep, slash + SKIP_ONE);
|
||||
}
|
||||
return ncand;
|
||||
}
|
||||
|
||||
/* Resolve a sibling-file import: a bare path/name (no leading "./") that names
|
||||
* a file relative to the importer's directory. This covers build/markup
|
||||
* grammars whose import string is a sibling filename or directory rather than a
|
||||
@@ -2063,38 +2106,8 @@ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Candidate relative paths, in priority order. */
|
||||
char cands[5][PKGMAP_PATH_BUF];
|
||||
int ncand = 0;
|
||||
const char *base = module_path;
|
||||
/* Skip a leading "./". */
|
||||
if (base[0] == '.' && base[1] == '/') {
|
||||
base += 2;
|
||||
}
|
||||
/* 1. Direct sibling: dir/<module_path>. */
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s", dir, dir[0] ? "/" : "", base);
|
||||
/* 2. SCSS partial: dir/[subdir/]_<basename>.scss (underscore-prefixed). */
|
||||
{
|
||||
const char *slash = strrchr(base, '/');
|
||||
const char *bn = slash ? slash + 1 : base;
|
||||
char *dpart = slash ? cbm_strndup(base, (size_t)(slash - base)) : strdup("");
|
||||
if (dpart && bn[0] != '_') {
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s%s_%s.scss", dir, dir[0] ? "/" : "",
|
||||
dpart[0] ? dpart : "", dpart[0] ? "/" : "", bn);
|
||||
}
|
||||
free(dpart);
|
||||
}
|
||||
/* 3. Meson subdir: dir/<module_path>/meson.build. */
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s/meson.build", dir, dir[0] ? "/" : "", base);
|
||||
/* 4. Basename sibling: dir/<basename(module_path)>. Covers include paths
|
||||
* that carry a non-relative prefix (Hyprlang `source = ~/.config/.../x.conf`,
|
||||
* absolute include paths) but reference a file sitting beside the importer. */
|
||||
{
|
||||
const char *slash = strrchr(base, '/');
|
||||
if (slash && slash[1]) {
|
||||
snprintf(cands[ncand++], PKGMAP_PATH_BUF, "%s%s%s", dir, dir[0] ? "/" : "", slash + 1);
|
||||
}
|
||||
}
|
||||
char cands[SIBLING_MAX_CANDS][PKGMAP_PATH_BUF];
|
||||
int ncand = sibling_file_candidates(dir, module_path, cands);
|
||||
|
||||
const cbm_gbuf_node_t *found = NULL;
|
||||
for (int i = 0; i < ncand; i++) {
|
||||
@@ -2123,8 +2136,7 @@ static const cbm_gbuf_node_t *resolve_sibling_file(const cbm_pipeline_ctx_t *ctx
|
||||
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] == '/'))) {
|
||||
if (!source_rel || !module_path || !explicit_relative_path(module_path)) {
|
||||
return NULL;
|
||||
}
|
||||
char dir[PKGMAP_PATH_BUF];
|
||||
|
||||
@@ -363,6 +363,51 @@ TEST(ei_relative_astro_css_imports_issue2558) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #2797: an explicit `../` path names one file. A same-named file beside the
|
||||
* importer (checks/values.nix) must not stand in for it, whether the named
|
||||
* file exists (lib/values.nix) or not (gone/values.nix). */
|
||||
TEST(ei_nix_parent_relative_import_issue2797) {
|
||||
static const EILangFile f[] = {
|
||||
{"lib/values.nix", "{ port = 1; }\n"},
|
||||
{"lib/helper.nix", "{ x }: { id = x; }\n"},
|
||||
{"checks/values.nix", "{ unrelated = true; }\n"},
|
||||
{"checks/user.nix", "let\n"
|
||||
" inherit (import ../lib/helper.nix { x = 1; }) id;\n"
|
||||
" port = (import ../lib/values.nix).port;\n"
|
||||
" gone = import ../gone/values.nix;\n"
|
||||
"in\n"
|
||||
"{ inherit id port; }\n"},
|
||||
{"top.nix", "{ v = (import ./lib/values.nix).port; }\n"},
|
||||
};
|
||||
static const EIFileEdgeExpect want[] = {
|
||||
{"checks/user.nix", "lib/helper.nix"},
|
||||
{"checks/user.nix", "lib/values.nix"},
|
||||
{"top.nix", "lib/values.nix"},
|
||||
};
|
||||
|
||||
ASSERT_TRUE(ei_file_edges_match(f, (int)(sizeof(f) / sizeof(f[0])), want,
|
||||
(int)(sizeof(want) / sizeof(want[0])), 3));
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #2797: the same basename guess applies to any grammar whose import is a
|
||||
* file path, not only Nix. */
|
||||
TEST(ei_thrift_parent_relative_import_issue2797) {
|
||||
static const EILangFile f[] = {
|
||||
{"shared/base.thrift", "struct Base { 1: i32 id }\n"},
|
||||
{"service/base.thrift", "struct Unrelated { 1: bool flag }\n"},
|
||||
{"service/api.thrift", "include \"../shared/base.thrift\"\n"
|
||||
"service Api { base.Base get() }\n"},
|
||||
};
|
||||
static const EIFileEdgeExpect want[] = {
|
||||
{"service/api.thrift", "shared/base.thrift"},
|
||||
};
|
||||
|
||||
ASSERT_TRUE(ei_file_edges_match(f, (int)(sizeof(f) / sizeof(f[0])), want,
|
||||
(int)(sizeof(want) / sizeof(want[0])), 1));
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* GREEN GUARD — Python
|
||||
*
|
||||
@@ -2396,6 +2441,8 @@ TEST(ei_php_psr4_missing_class_leaves_type_evidence_unknown) {
|
||||
|
||||
SUITE(edge_imports) {
|
||||
RUN_TEST(ei_relative_astro_css_imports_issue2558);
|
||||
RUN_TEST(ei_nix_parent_relative_import_issue2797);
|
||||
RUN_TEST(ei_thrift_parent_relative_import_issue2797);
|
||||
|
||||
/* ── GREEN GUARDS — Python (must stay passing) ── */
|
||||
RUN_TEST(ei_python_relative_from_import);
|
||||
|
||||
Reference in new issue
Block a user