use vize_carton::FxHashMap;
use vize_carton::FxHashSet;
use vize_carton::String;
use vize_carton::append;
use vize_carton::profile;
use vize_croquis::{Croquis, Scope, ScopeData, ScopeKind, analysis::ComponentUsage};
use crate::virtual_ts::expressions::generate_component_prop_checks;
use crate::virtual_ts::helpers::{to_camel_case, to_safe_identifier, to_safe_identifier_fragment};
use crate::virtual_ts::types::VizeMapping;
use super::context::{ComponentPropsContext, VForPropsContext};
use super::emit::{append_v_for_comment, emit_v_for_loop_open, slot_props_type};
pub(super) fn generate_component_props(
ts: &mut String,
mappings: &mut Vec<VizeMapping>,
ctx: &ComponentPropsContext<'_>,
) {
let summary = ctx.summary;
if summary.component_usages.is_empty() {
return;
}
let external_template_bindings: FxHashSet<&str> = ctx
.options
.external_template_bindings
.iter()
.map(|name| name.as_str())
.collect();
let checkable_usages: Vec<(usize, &ComponentUsage)> = summary
.component_usages
.iter()
.enumerate()
.filter(|(_, usage)| {
component_usage_has_checkable_binding(
summary,
usage,
&external_template_bindings,
ctx.check_unresolved_global_components,
)
})
.collect();
if checkable_usages.is_empty() {
return;
}
let mut components_by_scope: FxHashMap<u32, Vec<(usize, &ComponentUsage)>> =
FxHashMap::default();
for &(idx, usage) in &checkable_usages {
components_by_scope
.entry(usage.scope_id.as_u32())
.or_default()
.push((idx, usage));
}
ts.push_str("\n // Component props type declarations\n");
let any_dynamic_props = checkable_usages.iter().any(|(_, usage)| {
usage.props.iter().any(|p| {
p.name.as_str() != "key"
&& p.name.as_str() != "ref"
&& p.value.is_some()
&& p.is_dynamic
})
});
if any_dynamic_props {
ts.push_str(" type __VizeIsAny<T> = 0 extends (1 & T) ? true : false;\n");
ts.push_str(
" type __VizePropChecker<C> = __VizeIsAny<C> extends true ? (props: any) => void : C extends { __vizeCheck: infer __F } ? (__F extends (...args: any[]) => any ? __F : (props: any) => void) : (props: any) => void;\n",
);
ts.push_str(
" type __VizePropValue<P, K extends PropertyKey> = K extends keyof P ? P[K] : unknown;\n",
);
}
for &(idx, usage) in &checkable_usages {
let component_ref = to_safe_identifier(usage.name.as_str());
let component_type_name = to_safe_identifier_fragment(usage.name.as_str());
let has_dynamic_props = usage.props.iter().any(|p| {
p.name.as_str() != "key"
&& p.name.as_str() != "ref"
&& p.value.is_some()
&& p.is_dynamic
});
if !has_dynamic_props {
continue;
}
let src_start = (ctx.template_offset + usage.start) as usize;
let src_end = (ctx.template_offset + usage.end) as usize;
append!(*ts, " // @vize-map: component -> {src_start}:{src_end}\n",);
append!(
*ts,
" type __{component_type_name}_Props_{idx} = typeof {component_ref} extends {{ new (): {{ $props: infer __P }} }} ? __P : (typeof {component_ref} extends (props: infer __P) => any ? __P : {{}});\n",
);
for prop in &usage.props {
if prop.name.as_str() == "key" || prop.name.as_str() == "ref" {
continue;
}
if prop.value.is_some() && prop.is_dynamic {
let camel_prop_name = to_camel_case(prop.name.as_str());
let safe_prop_name = to_safe_identifier_fragment(prop.name.as_str());
append!(
*ts,
" type __{component_type_name}_{idx}_prop_{safe_prop_name} = __VizePropValue<__{component_type_name}_Props_{idx}, '{camel_prop_name}'>;\n",
);
}
}
append!(
*ts,
" type __{component_type_name}_Check_{idx} = __VizePropChecker<typeof {component_ref}>;\n",
);
}
let closure_scope_ids: FxHashSet<u32> = summary
.scopes
.iter()
.filter(|s| matches!(s.kind, ScopeKind::VFor | ScopeKind::VSlot))
.map(|s| s.id.as_u32())
.collect();
let root_closure_scope_ids: FxHashSet<u32> = summary
.scopes
.iter()
.filter(|s| {
matches!(s.kind, ScopeKind::VFor | ScopeKind::VSlot)
&& s.parent().is_none_or(|pid| {
summary
.scopes
.get_scope(pid)
.is_none_or(|p| !matches!(p.kind, ScopeKind::VFor | ScopeKind::VSlot))
})
})
.map(|s| s.id.as_u32())
.collect();
ts.push_str("\n // Component props value checks (template scope)\n");
for &(idx, usage) in &checkable_usages {
if closure_scope_ids.contains(&usage.scope_id.as_u32()) {
continue; }
profile!(
"canon.virtual_ts.component_prop_checks",
generate_component_prop_checks(
ts,
mappings,
usage,
idx,
ctx.template_prop_names,
ctx.template_offset,
" "
)
);
}
for scope in summary.scopes.iter() {
if !matches!(scope.kind, ScopeKind::VFor | ScopeKind::VSlot) {
continue;
}
if !root_closure_scope_ids.contains(&scope.id.as_u32()) {
continue;
}
let props_ctx = VForPropsContext {
summary,
components_by_scope: &components_by_scope,
children_map: ctx.children_map,
template_prop_names: ctx.template_prop_names,
template_offset: ctx.template_offset,
};
profile!(
"canon.virtual_ts.closure_component_props",
generate_closure_component_props_recursive(ts, mappings, &props_ctx, scope, " ")
);
}
}
fn component_usage_has_checkable_binding(
summary: &Croquis,
usage: &ComponentUsage,
external_template_bindings: &FxHashSet<&str>,
check_unresolved_global_components: bool,
) -> bool {
let name = usage.name.as_str();
summary.bindings.bindings.contains_key(name)
|| external_template_bindings.contains(name)
|| (check_unresolved_global_components && !name.is_empty())
}
fn generate_closure_component_props_recursive(
ts: &mut String,
mappings: &mut Vec<VizeMapping>,
ctx: &VForPropsContext<'_>,
scope: &Scope,
indent: &str,
) {
let scope_id = scope.id.as_u32();
let inner_indent = vize_carton::cstr!("{indent} ");
match scope.data() {
ScopeData::VFor(data) => {
append_v_for_comment(
ts,
indent,
"Component props in v-for scope",
data.value_alias.as_str(),
data.source.as_str(),
);
emit_v_for_loop_open(
ts,
indent,
data.value_alias.as_str(),
data.key_alias.as_deref(),
data.index_alias.as_deref(),
data.source.as_str(),
);
for value in &data.value_bindings {
append!(*ts, "{inner_indent}void {value};\n");
}
if let Some(ref key) = data.key_alias {
append!(*ts, "{inner_indent}void {key};\n");
}
if let Some(ref index) = data.index_alias {
append!(*ts, "{inner_indent}void {index};\n");
}
if let Some(usages) = ctx.components_by_scope.get(&scope_id) {
for &(idx, usage) in usages {
profile!(
"canon.virtual_ts.component_prop_checks",
generate_component_prop_checks(
ts,
mappings,
usage,
idx,
ctx.template_prop_names,
ctx.template_offset,
&inner_indent,
)
);
}
}
if let Some(child_ids) = ctx.children_map.get(&scope_id) {
for &child_id in child_ids {
if let Some(child_scope) = ctx.summary.scopes.get_scope(child_id)
&& matches!(child_scope.kind, ScopeKind::VFor | ScopeKind::VSlot)
{
profile!(
"canon.virtual_ts.closure_component_props",
generate_closure_component_props_recursive(
ts,
mappings,
ctx,
child_scope,
&inner_indent,
)
);
}
}
}
ts.push_str(indent);
ts.push_str("});\n");
}
ScopeData::VSlot(data) => {
let props_pattern = data.props_pattern.as_deref().unwrap_or("slotProps");
let safe_slot_name = to_safe_identifier_fragment(data.name.as_str());
let props_type = slot_props_type(
data.component.as_deref(),
data.name.as_str(),
ctx.summary.scopes.is_v_slot_name_static(scope.id),
);
append!(
*ts,
"\n{indent}// Component props in v-slot scope: #{}\n",
data.name
);
append!(
*ts,
"{indent}void function _slot_props_{safe_slot_name}({props_pattern}: {props_type}) {{\n",
);
if data.prop_names.is_empty() {
append!(*ts, "{inner_indent}void {props_pattern};\n");
} else {
for prop_name in data.prop_names.iter() {
append!(*ts, "{inner_indent}void {prop_name};\n");
}
}
if let Some(usages) = ctx.components_by_scope.get(&scope_id) {
for &(idx, usage) in usages {
profile!(
"canon.virtual_ts.component_prop_checks",
generate_component_prop_checks(
ts,
mappings,
usage,
idx,
ctx.template_prop_names,
ctx.template_offset,
&inner_indent,
)
);
}
}
if let Some(child_ids) = ctx.children_map.get(&scope_id) {
for &child_id in child_ids {
if let Some(child_scope) = ctx.summary.scopes.get_scope(child_id)
&& matches!(child_scope.kind, ScopeKind::VFor | ScopeKind::VSlot)
{
profile!(
"canon.virtual_ts.closure_component_props",
generate_closure_component_props_recursive(
ts,
mappings,
ctx,
child_scope,
&inner_indent,
)
);
}
}
}
ts.push_str(indent);
ts.push_str("};\n");
}
_ => {}
}
}