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:
harshitaajoshi committed 2026-10-25 15:43:31 -05:00
1 parent 31ba586de4
commit 2d06fe300a
2 files changed
+93 -34

No files matched your search

+46 -34
View File
@@ -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];
+47
View File
@@ -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);