use crate::css::CssTracker;
use crate::hoist::HoistTracker;
use crate::macros::MacroTracker;
use crate::optimization::OptimizationTracker;
use super::{
BlockDisplay, Croquis, CssDisplay, Diagnostic, EmitDisplay, EventCacheDisplay, HoistDisplay,
MacroDisplay, MemoCacheDisplay, OnceCacheDisplay, PropDisplay, SelectorDisplay, Severity,
TopLevelAwaitDisplay,
};
use vize_carton::append;
use vize_carton::FxHashMap;
use vize_carton::String;
use vize_carton::ToCompactString;
#[derive(Debug, Default)]
pub struct SummaryBuilder {
summary: Croquis,
}
impl SummaryBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_macros(mut self, tracker: &MacroTracker) -> Self {
for call in tracker.all_calls() {
self.summary.macros.push(MacroDisplay {
name: call.name.to_compact_string(),
#[allow(clippy::disallowed_macros)]
kind: format!("{:?}", call.kind).into(),
start: call.start,
end: call.end,
});
}
for prop in tracker.props() {
self.summary.props.push(PropDisplay {
name: prop.name.to_compact_string(),
prop_type: prop.prop_type.as_ref().map(|s| s.to_compact_string()),
required: prop.required,
has_default: prop.default_value.is_some(),
});
}
for emit in tracker.emits() {
self.summary.emits.push(EmitDisplay {
name: emit.name.to_compact_string(),
payload_type: emit.payload_type.as_ref().map(|s| s.to_compact_string()),
});
}
self.summary.is_async = tracker.is_async();
for await_expr in tracker.top_level_awaits() {
self.summary.top_level_awaits.push(TopLevelAwaitDisplay {
expression: await_expr.expression.to_compact_string(),
start: await_expr.start,
end: await_expr.end,
});
}
self.summary.stats.prop_count = tracker.props().len() as u32;
self.summary.stats.emit_count = tracker.emits().len() as u32;
self.summary.stats.model_count = tracker.models().len() as u32;
self
}
pub fn with_optimization(mut self, tracker: &OptimizationTracker) -> Self {
for block in tracker.blocks() {
self.summary.optimization.blocks.push(BlockDisplay {
id: block.id,
#[allow(clippy::disallowed_macros)]
block_type: format!("{:?}", block.block_type).into(),
parent_id: block.parent_id,
dynamic_children: block.dynamic_children_count,
});
}
for event in tracker.event_cache() {
self.summary
.optimization
.event_cache
.push(EventCacheDisplay {
cache_index: event.cache_index,
event_name: event.event_name.to_compact_string(),
handler: event.handler.to_compact_string(),
is_inline: event.is_inline,
});
}
for once in tracker.once_cache() {
self.summary.optimization.once_cache.push(OnceCacheDisplay {
cache_index: once.cache_index,
content: once.content.to_compact_string(),
start: once.start,
end: once.end,
});
}
for memo in tracker.memo_cache() {
self.summary.optimization.memo_cache.push(MemoCacheDisplay {
cache_index: memo.cache_index,
deps: memo.deps.to_compact_string(),
content: memo.content.to_compact_string(),
start: memo.start,
end: memo.end,
});
}
self.summary.stats.cache_count = tracker.current_cache_index();
self
}
pub fn with_hoists(mut self, tracker: &HoistTracker) -> Self {
for hoist in tracker.hoists() {
self.summary.hoists.push(HoistDisplay {
id: hoist.id.as_u32(),
#[allow(clippy::disallowed_macros)]
level: format!("{:?}", hoist.level).into(),
content: hoist.content.to_compact_string(),
});
}
self.summary.stats.hoist_count = tracker.count() as u32;
self
}
pub fn with_css(mut self, tracker: &CssTracker) -> Self {
let stats = tracker.stats();
self.summary.css = Some(CssDisplay {
selectors: tracker
.selectors()
.iter()
.map(|s| SelectorDisplay {
raw: s.raw.to_compact_string(),
scoped: true, })
.collect(),
v_bind_count: stats.v_bind_count,
has_deep: stats.deep_selectors > 0,
has_slotted: stats.slotted_selectors > 0,
has_global: stats.global_selectors > 0,
});
self
}
pub fn add_diagnostic(mut self, diagnostic: Diagnostic) -> Self {
self.summary.diagnostics.push(diagnostic);
self
}
pub fn build(self) -> Croquis {
self.summary
}
}
impl Croquis {
pub fn to_vir(&self) -> String {
let mut output = String::with_capacity(4096);
output.push_str("[analysis]\n");
append!(output, "is_async = {}\n", self.is_async);
append!(output, "scope_count = {}\n", self.stats.scope_count);
append!(output, "binding_count = {}\n", self.stats.binding_count);
output.push('\n');
if !self.scopes.is_empty() {
output.push_str("[scopes]\n");
let mut prefix_counters: FxHashMap<&str, u32> = FxHashMap::default();
prefix_counters.insert("#", 0);
prefix_counters.insert("~", 0);
prefix_counters.insert("!", 0);
let mut display_id_map: FxHashMap<u32, (String, u32)> = FxHashMap::default();
for scope in &self.scopes {
let prefix = scope.kind.prefix();
let counter = prefix_counters.entry(prefix).or_insert(0);
let display_id = *counter;
*counter += 1;
display_id_map.insert(scope.id, (prefix.to_compact_string(), display_id));
}
for scope in &self.scopes {
let (prefix, display_id) = display_id_map.get(&scope.id).unwrap();
append!(
output,
"{prefix}{display_id} {} @{}:{}",
scope.kind.to_display(),
scope.start,
scope.end
);
if !scope.bindings.is_empty() {
output.push_str(" {");
let mut first = true;
for (name, _) in &scope.bindings {
if !first {
output.push_str(", ");
}
output.push_str(name);
first = false;
}
output.push('}');
}
if !scope.parent_ids.is_empty() {
output.push_str(" $ ");
let mut first = true;
for parent_id in &scope.parent_ids {
if !first {
output.push_str(", ");
}
if let Some((p_prefix, p_display_id)) = display_id_map.get(parent_id) {
append!(output, "{p_prefix}{p_display_id}");
} else {
append!(output, "#{parent_id}");
}
first = false;
}
}
output.push('\n');
}
output.push('\n');
}
if !self.props.is_empty() {
output.push_str("[props]\n");
for prop in &self.props {
append!(
output,
" {} = {{ type = {:?}, required = {} }}\n",
prop.name,
prop.prop_type.as_deref().unwrap_or("any"),
prop.required
);
}
output.push('\n');
}
if !self.emits.is_empty() {
output.push_str("[emits]\n");
for emit in &self.emits {
append!(output, " {} = {:?}\n", emit.name, emit.payload_type);
}
output.push('\n');
}
if !self.top_level_awaits.is_empty() {
output.push_str("[top_level_await]\n");
for await_expr in &self.top_level_awaits {
append!(
output,
" {{ expression = \"{}\", span = [{}, {}] }}\n",
await_expr.expression,
await_expr.start,
await_expr.end
);
}
output.push('\n');
}
if !self.optimization.event_cache.is_empty() {
output.push_str("[event_cache]\n");
for event in &self.optimization.event_cache {
append!(
output,
" _cache[{}] = {{ event = \"{}\", handler = \"{}\" }}\n",
event.cache_index,
event.event_name,
event.handler
);
}
output.push('\n');
}
if !self.optimization.blocks.is_empty() {
output.push_str("[blocks]\n");
for block in &self.optimization.blocks {
append!(
output,
" block_{} = {{ type = \"{}\", dynamic_children = {} }}\n",
block.id,
block.block_type,
block.dynamic_children
);
}
output.push('\n');
}
if !self.diagnostics.is_empty() {
output.push_str("[diagnostics]\n");
for diag in &self.diagnostics {
let severity = match diag.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
};
append!(
output,
" {{ severity = \"{severity}\", message = \"{}\" }}\n",
diag.message
);
}
}
output
}
}