@@ -267,6 +267,102 @@ static int ei_edge_present(const EILangFile *files, int nfiles, const char *edge
267267 return got >= floor ;
268268}
269269
270+ typedef struct {
271+ const char * importer ;
272+ const char * target ;
273+ } EIFileEdgeExpect ;
274+
275+ static int ei_file_edges_match (const EILangFile * files , int nfiles , const EIFileEdgeExpect * want ,
276+ int nwant , int expected_count ) {
277+ EILangProj lp ;
278+ cbm_store_t * store = ei_index_files (& lp , files , nfiles );
279+ int ok = store && cbm_store_count_edges_by_type (store , lp .project , "IMPORTS" ) == expected_count ;
280+ if (store && !ok ) {
281+ int got = cbm_store_count_edges_by_type (store , lp .project , "IMPORTS" );
282+ fprintf (stderr , " [IMPORTS] FAIL count=%d expected==%d\n" , got , expected_count );
283+ }
284+ for (int i = 0 ; store && i < nwant ; i ++ ) {
285+ int64_t source_id = ei_node_id_for_file_label (store , lp .project , want [i ].importer , "File" );
286+ cbm_edge_t * edges = NULL ;
287+ int edge_count = 0 ;
288+ int found = 0 ;
289+ if (source_id > 0 &&
290+ cbm_store_find_edges_by_source_type (store , source_id , "IMPORTS" , & edges , & edge_count ) ==
291+ CBM_STORE_OK ) {
292+ for (int j = 0 ; j < edge_count ; j ++ ) {
293+ cbm_node_t target ;
294+ memset (& target , 0 , sizeof (target ));
295+ if (cbm_store_find_node_by_id (store , edges [j ].target_id , & target ) == CBM_STORE_OK ) {
296+ found = target .file_path && strcmp (target .file_path , want [i ].target ) == 0 ;
297+ cbm_node_free_fields (& target );
298+ }
299+ if (found ) {
300+ break ;
301+ }
302+ }
303+ }
304+ cbm_store_free_edges (edges , edge_count );
305+ if (!found ) {
306+ fprintf (stderr , " [IMPORTS] FAIL %s -> %s\n" , want [i ].importer , want [i ].target );
307+ ok = 0 ;
308+ }
309+ }
310+ ei_cleanup (& lp , store );
311+ return ok ;
312+ }
313+
314+ TEST (ei_relative_astro_css_imports_issue2558 ) {
315+ static const EILangFile f [] = {
316+ {"Badge.astro" , "<span>outside</span>\n" },
317+ {"src/components/shared/Button.astro" ,
318+ "---\nexport function buttonLabel(t: string): string { return t; }\n---\n<button />\n" },
319+ {"src/components/Badge.astro" , "<span>badge</span>\n" },
320+ {"src/components/Card.astro" ,
321+ "---\nimport Badge from './Badge.astro';\n"
322+ "import Button from './shared/Button.astro';\n---\n<div><Badge /><Button /></div>\n" },
323+ {"src/layouts/local.css" , "body { margin: 0; }\n" },
324+ {"src/styles/global.css" , "body { color: black; }\n" },
325+ {"src/layouts/Layout.astro" ,
326+ "---\nimport '../styles/global.css';\nimport './local.css';\n---\n"
327+ "<html><body><slot /></body></html>\n" },
328+ {"src/lib/format.ts" , "export function formatTitle(t: string): string { return t.trim(); }\n" },
329+ {"src/lib/registry.ts" ,
330+ "import Badge from '../components/Badge.astro';\n"
331+ "import '../styles/global.css';\n"
332+ "import { formatTitle } from './format';\n"
333+ "export function registry(): unknown[] { return [Badge, formatTitle]; }\n" },
334+ {"src/lib/sub/deep.ts" ,
335+ "import { formatTitle } from '../format';\n"
336+ "import { registry } from '../registry.ts';\n"
337+ "export function deep(): string { registry(); return formatTitle('x'); }\n" },
338+ {"src/pages/index.astro" ,
339+ "---\nimport Layout from '../layouts/Layout.astro';\n"
340+ "import Card from '../components/Card.astro';\n"
341+ "import Outside from '../../../Badge.astro';\n"
342+ "import { formatTitle } from '../lib/format';\n"
343+ "const title = formatTitle(' Home ');\n---\n"
344+ "<Layout><h1>{title}</h1><Card /></Layout>\n" },
345+ };
346+ static const EIFileEdgeExpect want [] = {
347+ {"src/components/Card.astro" , "src/components/Badge.astro" },
348+ {"src/components/Card.astro" , "src/components/shared/Button.astro" },
349+ {"src/layouts/Layout.astro" , "src/styles/global.css" },
350+ {"src/layouts/Layout.astro" , "src/layouts/local.css" },
351+ {"src/lib/registry.ts" , "src/components/Badge.astro" },
352+ {"src/lib/registry.ts" , "src/styles/global.css" },
353+ {"src/lib/registry.ts" , "src/lib/format.ts" },
354+ {"src/lib/sub/deep.ts" , "src/lib/format.ts" },
355+ {"src/lib/sub/deep.ts" , "src/lib/registry.ts" },
356+ {"src/pages/index.astro" , "src/layouts/Layout.astro" },
357+ {"src/pages/index.astro" , "src/components/Card.astro" },
358+ {"src/pages/index.astro" , "src/lib/format.ts" },
359+ };
360+
361+ ASSERT_TRUE (ei_file_edges_match (f , (int )(sizeof (f ) / sizeof (f [0 ])), want ,
362+ (int )(sizeof (want ) / sizeof (want [0 ])), 12 ));
363+ PASS ();
364+ }
365+
270366/* ═══════════════════════════════════════════════════════════════════════════
271367 * GREEN GUARD — Python
272368 *
@@ -2299,6 +2395,8 @@ TEST(ei_php_psr4_missing_class_leaves_type_evidence_unknown) {
22992395 * ═══════════════════════════════════════════════════════════════════════════ */
23002396
23012397SUITE (edge_imports ) {
2398+ RUN_TEST (ei_relative_astro_css_imports_issue2558 );
2399+
23022400 /* ── GREEN GUARDS — Python (must stay passing) ── */
23032401 RUN_TEST (ei_python_relative_from_import );
23042402 RUN_TEST (ei_python_absolute_import );
0 commit comments