#![allow(clippy::disallowed_macros)]
use super::{
BindingMetadata, BindingType, CompactString, MacroTracker, Path, RootNode, ScopeChain,
ScopeData, ScopeKind, ScriptParseResult, VirtualTsConfig, VirtualTsGenerator, VirtualTsOutput,
};
use vize_carton::{profile, String, ToCompactString};
impl VirtualTsGenerator {
fn extract_setup_info(scopes: &ScopeChain) -> (Option<CompactString>, bool) {
for scope in scopes.iter() {
if scope.kind == ScopeKind::ScriptSetup {
if let ScopeData::ScriptSetup(data) = scope.data() {
return (data.generic.clone(), data.is_async);
}
}
}
(None, false)
}
pub fn generate_from_croquis(
&mut self,
script_content: &str,
parse_result: &ScriptParseResult,
template_ast: Option<&RootNode>,
config: &VirtualTsConfig,
from_file: Option<&Path>,
) -> VirtualTsOutput {
self.reset();
self.block_offset = config.script_offset;
let (scope_generic, scope_async) = Self::extract_setup_info(&parse_result.scopes);
let generic = scope_generic.or_else(|| config.generic.clone());
let is_async = scope_async || config.is_async;
self.write_line("// Virtual TypeScript for Vue SFC type checking");
self.write_line("// Generated by vize_croquis");
self.write_line("");
self.write_line("type $Vue = import('vue');");
self.write_line("");
profile!(
"croquis.virtual_ts.emit_module_imports",
self.emit_module_imports(script_content, from_file)
);
profile!(
"croquis.virtual_ts.emit_setup_open",
self.emit_setup_function_open(&generic, is_async)
);
profile!(
"croquis.virtual_ts.emit_macros",
self.emit_compiler_macro_definitions(&parse_result.macros)
);
profile!(
"croquis.virtual_ts.emit_setup_body",
self.emit_setup_body(script_content)
);
if let Some(ast) = template_ast {
self.block_offset = config.template_offset;
profile!(
"croquis.virtual_ts.emit_template_scope",
self.emit_template_scope(ast, &parse_result.bindings)
);
}
profile!(
"croquis.virtual_ts.emit_setup_close",
self.emit_setup_function_close()
);
profile!("croquis.virtual_ts.create_output", self.create_output())
}
pub fn generate_script_setup(
&mut self,
script_content: &str,
bindings: &BindingMetadata,
from_file: Option<&Path>,
) -> VirtualTsOutput {
self.reset();
self.write_line("// Virtual TypeScript for Vue SFC type checking");
self.write_line("// Generated by vize_croquis");
self.write_line("");
self.write_line("type $Vue = import('vue');");
self.write_line("");
profile!(
"croquis.virtual_ts.emit_module_imports",
self.emit_module_imports(script_content, from_file)
);
profile!(
"croquis.virtual_ts.emit_setup_open",
self.emit_setup_function_open(&None, false)
);
profile!(
"croquis.virtual_ts.emit_default_macros",
self.emit_default_compiler_macro_definitions()
);
profile!(
"croquis.virtual_ts.emit_setup_body",
self.emit_setup_body(script_content)
);
self.emit_line("");
self.emit_line("// Props/Emits types for component");
profile!(
"croquis.virtual_ts.generate_props_type",
self.generate_props_type(bindings)
);
profile!(
"croquis.virtual_ts.generate_emits_type",
self.generate_emits_type(bindings)
);
profile!(
"croquis.virtual_ts.emit_setup_close",
self.emit_setup_function_close()
);
profile!("croquis.virtual_ts.create_output", self.create_output())
}
pub(crate) fn emit_module_imports(&mut self, content: &str, from_file: Option<&Path>) {
self.write_line("// Module-level imports");
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("import ") {
let resolved_line = self.resolve_import_line(line, from_file);
self.write_line(&resolved_line);
}
}
self.write_line("");
}
fn resolve_import_line(&self, line: &str, from_file: Option<&Path>) -> String {
let Some(file_path) = from_file else {
return line.to_compact_string();
};
let Some(parent) = file_path.parent() else {
return line.to_compact_string();
};
let import_re = regex::Regex::new(r#"from\s+['"](\.[^'"]+)['"]"#);
match import_re {
Ok(re) => re
.replace(line, |caps: ®ex::Captures| {
let rel_path = caps.get(1).map(|m| m.as_str()).unwrap_or("");
let resolved = parent.join(rel_path);
let abs_path = resolved
.canonicalize()
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_else(|| resolved.to_string_lossy().to_compact_string());
format!("from \"{}\"", abs_path)
})
.to_compact_string(),
Err(_) => line.to_compact_string(),
}
}
pub(crate) fn emit_setup_function_open(
&mut self,
generic: &Option<CompactString>,
is_async: bool,
) {
self.write_line("// Setup scope (function)");
let async_prefix = if is_async { "async " } else { "" };
let generic_params = generic
.as_ref()
.map(|g| format!("<{}>", g))
.unwrap_or_default();
self.write_line(&format!(
"{}function __setup{}() {{",
async_prefix, generic_params
));
self.indent_level += 1;
}
pub(crate) fn emit_setup_function_close(&mut self) {
self.indent_level = self.indent_level.saturating_sub(1);
self.write_line("}");
self.write_line("");
self.write_line("// Invoke setup");
self.write_line("__setup();");
}
pub(crate) fn emit_compiler_macro_definitions(&mut self, macros: &MacroTracker) {
self.emit_line("// Compiler macros (setup-scope only, actual functions not declare)");
self.emit_line("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[]>;");
self.emit_line(
"type __EmitArgs<T, K extends keyof T> = T[K] extends any[] ? T[K] : any[];",
);
self.emit_line("type __EmitFn<T> = __EmitShape<T> extends (...args: any[]) => any ? __EmitShape<T> : (<K extends keyof __EmitShape<T>>(event: K, ...args: __EmitArgs<__EmitShape<T>, K>) => void);");
self.emit_line("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;");
self.emit_line("type __RuntimePropResolved<T> = T extends { required: true } ? true : T extends { default: any } ? true : false;");
self.emit_line("type __RuntimePropShape<T extends Record<string, any>> = { [K in keyof T]: __RuntimePropResolved<T[K]> extends true ? __RuntimePropCtor<T[K]> : __RuntimePropCtor<T[K]> | undefined; };");
self.emit_line("type __RuntimeEmitShape<T extends Record<string, any>> = { [K in keyof T]: T[K] extends (...args: infer A) => any ? A : T[K] extends any[] ? T[K] : any[]; };");
self.emit_line("type __DefaultFactory<T> = (props: any) => T;");
self.emit_line("type __WithDefaultValue<T> = T | __DefaultFactory<T>;");
self.emit_line(
"type __WithDefaultsArgs<T> = { [K in keyof T]?: __WithDefaultValue<T[K]> };",
);
self.emit_line("type __WithDefaultsResult<T, D extends __WithDefaultsArgs<T>> = Omit<T, keyof D> & { [K in keyof D & keyof T]-?: T[K] };");
self.emit_line("function defineProps<T>(): T { return undefined as unknown as T; }");
self.emit_line("function defineProps<const T extends readonly string[]>(props: T): { [K in T[number]]?: any } { return undefined as unknown as { [K in T[number]]?: any }; }");
self.emit_line("function defineProps<const T extends Record<string, any>>(props: T): __RuntimePropShape<T> { return undefined as unknown as __RuntimePropShape<T>; }");
self.emit_line("function defineEmits<T>(): __EmitFn<T> { return undefined as unknown as __EmitFn<T>; }");
self.emit_line("function defineEmits<const T extends readonly string[]>(events: T): (event: T[number], ...args: any[]) => void { return (() => {}) as any; }");
self.emit_line("function defineEmits<const T extends Record<string, any>>(events: T): __EmitFn<__RuntimeEmitShape<T>> { return undefined as unknown as __EmitFn<__RuntimeEmitShape<T>>; }");
self.emit_line("function defineExpose<T>(exposed?: T): void { }");
self.emit_line("function defineOptions<T>(options: T): void { }");
self.emit_line("function defineSlots<T>(): T { return undefined as unknown as T; }");
self.emit_line("function defineModel<T>(): $Vue['ModelRef']<T | undefined> { return undefined as unknown as $Vue['ModelRef']<T | undefined>; }");
self.emit_line("function defineModel<T>(options: { required?: boolean, default?: T }): $Vue['ModelRef']<T> { return undefined as unknown as $Vue['ModelRef']<T>; }");
self.emit_line("function defineModel<T>(name: string, options?: { required?: boolean, default?: T }): $Vue['ModelRef']<T> { return undefined as unknown as $Vue['ModelRef']<T>; }");
self.emit_line("function withDefaults<T, D extends __WithDefaultsArgs<T>>(props: T, defaults: D): __WithDefaultsResult<T, D> { return undefined as unknown as __WithDefaultsResult<T, D>; }");
self.emit_line("const $event: Event = undefined as unknown as Event;");
self.emit_line("function useTemplateRef<T = any>(key: string): $Vue['ShallowRef']<T | null> { return undefined as unknown as $Vue['ShallowRef']<T | null>; }");
if let Some(props) = macros.define_props() {
if let Some(ref type_args) = props.type_args {
self.emit_line(&format!("type __Props = {};", type_args));
}
}
if let Some(emits) = macros.define_emits() {
if let Some(ref type_args) = emits.type_args {
self.emit_line(&format!("type __Emits = {};", type_args));
}
}
if let Some(expose) = macros.define_expose() {
if let Some(ref type_args) = expose.type_args {
self.emit_line(&format!("type __Exposed = {};", type_args));
} else if let Some(ref runtime_args) = expose.runtime_args {
self.emit_line(&format!("type __Exposed = typeof ({});", runtime_args));
}
self.emit_line(
"type __ComponentInstance = $Vue['ComponentPublicInstance'] & __Exposed;",
);
}
if let Some(slots) = macros.define_slots() {
if let Some(ref type_args) = slots.type_args {
self.emit_line(&format!("type __Slots = {};", type_args));
}
}
self.emit_line("");
}
pub(crate) fn emit_default_compiler_macro_definitions(&mut self) {
self.emit_line("// Compiler macros (setup-scope only, actual functions not declare)");
self.emit_line("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[]>;");
self.emit_line(
"type __EmitArgs<T, K extends keyof T> = T[K] extends any[] ? T[K] : any[];",
);
self.emit_line("type __EmitFn<T> = __EmitShape<T> extends (...args: any[]) => any ? __EmitShape<T> : (<K extends keyof __EmitShape<T>>(event: K, ...args: __EmitArgs<__EmitShape<T>, K>) => void);");
self.emit_line("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;");
self.emit_line("type __RuntimePropResolved<T> = T extends { required: true } ? true : T extends { default: any } ? true : false;");
self.emit_line("type __RuntimePropShape<T extends Record<string, any>> = { [K in keyof T]: __RuntimePropResolved<T[K]> extends true ? __RuntimePropCtor<T[K]> : __RuntimePropCtor<T[K]> | undefined; };");
self.emit_line("type __RuntimeEmitShape<T extends Record<string, any>> = { [K in keyof T]: T[K] extends (...args: infer A) => any ? A : T[K] extends any[] ? T[K] : any[]; };");
self.emit_line("type __DefaultFactory<T> = (props: any) => T;");
self.emit_line("type __WithDefaultValue<T> = T | __DefaultFactory<T>;");
self.emit_line(
"type __WithDefaultsArgs<T> = { [K in keyof T]?: __WithDefaultValue<T[K]> };",
);
self.emit_line("type __WithDefaultsResult<T, D extends __WithDefaultsArgs<T>> = Omit<T, keyof D> & { [K in keyof D & keyof T]-?: T[K] };");
self.emit_line("function defineProps<T>(): T { return undefined as unknown as T; }");
self.emit_line("function defineProps<const T extends readonly string[]>(props: T): { [K in T[number]]?: any } { return undefined as unknown as { [K in T[number]]?: any }; }");
self.emit_line("function defineProps<const T extends Record<string, any>>(props: T): __RuntimePropShape<T> { return undefined as unknown as __RuntimePropShape<T>; }");
self.emit_line("function defineEmits<T>(): __EmitFn<T> { return undefined as unknown as __EmitFn<T>; }");
self.emit_line("function defineEmits<const T extends readonly string[]>(events: T): (event: T[number], ...args: any[]) => void { return (() => {}) as any; }");
self.emit_line("function defineEmits<const T extends Record<string, any>>(events: T): __EmitFn<__RuntimeEmitShape<T>> { return undefined as unknown as __EmitFn<__RuntimeEmitShape<T>>; }");
self.emit_line("function defineExpose<T>(exposed?: T): void { }");
self.emit_line("function defineOptions<T>(options: T): void { }");
self.emit_line("function defineSlots<T>(): T { return undefined as unknown as T; }");
self.emit_line("function defineModel<T>(): $Vue['ModelRef']<T | undefined> { return undefined as unknown as $Vue['ModelRef']<T | undefined>; }");
self.emit_line("function defineModel<T>(options: { required?: boolean, default?: T }): $Vue['ModelRef']<T> { return undefined as unknown as $Vue['ModelRef']<T>; }");
self.emit_line("function defineModel<T>(name: string, options?: { required?: boolean, default?: T }): $Vue['ModelRef']<T> { return undefined as unknown as $Vue['ModelRef']<T>; }");
self.emit_line("function withDefaults<T, D extends __WithDefaultsArgs<T>>(props: T, defaults: D): __WithDefaultsResult<T, D> { return undefined as unknown as __WithDefaultsResult<T, D>; }");
self.emit_line("const $event: Event = undefined as unknown as Event;");
self.emit_line("function useTemplateRef<T = any>(key: string): $Vue['ShallowRef']<T | null> { return undefined as unknown as $Vue['ShallowRef']<T | null>; }");
self.emit_line("");
}
pub(crate) fn emit_setup_body(&mut self, content: &str) {
self.emit_line("// User setup code");
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("import ") {
continue;
}
self.emit_line(line);
}
}
#[allow(unused)]
fn resolve_import_paths(&self, content: &str, from_file: Option<&Path>) -> String {
let Some(file_path) = from_file else {
return content.to_compact_string();
};
let Some(parent) = file_path.parent() else {
return content.to_compact_string();
};
let import_re = regex::Regex::new(
r#"(import\s+(?:type\s+)?(?:\{[^}]*\}|[^{}\s]+)\s+from\s+['"])(\.[^'"]+)(['"])"#,
);
match import_re {
Ok(re) => re
.replace_all(content, |caps: ®ex::Captures| {
let prefix = caps.get(1).map(|m| m.as_str()).unwrap_or("");
let rel_path = caps.get(2).map(|m| m.as_str()).unwrap_or("");
let suffix = caps.get(3).map(|m| m.as_str()).unwrap_or("");
let resolved = parent.join(rel_path);
let abs_path = resolved
.canonicalize()
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_else(|| resolved.to_string_lossy().to_compact_string());
format!("{}{}{}", prefix, abs_path, suffix)
})
.to_compact_string(),
Err(_) => content.to_compact_string(),
}
}
pub(crate) fn generate_props_type(&mut self, bindings: &BindingMetadata) {
let props: Vec<_> = bindings
.bindings
.iter()
.filter(|(_, t)| matches!(t, BindingType::Props | BindingType::PropsAliased))
.collect();
self.emit_line("// Props type");
let mut type_str = String::from("type __Props = { ");
for (i, (name, _)) in props.iter().enumerate() {
if i > 0 {
type_str.push_str(", ");
}
type_str.push_str(&format!("{}?: any", name));
}
type_str.push_str(" };");
self.emit_line(&type_str);
}
pub(crate) fn generate_emits_type(&mut self, _bindings: &BindingMetadata) {
self.emit_line("// Emits type");
self.emit_line("type __Emits = {};");
}
}