@@ -1383,12 +1383,13 @@ static void parse_spec_imports(CBMExtractCtx *ctx) {
13831383 ts_tree_cursor_delete (& cursor );
13841384}
13851385
1386- // --- Embedded-language imports ---
1387- // Generic walker for host grammars (Svelte, Vue, HTML, Astro, ...) whose AST
1388- // keeps embedded sub-language source as raw_text (or similar) without parsing
1389- // it. The host's CBMLangSpec.embedded_imports declares which content nodes
1390- // hold which sub-language; we re-parse each match with the embedded grammar
1391- // and run the standard ES import walker over the inner AST.
1386+ // --- Embedded-language structure and imports ---
1387+ // Generic walker for host grammars (Svelte, Vue, HTML, Astro, CFML, ...) whose
1388+ // AST keeps embedded sub-language source as raw_text (or similar) without
1389+ // parsing it. The host's CBMLangSpec.embedded_imports declares which content
1390+ // nodes hold which sub-language; we re-parse each match with the embedded
1391+ // grammar and run the definition, import and call extractors over the inner
1392+ // AST, so a component's script contributes symbols like a standalone file.
13921393//
13931394// No grammar symbols are referenced here — the embedded TSLanguage is
13941395// resolved through cbm_ts_language(spec->embedded_language), the same hook
@@ -1458,66 +1459,94 @@ static bool ascii_trimmed_equals(const char *value, const char *expected) {
14581459 return true;
14591460}
14601461
1461- /* Vue is the only host that opts into structural embedded extraction. Restrict
1462- * its inline programs to the language forms whose parsers we can select
1463- * exactly. A src attribute always denotes an external program and therefore
1464- * suppresses any inline extraction, even for malformed mixed markup. */
1465- static bool vue_embedded_language (CBMExtractCtx * ctx , TSNode script , CBMLanguage * language ) {
1466- enum { VUE_ATTR_STACK_CAP = 128 };
1467- TSNode stack [VUE_ATTR_STACK_CAP ];
1462+ /* The text of a <script> attribute's value, or "" when it carries none
1463+ * (`<script type>`), so every attribute reads through one path. */
1464+ static const char * script_attribute_value (CBMExtractCtx * ctx , TSNode attribute ) {
1465+ TSNode value_node = attribute ;
1466+ if (!find_first_descendant_of (attribute , "attribute_value" , & value_node )) {
1467+ return "" ;
1468+ }
1469+ return cbm_node_text (ctx -> arena , value_node , ctx -> source );
1470+ }
1471+
1472+ /* Apply one <script> attribute to the parse decision. Returns false when the
1473+ * attribute rules the block out of inline extraction:
1474+ * src= always names an external program, so nothing inline is extracted,
1475+ * even for malformed mixed markup;
1476+ * lang= selects the JavaScript or TypeScript grammar; any other language
1477+ * has no parser here;
1478+ * type= plain HTML routinely carries non-program blocks (application/json,
1479+ * importmap, text/x-template, application/ld+json) that would parse
1480+ * into garbage definitions and calls. Only the JavaScript forms in
1481+ * current use pass — absent or empty, module, text/javascript,
1482+ * application/javascript — and the rest bail. */
1483+ static bool apply_script_attribute (CBMExtractCtx * ctx , TSNode attribute , CBMLanguage * language ) {
1484+ if (ts_node_named_child_count (attribute ) == 0 ) {
1485+ return false;
1486+ }
1487+ const char * name = cbm_node_text (ctx -> arena , ts_node_named_child (attribute , 0 ), ctx -> source );
1488+ if (ascii_trimmed_equals (name , "src" )) {
1489+ return false;
1490+ }
1491+ if (ascii_trimmed_equals (name , "lang" )) {
1492+ const char * value = script_attribute_value (ctx , attribute );
1493+ if (ascii_trimmed_equals (value , "js" ) || ascii_trimmed_equals (value , "javascript" )) {
1494+ * language = CBM_LANG_JAVASCRIPT ;
1495+ return true;
1496+ }
1497+ if (ascii_trimmed_equals (value , "ts" ) || ascii_trimmed_equals (value , "typescript" )) {
1498+ * language = CBM_LANG_TYPESCRIPT ;
1499+ return true;
1500+ }
1501+ return false;
1502+ }
1503+ if (ascii_trimmed_equals (name , "type" )) {
1504+ const char * value = script_attribute_value (ctx , attribute );
1505+ return ascii_trimmed_equals (value , "" ) || ascii_trimmed_equals (value , "module" ) ||
1506+ ascii_trimmed_equals (value , "text/javascript" ) ||
1507+ ascii_trimmed_equals (value , "application/javascript" );
1508+ }
1509+ return true;
1510+ }
1511+
1512+ /* Markup hosts (Vue, Svelte, HTML, Astro) hang the attributes that decide
1513+ * whether and how an inline program is parsed off the <script> start tag.
1514+ * *language enters holding the spec row's default and leaves holding the
1515+ * grammar to parse with; an attribute-free block (Astro's frontmatter fence)
1516+ * keeps the row's language rather than falling through to JavaScript. */
1517+ static bool script_embedded_language (CBMExtractCtx * ctx , TSNode script , CBMLanguage * language ) {
1518+ enum { SCRIPT_ATTR_STACK_CAP = 128 };
1519+ TSNode stack [SCRIPT_ATTR_STACK_CAP ];
14681520 int top = 0 ;
1469- bool has_lang = false;
1470- bool lang_supported = true;
1471- CBMLanguage selected = CBM_LANG_JAVASCRIPT ;
14721521 stack [top ++ ] = script ;
14731522 while (top > 0 ) {
14741523 TSNode node = stack [-- top ];
14751524 if (strcmp (ts_node_type (node ), "attribute" ) == 0 ) {
1476- uint32_t named_count = ts_node_named_child_count (node );
1477- if (named_count == 0 ) {
1478- return false;
1479- }
1480- TSNode name_node = ts_node_named_child (node , 0 );
1481- char * name = cbm_node_text (ctx -> arena , name_node , ctx -> source );
1482- if (ascii_trimmed_equals (name , "src" )) {
1525+ if (!apply_script_attribute (ctx , node , language )) {
14831526 return false;
14841527 }
1485- if (ascii_trimmed_equals (name , "lang" )) {
1486- TSNode value_node = node ;
1487- if (!find_first_descendant_of (node , "attribute_value" , & value_node )) {
1488- return false;
1489- }
1490- char * value = cbm_node_text (ctx -> arena , value_node , ctx -> source );
1491- has_lang = true;
1492- if (ascii_trimmed_equals (value , "js" ) ||
1493- ascii_trimmed_equals (value , "javascript" )) {
1494- selected = CBM_LANG_JAVASCRIPT ;
1495- } else if (ascii_trimmed_equals (value , "ts" ) ||
1496- ascii_trimmed_equals (value , "typescript" )) {
1497- selected = CBM_LANG_TYPESCRIPT ;
1498- } else {
1499- lang_supported = false;
1500- }
1501- }
15021528 continue ;
15031529 }
15041530 uint32_t count = ts_node_named_child_count (node );
1505- if ((int )count > VUE_ATTR_STACK_CAP - top ) {
1531+ if ((int )count > SCRIPT_ATTR_STACK_CAP - top ) {
15061532 return false;
15071533 }
15081534 for (int i = (int )count - 1 ; i >= 0 ; i -- ) {
15091535 stack [top ++ ] = ts_node_named_child (node , (uint32_t )i );
15101536 }
15111537 }
1512- if (has_lang && !lang_supported ) {
1513- return false;
1514- }
1515- * language = selected ;
15161538 return true;
15171539}
15181540
1541+ /* CFML's <cfscript> carries no attributes and its embedded language is fixed
1542+ * by the spec row; only the markup hosts consult the <script> tag. */
1543+ static bool markup_script_host (CBMLanguage language ) {
1544+ return language == CBM_LANG_VUE || language == CBM_LANG_SVELTE || language == CBM_LANG_HTML ||
1545+ language == CBM_LANG_ASTRO ;
1546+ }
1547+
15191548static void parse_one_embedded_block (CBMExtractCtx * ctx , const CBMEmbeddedBlock * block ,
1520- CBMLanguage embedded_language , bool extract_structure ) {
1549+ CBMLanguage embedded_language ) {
15211550 const TSLanguage * language = cbm_ts_language (embedded_language );
15221551 if (!language || ctx -> source_len < 0 ) {
15231552 return ;
@@ -1564,13 +1593,9 @@ static void parse_one_embedded_block(CBMExtractCtx *ctx, const CBMEmbeddedBlock
15641593 .macro_table = ctx -> macro_table ,
15651594 .return_type_table = ctx -> return_type_table ,
15661595 };
1567- if (extract_structure ) {
1568- cbm_extract_definitions_without_module (& sub_ctx );
1569- }
1596+ cbm_extract_definitions_without_module (& sub_ctx );
15701597 walk_es_imports (& sub_ctx , sub_ctx .root );
1571- if (extract_structure ) {
1572- cbm_extract_unified (& sub_ctx );
1573- }
1598+ cbm_extract_unified (& sub_ctx );
15741599
15751600 ts_tree_delete (tree );
15761601 ts_parser_delete (parser );
@@ -1588,22 +1613,11 @@ static void parse_embedded_imports(CBMExtractCtx *ctx) {
15881613 embedded_collect_content_nodes (ctx -> root , e , hits , & hit_count , MAX_EMBEDDED_BLOCKS );
15891614 for (int i = 0 ; i < hit_count ; i ++ ) {
15901615 CBMLanguage embedded_language = e -> embedded_language ;
1591- /* Structure (defs + calls), not just imports, for hosts whose
1592- * embedded language carries real code. Vue since #1852; CFML's
1593- * <cfscript> since the #1412 distillation. */
1594- bool extract_structure =
1595- ctx -> language == CBM_LANG_VUE || ctx -> language == CBM_LANG_CFML ;
1596- /* The attribute resolver is Vue's: it inspects <script lang=/src=>
1597- * and OVERRIDES the spec's embedded language (JS default, TS on
1598- * lang="ts", bail on src=). CFML's cf_script_tag carries no such
1599- * attributes and its embedded language is fixed by the spec row
1600- * (CFSCRIPT) — running the resolver would silently rewrite it to
1601- * JavaScript. Resolve only for Vue. */
1602- if (ctx -> language == CBM_LANG_VUE &&
1603- !vue_embedded_language (ctx , hits [i ].script , & embedded_language )) {
1616+ if (markup_script_host (ctx -> language ) &&
1617+ !script_embedded_language (ctx , hits [i ].script , & embedded_language )) {
16041618 continue ;
16051619 }
1606- parse_one_embedded_block (ctx , & hits [i ], embedded_language , extract_structure );
1620+ parse_one_embedded_block (ctx , & hits [i ], embedded_language );
16071621 }
16081622 }
16091623}
0 commit comments