fix(derive): fail missing surface generation

Make #[slhx::surface] report a compile-time error when slhx.generated.rs is absent instead of silently expanding to an empty module. Add compile-fail coverage for the missing generated include path.

req: build/004

req: test/003
This commit is contained in:
slhx agent
2026-05-25 21:20:18 +02:00
parent a6db4699f7
commit 3ea596a218
4 changed files with 78 additions and 19 deletions
+4
View File
@@ -0,0 +1,4 @@
fn main() {
let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
std::fs::write(out.join("slhx.generated.rs"), "").unwrap();
}
+11 -2
View File
@@ -66,13 +66,22 @@ fn inject_surface_include(item: TokenStream) -> TokenStream {
fn surface_include() -> String { fn surface_include() -> String {
let Some(path) = generated_path("slhx.generated.rs") else { let Some(path) = generated_path("slhx.generated.rs") else {
return String::new(); return format!(
" compile_error!({:?}); ",
"#[slhx::surface] requires OUT_DIR; run inside a Cargo crate with slhx_build::app() in build.rs"
);
}; };
if path.exists() { if path.exists() {
format!(" include!({:?}); ", path.display().to_string()) format!(" include!({:?}); ", path.display().to_string())
} else { } else {
String::new() format!(
" compile_error!({:?}); ",
format!(
"#[slhx::surface] could not find {}; add slhx_build::app().run()? to build.rs or check template generation",
path.display()
)
)
} }
} }
+62 -16
View File
@@ -46,14 +46,7 @@ fn known() {}
"#, "#,
); );
let output = Command::new("cargo") let output = check_fixture(&fixture);
.arg("check")
.arg("--quiet")
.arg("--manifest-path")
.arg(fixture.path.join("Cargo.toml"))
.env("CARGO_TARGET_DIR", fixture.path.join("target"))
.output()
.expect("cargo check fixture runs");
assert!(!output.status.success(), "fixture unexpectedly compiled"); assert!(!output.status.success(), "fixture unexpectedly compiled");
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
@@ -67,6 +60,55 @@ fn known() {}
); );
} }
#[test]
fn surface_macro_reports_missing_generated_include() {
// req: build/004 req: test/003
let fixture = Fixture::new("slhx-derive-surface-include-fail");
fixture.write(
"Cargo.toml",
&format!(
r#"[package]
name = "slhx-derive-surface-include-fail"
version = "0.0.0"
edition = "2021"
[lib]
path = "src/lib.rs"
[dependencies]
slhx = {{ path = {:?} }}
"#,
repo_path("slhx")
),
);
fixture.write(
"build.rs",
r#"fn main() {
println!("cargo:rerun-if-changed=build.rs");
}
"#,
);
fixture.write(
"src/lib.rs",
r#"#[slhx::surface]
pub mod ui {}
"#,
);
let output = check_fixture(&fixture);
assert!(!output.status.success(), "fixture unexpectedly compiled");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("#[slhx::surface] could not find"),
"missing surface include diagnostic in stderr:\n{stderr}"
);
assert!(
stderr.contains("add slhx_build::app().run()? to build.rs"),
"missing build.rs hint in stderr:\n{stderr}"
);
}
#[test] #[test]
fn generated_resource_references_fail_when_name_is_absent() { fn generated_resource_references_fail_when_name_is_absent() {
// req: style/002 req: codegen/001 req: test/003 // req: style/002 req: codegen/001 req: test/003
@@ -110,14 +152,7 @@ pub const MISSING: slhx::CssClass = ui::classes::missing;
"#, "#,
); );
let output = Command::new("cargo") let output = check_fixture(&fixture);
.arg("check")
.arg("--quiet")
.arg("--manifest-path")
.arg(fixture.path.join("Cargo.toml"))
.env("CARGO_TARGET_DIR", fixture.path.join("target"))
.output()
.expect("cargo check fixture runs");
assert!(!output.status.success(), "fixture unexpectedly compiled"); assert!(!output.status.success(), "fixture unexpectedly compiled");
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
@@ -127,6 +162,17 @@ pub const MISSING: slhx::CssClass = ui::classes::missing;
); );
} }
fn check_fixture(fixture: &Fixture) -> std::process::Output {
Command::new("cargo")
.arg("check")
.arg("--quiet")
.arg("--manifest-path")
.arg(fixture.path.join("Cargo.toml"))
.env("CARGO_TARGET_DIR", fixture.path.join("target"))
.output()
.expect("cargo check fixture runs")
}
fn repo_path(crate_name: &str) -> PathBuf { fn repo_path(crate_name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")) Path::new(env!("CARGO_MANIFEST_DIR"))
.parent() .parent()
+1 -1
View File
@@ -6,6 +6,6 @@ mod ui {
} }
#[test] #[test]
fn surface_macro_preserves_inline_module_without_generated_file() { fn surface_macro_preserves_inline_module_with_generated_file() {
assert_eq!(ui::EXISTING, 1); assert_eq!(ui::EXISTING, 1);
} }