Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion crates/vize_canon/src/virtual_ts/expressions/component_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use super::super::helpers::{to_camel_case, to_safe_identifier_fragment};
use super::super::types::{VizeMapping, VizeSubSpan};
use super::reserved_props::rewrite_reserved_template_prop;
use vize_carton::FxHashMap;
use vize_carton::FxHashSet;
use vize_carton::String;
use vize_carton::append;
Expand Down Expand Up @@ -132,6 +133,7 @@ pub(crate) fn generate_component_prop_checks(
indent: &str,
) {
let component_type_name = to_safe_identifier_fragment(usage.name.as_str());
let mut name_occurrences: FxHashMap<String, u32> = FxHashMap::default();
for prop in &usage.props {
if !is_checkable_prop(prop) {
continue;
Expand Down Expand Up @@ -163,7 +165,18 @@ pub(crate) fn generate_component_prop_checks(
append!(*ts, "{indent}if ({guard}) {{\n");
}

let check_name = cstr!("__vize_prop_check_{idx}_{safe_prop_name}");
// A repeated attribute name (static class next to :class) still
// checks every authored value, but each check constant needs a
// unique name or the virtual TS redeclares it (TS2451).
let occurrence = name_occurrences
.entry(String::from(safe_prop_name.as_str()))
.and_modify(|count| *count += 1)
.or_insert(1);
let check_name = if *occurrence == 1 {
cstr!("__vize_prop_check_{idx}_{safe_prop_name}")
} else {
cstr!("__vize_prop_check_{idx}_{safe_prop_name}_{occurrence}")
};
let gen_stmt_start = ts.len();
append!(*ts, "{expr_indent}const ");
let check_name_start = ts.len();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,53 @@ fn valueless_static_attributes_stay_out_of_per_prop_checks() {
output.code
);
}

#[test]
fn repeated_prop_names_keep_unique_checks_and_one_type_alias() {
let script = r#"import Child from "./Child.vue"
const isLoading = false
"#;
let template = r#"<Child class="static-card" :class="{ loading: isLoading }" />"#;

let allocator = vize_carton::Bump::new();
let (root, _) = vize_armature::parse(&allocator, template);
let mut analyzer = Analyzer::with_options(AnalyzerOptions::full());
analyzer.analyze_script_setup(script);
analyzer.analyze_template(&root);
let summary = analyzer.finish();

let output = generate_virtual_ts(&summary, Some(script), Some(&root), 0);

assert_eq!(
output
.code
.matches("type __Child_0_prop_class = __VizePropValue<__Child_Props_0, 'class'>;")
.count(),
1,
"the child prop type alias must be declared exactly once:\n{}",
output.code
);
assert!(
output
.code
.contains("const __vize_prop_check_0_class: __Child_0_prop_class = \"static-card\";"),
"the static class value keeps the base check name:\n{}",
output.code
);
assert!(
output.code.contains(
"const __vize_prop_check_0_class_2: __Child_0_prop_class = { loading: isLoading };"
),
"the bound class value gets a unique check name:\n{}",
output.code
);
assert_eq!(
output
.code
.matches("const __vize_prop_check_0_class")
.count(),
2,
"both authored values stay checked:\n{}",
output.code
);
}
7 changes: 7 additions & 0 deletions crates/vize_canon/src/virtual_ts/scope/component_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,20 @@ pub(super) fn generate_component_props(
" type __{component_type_name}_Props_{idx} = typeof {component_ref} extends {{ __vizeCheck: any }} ? Record<string, unknown> : (typeof {component_ref} extends {{ new (): {{ $props: infer __P }} }} ? __P : (typeof {component_ref} extends (props: infer __P) => any ? __P : {{}}));\n",
);

// One alias per distinct prop name: a repeated attribute (for example
// a static class next to a bound :class) reuses the same child prop
// type, and a duplicate alias would be a TS2300 in the virtual TS.
let mut declared_aliases = FxHashSet::default();
for prop in &usage.props {
if prop.name_is_dynamic || prop.name.as_str() == "key" || prop.name.as_str() == "ref" {
continue;
}
if prop.value.is_some() {
let camel_prop_name = to_camel_case(prop.name.as_str());
let safe_prop_name = to_safe_identifier_fragment(prop.name.as_str());
if !declared_aliases.insert(safe_prop_name.clone()) {
continue;
}
append!(
*ts,
" type __{component_type_name}_{idx}_prop_{safe_prop_name} = __VizePropValue<__{component_type_name}_Props_{idx}, '{camel_prop_name}'>;\n",
Expand Down
Loading