use std::ops::Range;
use super::types::VirtualTsOptions;
use vize_carton::String;
use vize_carton::append;
use vize_carton::config::VueVersion;
use vize_croquis::macros::{
DEFINE_EMITS, DEFINE_EXPOSE, DEFINE_MODEL, DEFINE_PROPS, DEFINE_SLOTS, WITH_DEFAULTS,
};
pub(crate) const USE_TEMPLATE_REF: &str = "useTemplateRef";
pub(crate) const SETUP_SCOPE_HELPER_NAMES: &[&str] = &[
DEFINE_PROPS,
DEFINE_EMITS,
DEFINE_EXPOSE,
DEFINE_MODEL,
DEFINE_SLOTS,
WITH_DEFAULTS,
USE_TEMPLATE_REF,
];
macro_rules! vue_type_aliases_text {
() => {
r#"type __EmitShape<T> = T extends (...args: any[]) => any ? T : T extends Record<string, any> ? { [K in keyof T]: T[K] extends (...args: infer A) => any ? A : T[K] extends any[] ? T[K] : any[]; } : Record<string, any[]>;
type __EmitArgs<T, K extends keyof T> = T[K] extends any[] ? T[K] : any[];
type __EmitFn<T> = __EmitShape<T> extends (...args: any[]) => any ? __EmitShape<T> : (<K extends keyof __EmitShape<T>>(event: K, ...args: __EmitArgs<__EmitShape<T>, K>) => void);
type __RuntimePropCtor<T> = T extends readonly (infer U)[] ? __RuntimePropCtor<U> : T extends { type: infer U } ? __RuntimePropCtor<U> : T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? unknown[] : T extends ObjectConstructor ? Record<string, unknown> : T extends DateConstructor ? Date : T extends FunctionConstructor ? (...args: any[]) => any : unknown;
type __RuntimePropResolved<T> = T extends { required: true } ? true : T extends { default: any } ? true : false;
type __RuntimePropShape<T extends Record<string, any>> = { [K in keyof T]: __RuntimePropResolved<T[K]> extends true ? __RuntimePropCtor<T[K]> : __RuntimePropCtor<T[K]> | undefined; };
type __DefaultFactory<T> = (props: any) => T;
type __WithDefaultValue<T> = T | __DefaultFactory<T>;
type __WithDefaultsArgs<T> = { [K in keyof T]?: __WithDefaultValue<T[K]> };
type __WithDefaultsResult<T, D extends __WithDefaultsArgs<T>> = Omit<T, keyof D> & Required<Pick<T, keyof D & keyof T>>;
type __Ref<T> = import('vue').Ref<T>;
type __ShallowRef<T> = import('vue').ShallowRef<T>;"#
};
}
macro_rules! v_for_list_decls_text {
() => {
r#"declare function __vForList<T>(source: readonly T[] | undefined | null): readonly [item: T, key: number, index: number][];
declare function __vForList(source: number | undefined | null): readonly [item: number, key: number, index: number][];
declare function __vForList(source: string | undefined | null): readonly [item: string, key: number, index: number][];
declare function __vForList<T>(source: Iterable<T> | undefined | null): readonly [item: T, key: number, index: number][];
declare function __vForList<T extends object>(source: T | undefined | null): readonly [item: T[keyof T], key: keyof T, index: number][];"#
};
}
macro_rules! vue_type_helpers_text {
() => {
concat!(vue_type_aliases_text!(), "\n", v_for_list_decls_text!())
};
}
macro_rules! emit_overload_helpers_text {
() => {
concat!(
"type __VizeOverloadProps<TOverload> = Pick<TOverload, keyof TOverload>;\n",
"type __VizeOverloadUnionRecursive<TOverload, TPartialOverload = unknown> = TOverload extends (...args: infer TArgs) => infer TReturn ? TPartialOverload extends TOverload ? never : __VizeOverloadUnionRecursive<TPartialOverload & TOverload, TPartialOverload & ((...args: TArgs) => TReturn) & __VizeOverloadProps<TOverload>> | ((...args: TArgs) => TReturn) : never;\n",
"type __VizeOverloadUnion<TOverload extends (...args: any[]) => any> = Exclude<__VizeOverloadUnionRecursive<(() => never) & TOverload>, TOverload extends () => never ? never : () => never>;\n",
"type __VizeOverloadParameters<T extends (...args: any[]) => any> = Parameters<__VizeOverloadUnion<T>>;\n",
"type __VizeIsStringLiteral<T> = T extends string ? string extends T ? false : true : false;\n",
"type __VizeParametersToFns<T extends any[]> = { [K in T[0]]: __VizeIsStringLiteral<K> extends true ? (...args: T extends [e: infer E, ...args: infer P] ? K extends E ? P : never : never) => any : never };\n",
"type __EmitOptions<T> = { [K in keyof __EmitShape<T> & string]: (...args: __EmitArgs<__EmitShape<T>, K>) => any } & (__EmitShape<T> extends (...args: any[]) => any ? __VizeParametersToFns<__VizeOverloadParameters<__EmitShape<T>>> : {});\n",
)
};
}
pub(crate) const VUE_TYPE_HELPERS: &str = vue_type_helpers_text!();
pub(crate) const EMIT_OVERLOAD_HELPERS: &str = emit_overload_helpers_text!();
pub(crate) const EMIT_PROPS_HELPER: &str =
"type __EmitProps<T> = import('vue').EmitsToProps<__EmitOptions<T>>;\n";
pub(crate) const VUE_SETUP_HELPERS: &str = r#" // Compiler macros (only valid in setup scope, not global)
function defineProps<_T = unknown>(): _T;
function defineProps<const _T extends readonly string[]>(_props: _T): { [K in _T[number]]?: any };
function defineProps<const _T extends Record<string, any>>(_props: _T): __RuntimePropShape<_T>;
function defineProps(_props?: any) { void _props; return undefined as any; }
function defineEmits<_T = unknown>(): __EmitFn<_T>;
function defineEmits<const _T extends readonly string[]>(_events: _T): (event: _T[number], ...args: any[]) => void;
function defineEmits<const _T extends Record<string, any>>(_events: _T): __EmitFn<_T>;
function defineEmits(_events?: any) { void _events; return (() => {}) as any; }
function defineExpose<_T = unknown>(_exposed?: _T): void { void _exposed; }
function defineModel<_T = unknown>(): __Ref<_T | undefined>;
function defineModel<_T = unknown>(_options: any): __Ref<_T>;
function defineModel<_T = unknown>(_name: string, _options?: any): __Ref<_T>;
function defineModel(_name_or_options?: any, _options?: any) { void _name_or_options; void _options; return undefined as any; }
function defineSlots<_T = unknown>(): _T { return undefined as unknown as _T; }
function withDefaults<_T, _D extends __WithDefaultsArgs<_T>>(_props: _T, _defaults: _D): __WithDefaultsResult<_T, _D> { void _props; void _defaults; return undefined as unknown as __WithDefaultsResult<_T, _D>; }
function useTemplateRef<_T = any>(_key: string): __ShallowRef<_T | null> { void _key; return undefined as unknown as __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 (reference existing framework types)
/// <reference types="vite/client" />
declare global {
// Extend ImportMeta with Nuxt-specific properties not covered by vite/client
interface ImportMeta {
client: boolean;
server: boolean;
dev: boolean;
prod: boolean;
ssr: boolean;
}
}
"#;
pub(crate) const VUE_SETUP_HELPERS_HOISTED: &str = r#" // Compiler macros (setup-scope only; signatures hoisted to the shared helpers file)
const defineProps = __vize_defineProps;
const defineEmits = __vize_defineEmits;
const defineExpose = __vize_defineExpose;
const defineModel = __vize_defineModel;
const defineSlots = __vize_defineSlots;
const withDefaults = __vize_withDefaults;
const useTemplateRef = __vize_useTemplateRef;
// Mark compiler macros as used
void defineProps; void defineEmits; void defineExpose; void defineModel; void defineSlots; void withDefaults; void useTemplateRef;"#;
pub const SHARED_PREAMBLE_FILE_NAME: &str = "__vize_helpers.d.ts";
pub const SHARED_PREAMBLE_DTS: &str = concat!(
"// ============================================\n",
"// Shared ambient helpers for vize virtual TypeScript\n",
"// Generated by vize\n",
"// ============================================\n",
"// Global script: one copy of these declarations per program replaces the\n",
"// preamble previously embedded in every generated .vue.ts module.\n",
"\n",
"// ImportMeta augmentation (reference existing framework types)\n",
"/// <reference types=\"vite/client\" />\n",
"// Extend ImportMeta with Nuxt-specific properties not covered by vite/client\n",
"interface ImportMeta {\n",
" client: boolean;\n",
" server: boolean;\n",
" dev: boolean;\n",
" prod: boolean;\n",
" ssr: boolean;\n",
"}\n",
"\n",
"// Shared type helpers used by generated virtual modules\n",
vue_type_helpers_text!(),
"\n\n",
"// Emit-overload helpers (consumed by the per-file __EmitProps alias)\n",
emit_overload_helpers_text!(),
"\n",
"// Compiler-macro signatures (aliased inside each module's __setup() scope)\n",
"declare function __vize_defineProps<_T = unknown>(): _T;\n",
"declare function __vize_defineProps<const _T extends readonly string[]>(_props: _T): { [K in _T[number]]?: any };\n",
"declare function __vize_defineProps<const _T extends Record<string, any>>(_props: _T): __RuntimePropShape<_T>;\n",
"declare function __vize_defineEmits<_T = unknown>(): __EmitFn<_T>;\n",
"declare function __vize_defineEmits<const _T extends readonly string[]>(_events: _T): (event: _T[number], ...args: any[]) => void;\n",
"declare function __vize_defineEmits<const _T extends Record<string, any>>(_events: _T): __EmitFn<_T>;\n",
"declare function __vize_defineExpose<_T = unknown>(_exposed?: _T): void;\n",
"declare function __vize_defineModel<_T = unknown>(): __Ref<_T | undefined>;\n",
"declare function __vize_defineModel<_T = unknown>(_options: any): __Ref<_T>;\n",
"declare function __vize_defineModel<_T = unknown>(_name: string, _options?: any): __Ref<_T>;\n",
"declare function __vize_defineSlots<_T = unknown>(): _T;\n",
"declare function __vize_withDefaults<_T, _D extends __WithDefaultsArgs<_T>>(_props: _T, _defaults: _D): __WithDefaultsResult<_T, _D>;\n",
"declare function __vize_useTemplateRef<_T = any>(_key: string): __ShallowRef<_T | null>;\n",
);
pub const DECLARATION_HELPERS_DTS: &str = concat!(
"// Shared helper types for vize-generated declaration files.\n",
"// Generated by vize\n",
vue_type_aliases_text!(),
"\n",
emit_overload_helpers_text!(),
);
const VUE2_INSTANCE_MEMBERS: &[&str] = &[
"$listeners",
"$children",
"$scopedSlots",
"$on",
"$off",
"$once",
"$set",
"$delete",
"$createElement",
"_c",
];
pub(crate) fn generate_template_context(options: &VirtualTsOptions, dialect: VueVersion) -> String {
let mut ctx = String::default();
let needs_global_helper =
!options.template_globals.is_empty() || !options.css_modules.is_empty();
let vue2_dialect = matches!(dialect, VueVersion::V2 | VueVersion::V2_7);
ctx.push_str(" // Vue template context (delegates to ComponentPublicInstance)\n");
ctx.push_str(" type __Ctx = import('vue').ComponentPublicInstance;\n");
if needs_global_helper {
ctx.push_str(" type __Global<K extends string, F = unknown> = K extends keyof __Ctx ? __Ctx[K] : F;\n");
}
ctx.push_str(" const __ctx = undefined as unknown as __Ctx;\n");
ctx.push_str(" const $attrs = __ctx.$attrs;\n");
ctx.push_str(" const $slots = __ctx.$slots;\n");
ctx.push_str(" const $refs = __ctx.$refs;\n");
ctx.push_str(" const $emit = __ctx.$emit;\n");
if vue2_dialect {
ctx.push_str(" // Vue 2 instance members (not on Vue 3 ComponentPublicInstance)\n");
for member in VUE2_INSTANCE_MEMBERS {
append!(ctx, " const {member} = undefined as any;\n");
}
}
if !options.template_globals.is_empty() {
ctx.push_str(" // Plugin globals (via ComponentCustomProperties)\n");
for global in &options.template_globals {
append!(
ctx,
" const {}: __Global<'{}', {}> = undefined as any;\n",
global.name,
global.name,
global.type_annotation
);
}
}
if !options.css_modules.is_empty() {
ctx.push_str(" // CSS modules (from <style module>)\n");
for module_name in &options.css_modules {
append!(
ctx,
" const {module_name}: __Global<'{module_name}', Record<string, string>> = undefined as any;\n"
);
}
}
ctx.push_str(" void __ctx; void $attrs; void $slots; void $refs; void $emit;\n");
if vue2_dialect {
ctx.push_str(" ");
for member in VUE2_INSTANCE_MEMBERS {
append!(ctx, "void {member};");
}
ctx.push('\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');
}
if !options.css_modules.is_empty() {
ctx.push_str(" ");
for (i, module_name) in options.css_modules.iter().enumerate() {
if i > 0 {
ctx.push(' ');
}
append!(ctx, "void {module_name};");
}
ctx.push('\n');
}
ctx
}
pub(crate) fn generated_text_range(
generated_segment: &str,
mapped_text: &str,
generated_start: usize,
) -> Range<usize> {
if mapped_text.is_empty() {
return generated_start..generated_start + generated_segment.len();
}
let relative_start = generated_segment.find(mapped_text).unwrap_or(0);
let start = generated_start + relative_start;
start..start + mapped_text.len()
}
pub(crate) fn get_dom_event_type(event_name: &str) -> &'static str {
match event_name {
"dblclick" | "mousedown" | "mouseup" | "mousemove" | "mouseenter" | "mouseleave"
| "mouseover" | "mouseout" | "contextmenu" => "MouseEvent",
"click" | "auxclick" | "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",
"toggle" | "beforetoggle" => "ToggleEvent",
"formdata" => "FormDataEvent",
"popstate" => "PopStateEvent",
"hashchange" => "HashChangeEvent",
"message" => "MessageEvent",
"storage" => "StorageEvent",
"online" | "offline" => "Event",
"securitypolicyviolation" => "SecurityPolicyViolationEvent",
_ => "Event",
}
}
#[cfg(test)]
mod event_type_tests {
use super::get_dom_event_type;
#[test]
fn maps_legacy_dom_events() {
assert_eq!(get_dom_event_type("click"), "PointerEvent");
assert_eq!(get_dom_event_type("auxclick"), "PointerEvent");
assert_eq!(get_dom_event_type("dblclick"), "MouseEvent");
assert_eq!(get_dom_event_type("keydown"), "KeyboardEvent");
assert_eq!(get_dom_event_type("submit"), "SubmitEvent");
}
#[test]
fn maps_modern_dom_events() {
assert_eq!(get_dom_event_type("toggle"), "ToggleEvent");
assert_eq!(get_dom_event_type("beforetoggle"), "ToggleEvent");
assert_eq!(get_dom_event_type("formdata"), "FormDataEvent");
}
#[test]
fn unknown_events_fall_back_to_event() {
assert_eq!(get_dom_event_type("totally-made-up"), "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 {
let mut result = to_safe_identifier_fragment(s);
if !result
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '$')
{
result.insert(0, '_');
}
if is_reserved_identifier(result.as_str()) {
result.insert(0, '_');
}
result
}
pub(crate) fn to_safe_identifier_fragment(s: &str) -> String {
let mut result = String::with_capacity(s.len().max(1));
for c in s.chars() {
if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
result.push(c);
} else {
result.push('_');
}
}
if result.is_empty() {
result.push('_');
}
result
}
#[inline]
pub(crate) fn is_reserved_identifier(s: &str) -> bool {
matches!(
s,
"await"
| "break"
| "case"
| "catch"
| "class"
| "const"
| "continue"
| "debugger"
| "default"
| "delete"
| "do"
| "else"
| "enum"
| "export"
| "extends"
| "false"
| "finally"
| "for"
| "function"
| "if"
| "import"
| "in"
| "instanceof"
| "new"
| "null"
| "return"
| "super"
| "switch"
| "this"
| "throw"
| "true"
| "try"
| "typeof"
| "var"
| "void"
| "while"
| "with"
| "yield"
| "let"
| "static"
| "implements"
| "interface"
| "package"
| "private"
| "protected"
| "public"
| "as"
| "from"
| "of"
)
}