@@ -931,79 +931,7 @@ impl Parser {
933 if let Some(rest) = content.strip_prefix("@include ") {
933 if let Some(rest) = content.strip_prefix("@include ") {
934− let filename = substitute_vars(rest.trim(), &ctx.variables);
935− let resolved = match &ctx.base_path {
936− Some(base) => base.join(&filename),
937− None => PathBuf::from(&filename),
940− // Circular include check
941− if ctx.include_stack.contains(&resolved) {
942− let cycle_chain = format_include_chain(&ctx.include_stack);
943− ctx.diagnostics.push(Diagnostic {
947− "circular include '{}' (cycle: {} → {})",
948− filename, cycle_chain, filename
950− severity: Severity::Error,
951− source_line: Some(content.clone()),
956− // Use file cache to avoid redundant reads
957− let included_text = if let Some(cached) = ctx.file_cache.get(&resolved) {
960− match std::fs::read_to_string(&resolved) {
962− ctx.file_cache.insert(resolved.clone(), text.clone());
966− ctx.diagnostics.push(Diagnostic {
969− message: format!("cannot include '{}': {}", filename, e),
970− severity: Severity::Error,
971− source_line: Some(content.clone()),
978− ctx.included_files.push(resolved.clone());
979− ctx.include_stack.push(resolved.clone());
980− let saved_base = ctx.base_path.clone();
981− ctx.base_path = resolved.parent().map(|p| p.to_path_buf());
983− let diag_count_before = ctx.diagnostics.len();
984− let included_lines = preprocess(&included_text);
985− let mut included_parser = Parser {
986− lines: included_lines,
989− let nodes = included_parser.parse_children(0, ctx);
991− // Annotate new diagnostics with include chain
992− let include_chain = format_include_chain(&ctx.include_stack);
993− for d in &mut ctx.diagnostics[diag_count_before..] {
994− d.message = format!("{}\n in {}", d.message, include_chain);
997− ctx.base_path = saved_base;
998− ctx.include_stack.pop();
999− return Ok(Some(nodes));
1002− // --- @import (definitions only, no DOM nodes) ---
1004− if let Some(rest) = content.strip_prefix("@import ") {
1005 let rest = rest.trim();
934 let rest = rest.trim();
1006− // Support @import "file.hl" as prefix — namespace imported definitions
1007 let (filename, alias) = if let Some((file_part, alias_part)) = rest.rsplit_once(" as ")
935 let (filename, alias) = if let Some((file_part, alias_part)) = rest.rsplit_once(" as ")
@@ -1077,13 +1005,14 @@ impl Parser {
1078 return Ok(None);
1006 return Ok(None);
1008+ let mut matched_nodes = Vec::new();
1080 for file_path in matched_files {
1009 for file_path in matched_files {
1081 let rel_name = file_path
1010 let rel_name = file_path
1082 .strip_prefix(&base_dir)
1011 .strip_prefix(&base_dir)
1083 .unwrap_or(&file_path)
1012 .unwrap_or(&file_path)
1084 .to_string_lossy()
1013 .to_string_lossy()
1085 .to_string();
1014 .to_string();
1086− // Synthesize an @import line for each matched file
1015+ // Synthesize an @include line for each matched file
1087 let import_line = match &alias {
1016 let import_line = match &alias {
1088 Some(pfx) => {
1017 Some(pfx) => {
1089 let stem = file_path
1018 let stem = file_path
@@ -1091,18 +1020,18 @@ impl Parser {
1091 .unwrap_or_default()
1020 .unwrap_or_default()
1092 .to_string_lossy()
1021 .to_string_lossy()
1093 .to_string();
1022 .to_string();
1094− format!("@import \"{}\" as {}.{}", rel_name, pfx, stem)
1023+ format!("@include \"{}\" as {}.{}", rel_name, pfx, stem)
1096− None => format!("@import \"{}\"", rel_name),
1025+ None => format!("@include \"{}\"", rel_name),
1098 let synth_lines = preprocess(&import_line);
1027 let synth_lines = preprocess(&import_line);
1099 let mut synth_parser = Parser {
1028 let mut synth_parser = Parser {
1100 lines: synth_lines,
1029 lines: synth_lines,
1103− let _ = synth_parser.parse_children(0, ctx);
1032+ matched_nodes.extend(synth_parser.parse_children(0, ctx));
1105− return Ok(None);
1034+ return Ok(Some(matched_nodes));
1108 let resolved = match &ctx.base_path {
1037 let resolved = match &ctx.base_path {
@@ -1116,7 +1045,7 @@ impl Parser {
1116 line: line_num,
1045 line: line_num,
1117 column: None,
1046 column: None,
1118 message: format!(
1047 message: format!(
1119− "circular import '{}' (cycle: {} → {})",
1048+ "circular include '{}' (cycle: {} → {})",
1120 filename, cycle_chain, filename
1049 filename, cycle_chain, filename
1122 severity: Severity::Error,
1051 severity: Severity::Error,
@@ -1137,7 +1066,7 @@ impl Parser {
1137 ctx.diagnostics.push(Diagnostic {
1066 ctx.diagnostics.push(Diagnostic {
1138 line: line_num,
1067 line: line_num,
1139 column: None,
1068 column: None,
1140− message: format!("cannot import '{}': {}", filename, e),
1069+ message: format!("cannot include '{}': {}", filename, e),
1141 severity: Severity::Error,
1070 severity: Severity::Error,
1142 source_line: Some(content.clone()),
1071 source_line: Some(content.clone()),
@@ -1153,6 +1082,7 @@ impl Parser {
1153 ctx.base_path = resolved.parent().map(|p| p.to_path_buf());
1082 ctx.base_path = resolved.parent().map(|p| p.to_path_buf());
1155 let diag_count_before = ctx.diagnostics.len();
1084 let diag_count_before = ctx.diagnostics.len();
1085+ let mut included_nodes = Vec::new();
1157 if let Some(ref prefix) = alias {
1087 if let Some(ref prefix) = alias {
1158 // Snapshot just the *key sets* before parsing — much cheaper
1088 // Snapshot just the *key sets* before parsing — much cheaper
@@ -1209,13 +1139,12 @@ impl Parser {
1211 } else {
1141 } else {
1212− // Parse the file but discard DOM nodes — only keep definitions
1213− let imported_lines = preprocess(&imported_text);
1142+ let included_lines = preprocess(&imported_text);
1214− let mut imported_parser = Parser {
1143+ let mut included_parser = Parser {
1215− lines: imported_lines,
1144+ lines: included_lines,
1218− let _discarded_nodes = imported_parser.parse_children(0, ctx);
1147+ included_nodes = included_parser.parse_children(0, ctx);
1221 // Annotate new diagnostics with import chain
1150 // Annotate new diagnostics with import chain
@@ -1227,7 +1156,7 @@ impl Parser {
1227 ctx.base_path = saved_base;
1156 ctx.base_path = saved_base;
1228 ctx.include_stack.pop();
1157 ctx.include_stack.pop();
1229 ctx.import_stack.pop();
1158 ctx.import_stack.pop();
1230− return Ok(None); // No nodes emitted
1159+ return Ok(Some(included_nodes));
1233 // --- @env (compile-time environment variables) ---
1162 // --- @env (compile-time environment variables) ---
@@ -2714,7 +2643,6 @@ const KNOWN_DIRECTIVES: &[&str] = &[
2716 "include",
2645 "include",
2719 "keyframes",
2647 "keyframes",
@@ -2776,7 +2704,11 @@ const REMOVED_SYNTAX: &[(&str, &str)] = &[
2776 "@switch",
2704 "@switch",
2777 "use `@match`, with `@let __switch [...]` inside a case for its attributes",
2705 "use `@match`, with `@let __switch [...]` inside a case for its attributes",
2707+ ("@use", "use `@include`"),
2779− ("@use", "use `@import`"),
2709+ "@import",
2710+ "use `@include` (a file with only definitions emits no content)",
2780 ("@with", "use `@let alias $source`"),
2712 ("@with", "use `@let alias $source`"),
2782 "@layout",
2714 "@layout",