1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//! Code generation context and result types.
use crate::ast::RuntimeHelper;
use crate::options::CodegenOptions;
use crate::runtime_helpers::RuntimeHelpers;
use super::helpers::default_helper_alias;
use vize_carton::FxHashSet;
use vize_carton::String;
use vize_carton::ToCompactString;
use vize_carton::camelize;
use vize_carton::capitalize;
/// Code generation context using a UTF-8 string buffer for performance.
pub struct CodegenContext {
/// Generated code buffer
pub(super) code: String,
/// Current indentation level
pub(super) indent_level: u32,
/// Whether we're in SSR mode
#[allow(dead_code)]
pub(super) ssr: bool,
/// Helper function alias map
pub(super) helper_alias: fn(RuntimeHelper) -> &'static str,
/// Runtime global name
pub(super) runtime_global_name: String,
/// Runtime module name
pub(super) runtime_module_name: String,
/// Options
pub(super) options: CodegenOptions,
/// Pure annotation for tree-shaking
pub(super) pure: bool,
/// Helpers used during codegen
pub(super) used_helpers: RuntimeHelpers,
/// Cache index for v-once
pub(super) cache_index: usize,
/// Template-scope parameters (slot props and v-for aliases) that should
/// not be prefixed with `_ctx.`
pub(super) slot_params: FxHashSet<String>,
/// When true, skip `is` prop in generate_props (used for dynamic components)
pub(super) skip_is_prop: bool,
/// When true, skip scope_id attribute in props (used for component/slot elements)
pub(super) skip_scope_id: bool,
/// When true, skip normalizeClass/normalizeStyle wrappers (inside mergeProps)
pub(super) skip_normalize: bool,
/// When true, we are inside a v-for loop (affects slot stability flags)
pub(super) in_v_for: bool,
/// When true, skip v-memo wrapping (already handled by v-for + v-memo)
pub(super) skip_v_memo: bool,
/// When true, the props currently being generated belong to a plain
/// (native) element rather than a component/slot/template. Affects v-on
/// event-name casing rules (Vue preserves case via `on:` for plain
/// elements that have uppercase letters in the raw event name).
pub(super) props_is_plain_element: bool,
/// Whether static child VNodes should be cached in the render function.
pub(super) static_cache: bool,
/// Template-wide counter for v-if branch keys. Each branch in any
/// conditional chain in the template consumes one value, so sibling
/// `v-if`/`v-else` blocks do not reuse the same `{ key: n }` and a
/// patch-time element from one branch can't be reused for another
/// (#961). Vue uses the same shared counter.
pub(super) v_if_branch_counter: usize,
}
/// Code generation result
pub struct CodegenResult {
/// Generated code
pub code: String,
/// Preamble (imports)
pub preamble: String,
/// Source map (JSON)
pub map: Option<String>,
}
impl CodegenContext {
/// Create a new codegen context
pub fn new(options: CodegenOptions) -> Self {
Self {
code: String::with_capacity(4096),
indent_level: 0,
ssr: options.ssr,
helper_alias: default_helper_alias,
runtime_global_name: options.runtime_global_name.to_compact_string(),
runtime_module_name: options.runtime_module_name.to_compact_string(),
options,
pure: false,
used_helpers: RuntimeHelpers::default(),
cache_index: 0,
slot_params: FxHashSet::default(),
skip_is_prop: false,
skip_scope_id: false,
skip_normalize: false,
in_v_for: false,
skip_v_memo: false,
props_is_plain_element: false,
static_cache: false,
v_if_branch_counter: 0,
}
}
/// Allocate the next v-if branch key for the current template.
///
/// Branch keys are unique across the whole template, matching Vue's
/// shared counter, so two sibling conditional blocks never collide on
/// `{ key: n }`.
pub(super) fn next_v_if_branch_key(&mut self) -> usize {
let key = self.v_if_branch_counter;
self.v_if_branch_counter = self.v_if_branch_counter.saturating_add(1);
key
}
/// Add template-scope parameters (identifiers that should not be prefixed)
pub fn add_slot_params(&mut self, params: &[String]) {
for param in params {
self.slot_params.insert(param.clone());
}
}
/// Remove template-scope parameters when exiting their scope
pub fn remove_slot_params(&mut self, params: &[String]) {
for param in params {
self.slot_params.remove(param);
}
}
/// Check if an identifier is a template-scope parameter
pub fn is_slot_param(&self, name: &str) -> bool {
self.slot_params.contains(name)
}
/// Check if there are any template-scope parameters registered
#[inline]
pub fn has_slot_params(&self) -> bool {
!self.slot_params.is_empty()
}
/// Event handler caching is unsafe while template-scope params are in play,
/// because a cached closure would capture the first scoped value.
#[inline]
pub fn cache_handlers_in_current_scope(&self) -> bool {
self.options.cache_handlers && !self.has_slot_params()
}
/// Get next cache index for v-once
pub fn next_cache_index(&mut self) -> usize {
let index = self.cache_index;
self.cache_index += 1;
index
}
/// Push string to buffer
#[inline]
pub fn push(&mut self, code: &str) {
self.code.push_str(code);
}
/// Push code with newline
#[inline]
pub fn push_line(&mut self, code: &str) {
self.push(code);
self.newline();
}
/// Add newline with proper indentation
#[inline]
pub fn newline(&mut self) {
self.code.push('\n');
for _ in 0..self.indent_level {
self.code.push_str(" ");
}
}
/// Increase indentation
#[inline]
pub fn indent(&mut self) {
self.indent_level += 1;
}
/// Decrease indentation
#[inline]
pub fn deindent(&mut self) {
if self.indent_level > 0 {
self.indent_level -= 1;
}
}
/// Add pure annotation /*#__PURE__*/
#[inline]
pub fn push_pure(&mut self) {
if self.pure {
self.code.push_str("/*#__PURE__*/ ");
}
}
/// Get helper name
#[inline]
pub fn helper(&self, helper: RuntimeHelper) -> &'static str {
(self.helper_alias)(helper)
}
/// Track a helper for preamble generation
#[inline]
pub fn use_helper(&mut self, helper: RuntimeHelper) {
self.used_helpers.add(helper);
}
/// Check if a component is in binding metadata (from script setup)
pub fn is_component_in_bindings(&self, component: &str) -> bool {
self.resolve_component_binding_name(component).is_some()
}
/// Resolve the binding name for a component tag.
pub fn resolve_component_binding_name(&self, component: &str) -> Option<String> {
let metadata = self.options.binding_metadata.as_ref()?;
let resolve_base = |name: &str| {
if metadata.bindings.contains_key(name) {
return Some(name.to_compact_string());
}
let camel = camelize(name);
if metadata.bindings.contains_key(camel.as_str()) {
return Some(camel);
}
let pascal = capitalize(&camel);
if metadata.bindings.contains_key(pascal.as_str()) {
return Some(pascal);
}
None
};
if let Some((base, suffix)) = component.split_once('.') {
let resolved_base = resolve_base(base)?;
let mut resolved = String::with_capacity(resolved_base.len() + suffix.len() + 1);
resolved.push_str(resolved_base.as_str());
resolved.push('.');
resolved.push_str(suffix);
return Some(resolved);
}
resolve_base(component)
}
/// Push string to buffer (alias for `push`, compatible with `appends!`/`append!` macros)
#[inline]
#[allow(dead_code)]
pub fn push_str(&mut self, code: &str) {
self.code.push_str(code);
}
/// Push formatted line (format_args! + newline with indentation)
#[inline]
#[allow(dead_code)]
pub fn push_line_fmt(&mut self, args: std::fmt::Arguments<'_>) {
use std::fmt::Write as _;
let _ = self.write_fmt(args);
self.newline();
}
/// Get the generated code as a String
pub fn into_code(self) -> String {
self.code
}
/// Get the generated code as a reference (for temporary use)
pub fn code_as_str(&self) -> &str {
&self.code
}
}
impl std::fmt::Write for CodegenContext {
#[inline]
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.code.push_str(s);
Ok(())
}
}