use super::PhpBackend;
use crate::core::backend::Backend;
use crate::core::config::{NewAlefConfig, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, FieldDef, FunctionDef, TypeDef, TypeRef};
const FEATURE: &str = "alpha";
fn php_config_with_feature(feature: &str) -> ResolvedCrateConfig {
let toml_src = format!(
"[workspace]\nlanguages = [\"php\"]\n[[crates]]\nname = \"test-lib\"\nsources = [\"src/lib.rs\"]\n\
[crates.php]\nfeatures = [\"{feature}\"]\n"
);
let cfg: NewAlefConfig = toml::from_str(&toml_src).expect("valid config");
cfg.resolve().expect("resolve config").remove(0)
}
fn shared_feature_api() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
types: vec![TypeDef {
name: "Config".to_string(),
rust_path: "test_lib::Config".to_string(),
fields: vec![
FieldDef {
name: "always_on".to_string(),
ty: TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool),
..Default::default()
},
FieldDef {
name: "alpha_option".to_string(),
ty: TypeRef::String,
cfg: Some(format!(r#"feature = "{FEATURE}""#)),
..Default::default()
},
],
..Default::default()
}],
functions: vec![FunctionDef {
name: "alpha_reader".to_string(),
rust_path: "test_lib::alpha_reader".to_string(),
return_type: TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool),
cfg: Some(format!(r#"feature = "{FEATURE}""#)),
..Default::default()
}],
..Default::default()
}
}
fn cargo_toml_content(files: &[crate::core::backend::GeneratedFile]) -> &str {
&files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("Cargo.toml"))
.expect("scaffold_php_cargo must emit Cargo.toml")
.content
}
fn lib_rs_content(files: &[crate::core::backend::GeneratedFile]) -> &str {
&files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("lib.rs"))
.expect("generate_bindings must emit lib.rs")
.content
}
#[test]
fn php_declared_features_keeps_a_name_a_field_still_needs() {
let api = shared_feature_api();
let declared = crate::scaffold::languages::php::php_declared_features(&api, &[]);
assert!(
declared.contains(FEATURE),
"a feature name a struct field's cfg references must stay declared even when a \
top-level function also references it; got {declared:?}"
);
}
#[test]
fn php_declared_features_still_drops_a_function_only_name() {
let mut api = shared_feature_api();
api.types.clear();
let declared = crate::scaffold::languages::php::php_declared_features(&api, &[]);
assert!(
!declared.contains(FEATURE),
"a feature referenced only by a top-level function's cfg (no field/type anywhere \
references it) must stay out of the declared set; got {declared:?}"
);
}
#[test]
fn generated_field_cfg_gate_names_a_feature_declared_in_the_same_crate() {
let api = shared_feature_api();
let config = php_config_with_feature(FEATURE);
let cargo_files =
crate::scaffold::languages::php::scaffold_php_cargo(&api, &config).expect("scaffold_php_cargo ok");
let cargo_toml = cargo_toml_content(&cargo_files);
let bindings = PhpBackend
.generate_bindings(&api, &config)
.expect("generate_bindings ok");
let lib_rs = lib_rs_content(&bindings);
let expected_gate = format!(r#"#[cfg(feature = "{FEATURE}")]"#);
assert!(
lib_rs.contains(&expected_gate),
"expected the field conversion to still carry its cfg gate:\n{lib_rs}"
);
let declared_line = format!(r#"{FEATURE} = ["test-lib/{FEATURE}"]"#);
assert!(
cargo_toml.contains(&declared_line),
"a struct field's cfg gate named `{FEATURE}` in the generated conversion, so the PHP \
crate's own [features] table must declare it (`{declared_line}`); got:\n{cargo_toml}"
);
let default_line = cargo_toml
.lines()
.find(|line| line.starts_with("default = ["))
.expect("default array present");
assert!(
default_line.contains(FEATURE),
"`{FEATURE}` is configured for this binding, so it must also be enabled by default; \
got: {default_line}"
);
}