fix(build): recognize exact Hemlate derives
Parse derive paths structurally so qualified Hemlate derives provide template context while similarly named derives fail closed. Prove hyphen/underscore template type mapping, empty and non-UTF-8 paths, Cargo-root lookup, Rust source failures, and derived field facts through the public inspection entry point. req: diagnostics/006 req: surface/008 req: test/021
This commit is contained in:
+53
-24
@@ -1868,18 +1868,10 @@ fn reject_unkeyed_loop(
|
||||
fn context_type_for_heml_path(path: &Path) -> Option<String> {
|
||||
let stem = path.file_stem()?.to_str()?;
|
||||
let mut out = String::new();
|
||||
let mut upper_next = true;
|
||||
for ch in stem.chars() {
|
||||
if ch == '_' || ch == '-' {
|
||||
upper_next = true;
|
||||
continue;
|
||||
}
|
||||
if upper_next {
|
||||
out.extend(ch.to_uppercase());
|
||||
upper_next = false;
|
||||
} else {
|
||||
out.push(ch);
|
||||
}
|
||||
for word in stem.split(['_', '-']).filter(|word| !word.is_empty()) {
|
||||
let mut chars = word.chars();
|
||||
out.extend(chars.next()?.to_uppercase());
|
||||
out.extend(chars);
|
||||
}
|
||||
(!out.is_empty()).then_some(out)
|
||||
}
|
||||
@@ -1966,17 +1958,21 @@ fn collect_rust_struct_facts_from_items(
|
||||
}
|
||||
|
||||
fn derives_hemplate(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| {
|
||||
attr.path()
|
||||
.segments
|
||||
.last()
|
||||
.is_some_and(|segment| segment.ident == "derive")
|
||||
&& attr
|
||||
.meta
|
||||
.require_list()
|
||||
.ok()
|
||||
.is_some_and(|list| list.tokens.to_string().contains("Hemplate"))
|
||||
})
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("derive"))
|
||||
.filter_map(|attr| {
|
||||
attr.parse_args_with(
|
||||
syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.flatten()
|
||||
.any(|path| {
|
||||
path.segments
|
||||
.last()
|
||||
.is_some_and(|segment| segment.ident == "Hemplate")
|
||||
})
|
||||
}
|
||||
|
||||
fn compact_tokens(tokens: &impl ToTokens) -> String {
|
||||
@@ -2306,7 +2302,7 @@ mod tests {
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
crate_root.join("src/lib.rs"),
|
||||
"#[derive(Hemplate)] struct Profile { title: String }\nstruct Plain { title: String }",
|
||||
"#[derive(Hemplate)] struct Profile { title: String }\n#[derive(Clone, hemplate::Hemplate)] struct ProfileCard { card_title: String }\n#[derive(NotHemplate)] struct ProfileDetails { hidden: String }\n#[derive(HemplateExtra)] struct Extra { hidden: String }\nstruct Plain { title: String }",
|
||||
)
|
||||
.unwrap();
|
||||
let profile = templates.join("profile.heml");
|
||||
@@ -2318,6 +2314,39 @@ mod tests {
|
||||
.expect("Hemlate-derived context facts");
|
||||
assert_eq!(facts.context_type, "Profile");
|
||||
assert_eq!(facts.self_fields[0].name, "title");
|
||||
|
||||
let card_facts = template_context_facts_for_heml_source(
|
||||
templates.join("profile-card.heml"),
|
||||
"<main>{{ self.card_title }}</main>".to_owned(),
|
||||
)
|
||||
.unwrap()
|
||||
.expect("qualified Hemlate derive");
|
||||
assert_eq!(card_facts.context_type, "ProfileCard");
|
||||
assert_eq!(card_facts.self_fields[0].name, "card_title");
|
||||
assert_eq!(
|
||||
template_context_facts_for_heml_source(
|
||||
templates.join("profile_details.heml"),
|
||||
"<main>{{ self.hidden }}</main>".to_owned(),
|
||||
)
|
||||
.unwrap(),
|
||||
None,
|
||||
"derive names containing Hemlate must not grant context authority"
|
||||
);
|
||||
assert_eq!(
|
||||
context_type_for_heml_path(Path::new("--profile__card--.heml")),
|
||||
Some("ProfileCard".into())
|
||||
);
|
||||
assert_eq!(context_type_for_heml_path(Path::new("---.heml")), None);
|
||||
assert_eq!(context_type_for_heml_path(Path::new("")), None);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
assert_eq!(
|
||||
context_type_for_heml_path(Path::new(std::ffi::OsStr::from_bytes(b"\xff.heml"))),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
std::fs::write(crate_root.join("src/broken.rs"), [0xff]).unwrap();
|
||||
assert_eq!(
|
||||
template_context_facts_for_heml_source(
|
||||
|
||||
Reference in New Issue
Block a user