use super::types::VirtualTsOptions;
use vize_carton::append;
use vize_carton::String;
pub(crate) const VUE_SETUP_COMPILER_MACROS: &str = r#" // Compiler macros (only valid in setup scope, not global)
// Emit type helper: converts { event: [args] } to callable emit function
type __EmitFn<T> = T extends Record<string, any[]> ? <K extends keyof T>(event: K, ...args: T[K]) => void : T;
function defineProps<_T = unknown>(): _T { return undefined as unknown as _T; }
function defineEmits<_T = unknown>(): __EmitFn<_T> { return (() => {}) as any; }
function defineExpose<_T = unknown>(_exposed?: _T): void { void _exposed; }
function defineModel<_T = unknown>(_name?: string, _options?: any): _T { void _name; void _options; return undefined as unknown as _T; }
function defineSlots<_T = unknown>(): _T { return undefined as unknown as _T; }
function withDefaults<_T = unknown, _D = unknown>(_props: _T, _defaults: _D): _T & _D { void _props; void _defaults; return undefined as unknown as _T & _D; }
function useTemplateRef<_T extends Element | import('vue').ComponentPublicInstance = Element>(_key: string): import('vue').ShallowRef<_T | null> { void _key; return undefined as unknown as import('vue').ShallowRef<_T | null>; }
// Mark compiler macros as used
void defineProps; void defineEmits; void defineExpose; void defineModel; void defineSlots; void withDefaults; void useTemplateRef;"#;
pub(crate) const IMPORT_META_AUGMENTATION: &str = r#"// ImportMeta augmentation (Vite/Nuxt)
declare global {
interface ImportMeta {
readonly env: Record<string, string | boolean | undefined>;
readonly client: boolean;
readonly server: boolean;
readonly dev: boolean;
readonly prod: boolean;
readonly ssr: boolean;
readonly hot?: {
readonly data: any;
accept(): void;
accept(cb: (mod: any) => void): void;
accept(dep: string, cb: (mod: any) => void): void;
accept(deps: readonly string[], cb: (mods: any[]) => void): void;
dispose(cb: (data: any) => void): void;
decline(): void;
invalidate(message?: string): void;
on(event: string, cb: (...args: any[]) => void): void;
};
glob(pattern: string, options?: any): Record<string, any>;
glob(pattern: string[], options?: any): Record<string, any>;
}
}
"#;
pub(crate) fn generate_template_context(options: &VirtualTsOptions) -> String {
let mut ctx = String::default();
ctx.push_str(" // Vue instance context (available in template)\n");
ctx.push_str(" const $attrs: Record<string, unknown> = {} as any;\n");
ctx.push_str(" const $slots: Record<string, (...args: any[]) => any> = {} as any;\n");
ctx.push_str(" const $refs: Record<string, any> = {} as any;\n");
ctx.push_str(" const $emit: (...args: any[]) => void = (() => {}) as any;\n");
if !options.template_globals.is_empty() {
ctx.push_str(" // Plugin globals (configurable via --globals)\n");
for global in &options.template_globals {
append!(
ctx,
" const {}: {} = {};\n",
global.name,
global.type_annotation,
global.default_value
);
}
}
ctx.push_str(" // Mark template context as used\n");
ctx.push_str(" void $attrs; void $slots; void $refs; void $emit;\n");
if !options.template_globals.is_empty() {
ctx.push_str(" ");
for (i, global) in options.template_globals.iter().enumerate() {
if i > 0 {
ctx.push(' ');
}
append!(ctx, "void {};", global.name);
}
ctx.push('\n');
}
ctx
}
pub(crate) fn is_type_decl_complete(trimmed: &str, brace_depth: i32, is_alias: bool) -> bool {
if is_alias {
brace_depth <= 0 && trimmed.ends_with(';')
} else {
brace_depth <= 0 && (trimmed.ends_with('}') || trimmed.ends_with("};"))
}
}
pub(crate) fn is_type_declaration_start(trimmed: &str) -> bool {
let s = trimmed.strip_prefix("export ").unwrap_or(trimmed);
if s.starts_with("interface ") || s.starts_with("enum ") {
return true;
}
if let Some(rest) = s.strip_prefix("type ") {
let rest = rest.trim_start();
if let Some(first_char) = rest.chars().next() {
if first_char.is_ascii_alphabetic() || first_char == '_' {
return rest.contains('=');
}
}
}
false
}
pub(crate) fn strip_as_assertion(source: &str) -> (&str, Option<&str>) {
let trimmed = source.trim();
let mut paren_depth = 0i32;
let bytes = trimmed.as_bytes();
let mut last_as_pos = None;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'(' => paren_depth += 1,
b')' => paren_depth -= 1,
b' ' if paren_depth == 0 => {
if i + 4 <= bytes.len() && &bytes[i..i + 4] == b" as " {
last_as_pos = Some(i);
}
}
_ => {}
}
i += 1;
}
if let Some(pos) = last_as_pos {
let expr = trimmed[..pos].trim();
let type_ann = trimmed[pos + 4..].trim();
if !type_ann.is_empty() {
return (expr, Some(type_ann));
}
}
(trimmed, None)
}
pub(crate) fn get_dom_event_type(event_name: &str) -> &'static str {
match event_name {
"click" | "dblclick" | "mousedown" | "mouseup" | "mousemove" | "mouseenter"
| "mouseleave" | "mouseover" | "mouseout" | "contextmenu" => "MouseEvent",
"pointerdown" | "pointerup" | "pointermove" | "pointerenter" | "pointerleave"
| "pointerover" | "pointerout" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" => "PointerEvent",
"touchstart" | "touchend" | "touchmove" | "touchcancel" => "TouchEvent",
"keydown" | "keyup" | "keypress" => "KeyboardEvent",
"focus" | "blur" | "focusin" | "focusout" => "FocusEvent",
"input" | "beforeinput" => "InputEvent",
"compositionstart" | "compositionend" | "compositionupdate" => "CompositionEvent",
"submit" => "SubmitEvent",
"change" => "Event",
"reset" => "Event",
"drag" | "dragstart" | "dragend" | "dragenter" | "dragleave" | "dragover" | "drop" => {
"DragEvent"
}
"cut" | "copy" | "paste" => "ClipboardEvent",
"wheel" => "WheelEvent",
"animationstart" | "animationend" | "animationiteration" | "animationcancel" => {
"AnimationEvent"
}
"transitionstart" | "transitionend" | "transitionrun" | "transitioncancel" => {
"TransitionEvent"
}
"scroll" | "resize" => "Event",
"play" | "pause" | "ended" | "loadeddata" | "loadedmetadata" | "timeupdate"
| "volumechange" | "waiting" | "seeking" | "seeked" | "ratechange" | "durationchange"
| "canplay" | "canplaythrough" | "playing" | "progress" | "stalled" | "suspend"
| "emptied" | "abort" => "Event",
"error" => "ErrorEvent",
"load" => "Event",
"select" | "selectionchange" | "selectstart" => "Event",
_ => "Event",
}
}
pub(crate) fn to_camel_case(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut capitalize_next = false;
let mut first = true;
for c in s.chars() {
if c == '-' || c == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else if first {
result.push(c.to_ascii_lowercase());
first = false;
} else {
result.push(c);
}
}
result
}
pub(crate) fn to_safe_identifier(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect()
}