From 3ea596a21890474ea8671888380acfaa23e07760 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 25 May 2026 21:20:18 +0200 Subject: [PATCH] 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 --- slhx-derive/build.rs | 4 ++ slhx-derive/src/lib.rs | 13 +++++- slhx-derive/tests/compile_fail.rs | 78 ++++++++++++++++++++++++------- slhx-derive/tests/surface.rs | 2 +- 4 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 slhx-derive/build.rs diff --git a/slhx-derive/build.rs b/slhx-derive/build.rs new file mode 100644 index 0000000..66220c6 --- /dev/null +++ b/slhx-derive/build.rs @@ -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(); +} diff --git a/slhx-derive/src/lib.rs b/slhx-derive/src/lib.rs index acfc806..42de439 100644 --- a/slhx-derive/src/lib.rs +++ b/slhx-derive/src/lib.rs @@ -66,13 +66,22 @@ fn inject_surface_include(item: TokenStream) -> TokenStream { fn surface_include() -> String { 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() { format!(" include!({:?}); ", path.display().to_string()) } 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() + ) + ) } } diff --git a/slhx-derive/tests/compile_fail.rs b/slhx-derive/tests/compile_fail.rs index 8bf03f9..c73a5f8 100644 --- a/slhx-derive/tests/compile_fail.rs +++ b/slhx-derive/tests/compile_fail.rs @@ -46,14 +46,7 @@ fn known() {} "#, ); - let 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"); + let output = check_fixture(&fixture); assert!(!output.status.success(), "fixture unexpectedly compiled"); 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] fn generated_resource_references_fail_when_name_is_absent() { // 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") - .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"); + let output = check_fixture(&fixture); assert!(!output.status.success(), "fixture unexpectedly compiled"); 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 { Path::new(env!("CARGO_MANIFEST_DIR")) .parent() diff --git a/slhx-derive/tests/surface.rs b/slhx-derive/tests/surface.rs index 306de01..01e8f6a 100644 --- a/slhx-derive/tests/surface.rs +++ b/slhx-derive/tests/surface.rs @@ -6,6 +6,6 @@ mod ui { } #[test] -fn surface_macro_preserves_inline_module_without_generated_file() { +fn surface_macro_preserves_inline_module_with_generated_file() { assert_eq!(ui::EXISTING, 1); }