From 24f54c0eb64c516941abee2ad0d7de758b34ac86 Mon Sep 17 00:00:00 2001 From: Brian McNaboe Date: Thu, 3 Sep 2026 16:24:29 -0400 Subject: [PATCH] fix(extract): extract structure from Svelte/HTML/Astro inline scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Vue change (#1852) built the shared embedded-script seam — the included-ranges parse over the original buffer and the lang=/src= resolver — but gated structural extraction on the host being Vue. A Svelte component, an HTML page or an Astro file therefore still yielded only File + Module nodes and IMPORTS edges from its \n" + "\n"}, + }; + ASSERT_EQ(es_exact_edge_by_name(f, 1, "CALLS", "caller", "callee"), 1); + PASS(); +} + /* Java: caller in Main.java calls static method from Util.java (same package). */ TEST(es_calls_crossfile_java) { static const ES_LangFile f[] = { @@ -927,6 +940,7 @@ SUITE(edge_structural) { RUN_TEST(es_calls_crossfile_kotlin); RUN_TEST(es_calls_crossfile_csharp); RUN_TEST(es_calls_vue_embedded_issue1410); + RUN_TEST(es_calls_svelte_embedded_issue1807); /* ── FAMILY 2: INHERITS cross-file ────────────────────────── */ /* GREEN: Java, C#, C++ (extraction confirmed correct). */ diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 7ea702c3a..fe36d1509 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -3345,7 +3345,10 @@ TEST(vue_embedded_structure_negative_controls_issue1410) { PASS(); } -TEST(vue_embedded_structure_host_controls_issue1410) { +/* The sibling hosts ride Vue's embedded seam: the function and call a .ts file + * yields come out of a plain \n"}, - {CBM_LANG_HTML, "control.html", - "\n"}, - {CBM_LANG_ASTRO, "Control.astro", - "---\nimport value from './astro.js'; function hidden() { target(); }\n---\n"}, + {CBM_LANG_SVELTE, "Sibling.svelte", + "\n"}, + {CBM_LANG_HTML, "sibling.html", + "\n"}, + {CBM_LANG_ASTRO, "Sibling.astro", + "---\nimport value from './astro.js'; function visible() { target(); }\n---\n"}, }; for (int i = 0; i < 3; i++) { CBMFileResult *r = extract(hosts[i].source, hosts[i].language, "t", hosts[i].path); ASSERT_NOT_NULL(r); ASSERT_FALSE(r->has_error); ASSERT_EQ(count_defs_with_label(r, "Module"), 1); + ASSERT_EQ(count_defs_named(r, "Function", "visible"), 1); + ASSERT_EQ(count_calls_named(r, "target"), 1); + ASSERT_EQ(r->imports.count, 1); + cbm_free_result(r); + } + PASS(); +} + +/* Blocks that must never yield inline symbols, whatever the host: an external + * program (src=) and a non-JavaScript MIME type. The bodies are deliberately + * code, so a leak would surface as a definition and a call. HTML's own tag + * walker still records the src= reference as an import; that edge names the + * external file and is not the inline body leaking through. */ +TEST(embedded_structure_inert_blocks_issue1807) { + static const struct { + CBMLanguage language; + const char *path; + const char *source; + int imports; + } blocks[] = { + {CBM_LANG_VUE, "Inert.vue", + "\n", 0}, + {CBM_LANG_VUE, "Inert.vue", + "\n", 0}, + {CBM_LANG_SVELTE, "Inert.svelte", + "\n", 0}, + {CBM_LANG_SVELTE, "Inert.svelte", + "\n", 0}, + {CBM_LANG_HTML, "inert.html", + "\n", 0}, + {CBM_LANG_HTML, "inert.html", + "\n", 0}, + {CBM_LANG_HTML, "inert.html", + "\n", 0}, + {CBM_LANG_HTML, "inert.html", + "\n", 1}, + {CBM_LANG_ASTRO, "Inert.astro", + "\n", 0}, + {CBM_LANG_ASTRO, "Inert.astro", + "\n", 0}, + }; + for (int i = 0; i < 10; i++) { + CBMFileResult *r = extract(blocks[i].source, blocks[i].language, "t", blocks[i].path); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_EQ(count_defs_with_label(r, "Module"), 1); ASSERT_EQ(count_defs_with_label(r, "Function"), 0); ASSERT_EQ(r->calls.count, 0); + ASSERT_EQ(r->imports.count, blocks[i].imports); + cbm_free_result(r); + } + PASS(); +} + +/* Svelte's module-level block (\n" + "\n" + "\n", + "\n" + "\n" + "\n", + }; + for (int i = 0; i < 2; i++) { + CBMFileResult *r = extract(sources[i], CBM_LANG_SVELTE, "t", "Widget.svelte"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_EQ(count_defs_with_label(r, "Function"), 2); + ASSERT_EQ(count_defs_named(r, "Function", "fromModule"), 1); + ASSERT_EQ(count_defs_named(r, "Function", "fromInstance"), 1); + ASSERT_EQ(count_calls_named(r, "shared"), 2); + ASSERT_EQ(count_calls_named(r, "fromModule"), 1); ASSERT_EQ(r->imports.count, 1); + ASSERT(has_import(r, "shared")); + for (int d = 0; d < r->defs.count; d++) { + const CBMDefinition *def = &r->defs.items[d]; + if (strcmp(def->name, "fromModule") == 0) { + ASSERT_EQ(def->start_line, 2); + } else if (strcmp(def->name, "fromInstance") == 0) { + ASSERT_EQ(def->start_line, 6); + } + } cbm_free_result(r); } PASS(); } +/* Every type= form HTML itself runs as JavaScript contributes, in host-file + * coordinates; a data block on the same page does not. */ +TEST(html_embedded_structure_issue1807) { + CBMFileResult *r = + extract("\n" + "\n" + "\n" + "\n" + "\n" + "\n", + CBM_LANG_HTML, "t", "index.html"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_EQ(count_defs_with_label(r, "Function"), 3); + ASSERT_EQ(count_calls_named(r, "renderApp"), 1); + ASSERT_EQ(count_calls_named(r, "boot"), 1); + ASSERT_EQ(count_calls_named(r, "legacy"), 1); + ASSERT_EQ(r->imports.count, 1); + for (int i = 0; i < r->defs.count; i++) { + const CBMDefinition *d = &r->defs.items[i]; + if (strcmp(d->name, "boot") == 0) { + ASSERT_EQ(d->start_line, 4); + } else if (strcmp(d->name, "legacy") == 0) { + ASSERT_EQ(d->start_line, 7); + } else if (strcmp(d->name, "fallback") == 0) { + ASSERT_EQ(d->start_line, 10); + } + } + cbm_free_result(r); + PASS(); +} + +/* Astro's frontmatter fence and \n", + CBM_LANG_ASTRO, "t", "Page.astro"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_def_any(r, "Props")); + ASSERT_EQ(count_defs_named(r, "Function", "heading"), 1); + ASSERT_EQ(count_defs_named(r, "Function", "hydrate"), 1); + ASSERT_EQ(count_calls_named(r, "format"), 1); + ASSERT_EQ(count_calls_named(r, "heading"), 1); + ASSERT_EQ(r->imports.count, 1); + ASSERT(has_import(r, "Header.astro")); + for (int i = 0; i < r->defs.count; i++) { + const CBMDefinition *d = &r->defs.items[i]; + if (strcmp(d->name, "heading") == 0) { + ASSERT_EQ(d->start_line, 5); + } else if (strcmp(d->name, "hydrate") == 0) { + ASSERT_EQ(d->start_line, 9); + } + } + cbm_free_result(r); + PASS(); +} + TEST(html_imports_basic) { /* Plain HTML with inline ES module imports — same generic walker. */ CBMFileResult *r = extract("\n" @@ -7162,7 +7332,11 @@ SUITE(extraction) { RUN_TEST(vue_imports_basic); RUN_TEST(vue_embedded_structure_issue1410); RUN_TEST(vue_embedded_structure_negative_controls_issue1410); - RUN_TEST(vue_embedded_structure_host_controls_issue1410); + RUN_TEST(embedded_structure_sibling_hosts_issue1807); + RUN_TEST(embedded_structure_inert_blocks_issue1807); + RUN_TEST(svelte_embedded_structure_both_blocks_issue1807); + RUN_TEST(html_embedded_structure_issue1807); + RUN_TEST(astro_embedded_structure_issue1807); RUN_TEST(html_imports_basic); /* config_extraction_test.go ports */ diff --git a/tests/test_grammar_labels.c b/tests/test_grammar_labels.c index 4008b6d2c..7818c4c44 100644 --- a/tests/test_grammar_labels.c +++ b/tests/test_grammar_labels.c @@ -236,8 +236,10 @@ static const LabelGolden LABEL_GOLDENS[] = { {"liquid", "Module:1"}, {"blade", "Module:1"}, {"vue", "Module:1"}, - {"svelte", "Module:1"}, - {"astro", "Module:1"}, + /* Svelte's