use std::borrow::Cow;
use oxc_allocator::Allocator;
use oxc_ast::ast::{Expression, Statement};
use oxc_parser::Parser;
use oxc_span::{GetSpan, SourceType};
use vize_carton::{profile, String, ToCompactString};
use crate::script::{transform_destructured_props, ScriptCompileContext};
use crate::types::SfcError;
use super::super::function_mode::{contains_top_level_await, dedupe_imports};
use super::super::import_utils::extract_import_identifiers;
use super::super::macros::{
is_macro_call_line, is_multiline_macro_start, is_paren_macro_start, is_props_destructure_line,
};
use super::super::props::{
add_null_to_runtime_type, extract_emit_names_from_type, extract_prop_types_from_type,
extract_with_defaults_defaults,
};
use super::super::statement_sections::extract_script_sections;
use super::super::typescript::transform_typescript_to_js;
use super::super::{ScriptCompileResult, TemplateParts};
use super::helpers::{extract_const_name, strip_comments_for_counting};
use super::type_handling::resolve_type_args;
const VAPOR_RENDER_ALIAS_BASE: &str = "__vaporRender";
const VAPOR_TEMPLATE_REF_SETTER: &str = "vaporTemplateRefSetter";
#[allow(clippy::too_many_arguments)]
pub fn compile_script_setup_inline(
content: &str,
component_name: &str,
is_ts: bool,
source_is_ts: bool,
is_vapor: bool,
template: TemplateParts<'_>,
normal_script_content: Option<&str>,
css_vars: &[Cow<'_, str>],
scope_id: &str,
filename: Option<&str>,
) -> Result<ScriptCompileResult, SfcError> {
let ctx = build_script_setup_context(content, normal_script_content, filename);
compile_script_setup_inline_with_context(
ctx,
content,
component_name,
is_ts,
source_is_ts,
is_vapor,
template,
normal_script_content,
css_vars,
scope_id,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compile_script_setup_inline_with_context(
ctx: ScriptCompileContext,
content: &str,
component_name: &str,
is_ts: bool,
source_is_ts: bool,
is_vapor: bool,
template: TemplateParts<'_>,
normal_script_content: Option<&str>,
css_vars: &[Cow<'_, str>],
scope_id: &str,
) -> Result<ScriptCompileResult, SfcError> {
let (user_imports, setup_lines, ts_declarations) = profile!(
"atelier.script_inline.parse_sections",
parse_script_content(content, is_ts)
);
let setup_code: String = setup_lines.join("\n").into();
let bump = vize_carton::Bump::new();
let mut output: vize_carton::Vec<u8> = vize_carton::Vec::with_capacity_in(4096, &bump);
let preserved_normal_script = normal_script_content
.filter(|s| !s.is_empty())
.map(|s| s.to_compact_string());
let has_props_destructure = ctx.macros.props_destructure.is_some();
let has_type_based_props = ctx
.macros
.define_props
.as_ref()
.is_some_and(|p| p.type_args.is_some());
let needs_merge_defaults = has_props_destructure
&& !has_type_based_props
&& ctx
.macros
.props_destructure
.as_ref()
.map(|d| d.bindings.values().any(|b| b.default.is_some()))
.unwrap_or(false);
let has_define_model = !ctx.macros.define_models.is_empty();
let has_define_slots = ctx.macros.define_slots.is_some();
let needs_vapor_setup_context = is_vapor && !template.render_fn.is_empty();
let vapor_render_alias = needs_vapor_setup_context
.then(|| build_vapor_render_alias(content, normal_script_content, template.render_fn));
let is_async = contains_top_level_await(&setup_code, source_is_ts);
if is_async {
if is_vapor {
if needs_vapor_setup_context {
output.extend_from_slice(
b"import { withAsyncContext as _withAsyncContext, defineVaporComponent as _defineVaporComponent, getCurrentInstance as _getCurrentInstance, proxyRefs as _proxyRefs } from 'vue'\n",
);
} else {
output.extend_from_slice(
b"import { withAsyncContext as _withAsyncContext, defineVaporComponent as _defineVaporComponent } from 'vue'\n",
);
}
} else if is_ts {
output.extend_from_slice(
b"import { withAsyncContext as _withAsyncContext, defineComponent as _defineComponent } from 'vue'\n",
);
} else {
output.extend_from_slice(
b"import { withAsyncContext as _withAsyncContext } from 'vue'\n",
);
}
}
compile_script_setup_inline_body(
ctx,
component_name,
is_ts,
source_is_ts,
is_vapor,
template,
css_vars,
scope_id,
user_imports,
ts_declarations,
setup_code,
output,
preserved_normal_script,
needs_merge_defaults,
has_define_model,
has_define_slots,
needs_vapor_setup_context,
vapor_render_alias,
is_async,
)
}
fn build_script_setup_context(
content: &str,
normal_script_content: Option<&str>,
filename: Option<&str>,
) -> ScriptCompileContext {
let mut ctx = profile!(
"atelier.script_inline.context.new",
ScriptCompileContext::new(content)
);
if let Some(normal_src) = normal_script_content {
if !normal_src.is_empty() {
profile!(
"atelier.script_inline.collect_normal_types",
ctx.collect_types_from(normal_src)
);
}
}
if let Some(path) = filename {
profile!(
"atelier.script_inline.collect_setup_import_types",
ctx.collect_imported_types_from_path(content, path)
);
if let Some(normal_src) = normal_script_content {
if !normal_src.is_empty() {
profile!(
"atelier.script_inline.collect_normal_import_types",
ctx.collect_imported_types_from_path(normal_src, path)
);
}
}
}
profile!("atelier.script_inline.context.analyze", ctx.analyze());
ctx
}
#[allow(clippy::too_many_arguments)]
fn compile_script_setup_inline_body(
ctx: ScriptCompileContext,
component_name: &str,
is_ts: bool,
source_is_ts: bool,
is_vapor: bool,
template: TemplateParts<'_>,
css_vars: &[Cow<'_, str>],
scope_id: &str,
user_imports: Vec<String>,
ts_declarations: Vec<String>,
setup_code: String,
mut output: vize_carton::Vec<u8>,
preserved_normal_script: Option<String>,
needs_merge_defaults: bool,
has_define_model: bool,
has_define_slots: bool,
needs_vapor_setup_context: bool,
vapor_render_alias: Option<String>,
is_async: bool,
) -> Result<ScriptCompileResult, SfcError> {
if needs_merge_defaults {
output.extend_from_slice(b"import { mergeDefaults as _mergeDefaults } from 'vue'\n");
}
if has_define_slots {
output.extend_from_slice(b"import { useSlots as _useSlots } from 'vue'\n");
}
if has_define_model {
output.extend_from_slice(b"import { useModel as _useModel } from 'vue'\n");
}
let has_css_vars = !css_vars.is_empty();
if has_css_vars {
output.extend_from_slice(
b"import { useCssVars as _useCssVars, unref as _unref } from 'vue'\n",
);
}
let needs_prop_type = false;
if is_vapor && !is_async {
if needs_vapor_setup_context {
output.extend_from_slice(
b"import { defineVaporComponent as _defineVaporComponent, getCurrentInstance as _getCurrentInstance, proxyRefs as _proxyRefs } from 'vue'\n",
);
} else {
output.extend_from_slice(
b"import { defineVaporComponent as _defineVaporComponent } from 'vue'\n",
);
}
} else if is_ts && !is_async {
output.extend_from_slice(b"import { defineComponent as _defineComponent } from 'vue'\n");
}
if !template.imports.is_empty() {
output.extend_from_slice(template.imports.as_bytes());
output.push(b'\n');
}
if !template.hoisted.is_empty() {
output.push(b'\n');
output.extend_from_slice(template.hoisted.as_bytes());
}
if !template.render_fn.is_empty() {
output.push(b'\n');
output.extend_from_slice(template.render_fn.as_bytes());
if let Some(alias) = vapor_render_alias.as_ref() {
output.extend_from_slice(b"const ");
output.extend_from_slice(alias.as_bytes());
output.extend_from_slice(b" = render\n");
}
}
let deduped_imports = profile!(
"atelier.script_inline.dedupe_imports",
dedupe_imports(&user_imports, is_ts)
);
let normal_script_imports = preserved_normal_script
.as_deref()
.map(|script| parse_script_content(script, is_ts).0)
.unwrap_or_default();
let mut setup_return_imports = deduped_imports.clone();
setup_return_imports.extend(normal_script_imports.iter().cloned());
for import in &deduped_imports {
output.extend_from_slice(import.as_bytes());
}
if !ts_declarations.is_empty() {
output.push(b'\n');
for decl in &ts_declarations {
output.extend_from_slice(decl.as_bytes());
output.push(b'\n');
}
}
let has_default_export = if let Some(ref normal_script) = preserved_normal_script {
output.push(b'\n');
output.extend_from_slice(normal_script.as_bytes());
output.push(b'\n');
normal_script.contains("const __default__")
} else {
false
};
let props_emits_buf = profile!(
"atelier.script_inline.build_props_emits",
build_props_emits(&ctx, is_ts, needs_prop_type, needs_merge_defaults)
);
let model_infos: Vec<(String, String, Option<String>)> = profile!(
"atelier.script_inline.collect_model_infos",
collect_model_infos(&ctx)
);
let model_props_emits_buf = profile!(
"atelier.script_inline.build_model_props_emits",
build_model_props_emits(
&ctx,
&model_infos,
is_ts,
needs_prop_type,
needs_merge_defaults,
)
);
let transformed_setup: String = if let Some(ref destructure) = ctx.macros.props_destructure {
profile!(
"atelier.script_inline.transform_props_destructure",
transform_destructured_props(&setup_code, destructure)
)
} else {
setup_code
};
let (hoisted_lines, setup_body_lines) = profile!(
"atelier.script_inline.separate_hoisted",
separate_hoisted_consts(&transformed_setup, &ctx)
);
if !hoisted_lines.is_empty() {
for line in &hoisted_lines {
output.extend_from_slice(line.as_bytes());
output.push(b'\n');
}
}
output.push(b'\n');
let has_options = ctx.macros.define_options.is_some();
let has_emit = ctx.macros.define_emits.is_some();
let has_emit_binding = ctx
.macros
.define_emits
.as_ref()
.is_some_and(|emits| emits.binding_name.is_some());
let has_expose = ctx.macros.define_expose.is_some();
if has_options {
if is_vapor {
output.extend_from_slice(
b"export default /*@__PURE__*/_defineVaporComponent(Object.assign(",
);
} else {
output.extend_from_slice(b"export default /*@__PURE__*/Object.assign(");
}
let options_args = ctx.macros.define_options.as_ref().unwrap().args.trim();
output.extend_from_slice(options_args.as_bytes());
output.extend_from_slice(b", {\n");
} else if has_default_export {
if is_vapor {
output.extend_from_slice(
b"export default /*@__PURE__*/_defineVaporComponent(Object.assign(__default__, {\n",
);
} else {
output.extend_from_slice(b"export default /*@__PURE__*/Object.assign(__default__, {\n");
}
} else if is_vapor {
output.extend_from_slice(b"export default /*@__PURE__*/_defineVaporComponent({\n");
} else if is_ts {
output.extend_from_slice(b"export default /*@__PURE__*/_defineComponent({\n");
} else {
output.extend_from_slice(b"export default {\n");
}
output.extend_from_slice(b" __name: '");
output.extend_from_slice(component_name.as_bytes());
output.extend_from_slice(b"',\n");
output.extend_from_slice(&props_emits_buf);
output.extend_from_slice(&model_props_emits_buf);
if !template.render_fn.is_empty() {
output.extend_from_slice(b" ");
output.extend_from_slice(template.render_fn_name.as_bytes());
output.extend_from_slice(b": ");
if let Some(alias) = vapor_render_alias.as_ref() {
output.extend_from_slice(alias.as_bytes());
} else {
output.extend_from_slice(template.render_fn_name.as_bytes());
}
output.extend_from_slice(b",\n");
}
let mut setup_args = Vec::new();
if has_expose {
setup_args.push("expose: __expose");
}
if has_emit || needs_vapor_setup_context {
if has_emit_binding || needs_vapor_setup_context {
setup_args.push("emit: __emit");
} else {
setup_args.push("emit: $emit");
}
}
if needs_vapor_setup_context {
setup_args.push("attrs: __attrs");
setup_args.push("slots: __slots");
}
let has_typed_props = is_ts
&& ctx
.macros
.define_props
.as_ref()
.is_some_and(|p| p.type_args.is_some() || !p.args.is_empty());
let props_param = if has_typed_props && !needs_prop_type {
"__props: any"
} else {
"__props"
};
let async_prefix = if is_async {
" async setup("
} else {
" setup("
};
if setup_args.is_empty() {
output.extend_from_slice(async_prefix.as_bytes());
output.extend_from_slice(props_param.as_bytes());
output.extend_from_slice(b") {\n");
} else {
output.extend_from_slice(async_prefix.as_bytes());
output.extend_from_slice(props_param.as_bytes());
output.extend_from_slice(b", { ");
output.extend_from_slice(setup_args.join(", ").as_bytes());
output.extend_from_slice(b" }) {\n");
}
output.push(b'\n');
if is_async {
if is_ts {
output.extend_from_slice(b"let __temp: any, __restore: any\n\n");
} else {
output.extend_from_slice(b"let __temp, __restore\n\n");
}
}
if let Some(ref emits_macro) = ctx.macros.define_emits {
if let Some(ref binding_name) = emits_macro.binding_name {
output.extend_from_slice(b"const ");
output.extend_from_slice(binding_name.as_bytes());
output.extend_from_slice(b" = __emit\n");
}
}
if let Some(ref props_macro) = ctx.macros.define_props {
if let Some(ref binding_name) = props_macro.binding_name {
output.extend_from_slice(b"const ");
output.extend_from_slice(binding_name.as_bytes());
output.extend_from_slice(b" = __props\n");
}
}
if !model_infos.is_empty() {
for (model_name, binding_name, _) in &model_infos {
output.extend_from_slice(b"const ");
output.extend_from_slice(binding_name.as_bytes());
output.extend_from_slice(b" = _useModel(__props, \"");
output.extend_from_slice(model_name.as_bytes());
output.extend_from_slice(b"\")\n");
}
}
if let Some(ref slots_macro) = ctx.macros.define_slots {
if let Some(ref binding_name) = slots_macro.binding_name {
output.extend_from_slice(b"const ");
output.extend_from_slice(binding_name.as_bytes());
output.extend_from_slice(b" = _useSlots()\n");
}
}
if is_async {
let transformed_async = profile!(
"atelier.script_inline.transform_await",
transform_await_expressions(&setup_body_lines, source_is_ts)
);
for line in &transformed_async {
output.extend_from_slice(line.as_bytes());
output.push(b'\n');
}
} else {
for line in &setup_body_lines {
output.extend_from_slice(line.as_bytes());
output.push(b'\n');
}
}
if let Some(ref expose_macro) = ctx.macros.define_expose {
let args = expose_macro.args.trim();
output.extend_from_slice(b"__expose(");
output.extend_from_slice(args.as_bytes());
output.extend_from_slice(b")\n");
}
if has_css_vars {
output.extend_from_slice(b"_useCssVars((_ctx) => ({\n");
for (i, var_expr) in css_vars.iter().enumerate() {
output.extend_from_slice(b" \"");
output.extend_from_slice(scope_id.as_bytes());
output.extend_from_slice(b"-");
output.extend_from_slice(var_expr.as_bytes());
output.extend_from_slice(b"\": (_unref(");
output.extend_from_slice(var_expr.as_bytes());
output.extend_from_slice(b"))");
if i < css_vars.len() - 1 {
output.extend_from_slice(b",");
}
output.extend_from_slice(b"\n");
}
output.extend_from_slice(b"}))\n");
}
output.push(b'\n');
emit_render_return(
&mut output,
&template,
&setup_return_imports,
is_ts,
is_vapor,
vapor_render_alias.as_deref(),
&ctx,
);
output.extend_from_slice(b"}\n");
output.push(b'\n');
if is_vapor && (has_options || has_default_export) {
output.extend_from_slice(b"}))\n");
} else if has_options || has_default_export || is_ts || is_vapor {
output.extend_from_slice(b"})\n");
} else {
output.extend_from_slice(b"}\n");
}
#[allow(clippy::disallowed_types)]
let output_str: std::string::String =
unsafe { std::string::String::from_utf8_unchecked(output.into_iter().collect()) };
let final_code: String = if is_ts || !source_is_ts {
let mut code = output_str;
if is_ts {
code = code.replace("$event => (", "($event: any) => (");
code = code.replace("$event => { ", "($event: any) => { ");
}
code.into()
} else {
profile!(
"atelier.script_inline.ts_to_js",
transform_typescript_to_js(&output_str)
)
};
Ok(ScriptCompileResult {
code: final_code,
bindings: Some(ctx.bindings),
})
}
fn emit_render_return(
output: &mut vize_carton::Vec<u8>,
template: &TemplateParts<'_>,
imports: &[String],
is_ts: bool,
is_vapor: bool,
vapor_render_alias: Option<&str>,
ctx: &ScriptCompileContext,
) {
if !template.render_body.is_empty() {
if is_ts {
output.extend_from_slice(b"return (_ctx: any,_cache: any) => {\n");
} else {
output.extend_from_slice(b"return (_ctx, _cache) => {\n");
}
for line in template.preamble.lines() {
if !line.trim().is_empty() {
output.extend_from_slice(b" ");
output.extend_from_slice(line.as_bytes());
output.push(b'\n');
}
}
if !template.preamble.is_empty() {
output.push(b'\n');
}
if template.render_is_block {
for line in template.render_body.lines() {
if line.trim().is_empty() {
output.push(b'\n');
continue;
}
output.extend_from_slice(b" ");
output.extend_from_slice(line.as_bytes());
output.push(b'\n');
}
} else {
let mut first_line = true;
for line in template.render_body.lines() {
if first_line {
output.extend_from_slice(b" return ");
output.extend_from_slice(line.as_bytes());
first_line = false;
} else {
output.push(b'\n');
if !line.trim().is_empty() {
output.extend_from_slice(b" ");
}
output.extend_from_slice(line.as_bytes());
}
}
if first_line {
output.extend_from_slice(b" return null");
}
output.push(b'\n');
}
output.extend_from_slice(b"}\n");
} else {
let setup_bindings = collect_setup_bindings(ctx, imports, template);
if is_vapor && !template.render_fn.is_empty() {
let needs_template_ref_setter = template.render_fn.contains("_createTemplateRefSetter");
if needs_template_ref_setter {
output.extend_from_slice(b"const ");
output.extend_from_slice(VAPOR_TEMPLATE_REF_SETTER.as_bytes());
output.extend_from_slice(b" = _createTemplateRefSetter()\n");
}
output.extend_from_slice(b"const __returned__ = { ");
let mut binding_index = 0usize;
if needs_template_ref_setter {
output.extend_from_slice(VAPOR_TEMPLATE_REF_SETTER.as_bytes());
binding_index += 1;
}
for name in setup_bindings.iter() {
if binding_index > 0 {
output.extend_from_slice(b", ");
}
binding_index += 1;
output.extend_from_slice(name.as_bytes());
}
output.extend_from_slice(b" }\n");
output.extend_from_slice(b"Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n");
output.extend_from_slice(b"const __instance = _getCurrentInstance()\n");
output.extend_from_slice(b"const __ctx = _proxyRefs(__returned__)\n");
output.extend_from_slice(b"if (__instance) __instance.setupState = __ctx\n");
output.extend_from_slice(b"return ");
output.extend_from_slice(vapor_render_alias.unwrap_or("render").as_bytes());
output.extend_from_slice(b"(__ctx, __props, __emit, __attrs, __slots)\n");
} else if !setup_bindings.is_empty() {
output.extend_from_slice(b"return { ");
for (i, name) in setup_bindings.iter().enumerate() {
if i > 0 {
output.extend_from_slice(b", ");
}
output.extend_from_slice(name.as_bytes());
}
output.extend_from_slice(b" }\n");
} else if !template.render_fn.is_empty() {
output.extend_from_slice(b"return {}\n");
}
}
}
fn build_vapor_render_alias(
content: &str,
normal_script_content: Option<&str>,
template_render_fn: &str,
) -> String {
let mut suffix = 0usize;
loop {
let candidate = build_vapor_render_alias_candidate(suffix);
let candidate_str = candidate.as_str();
if !content.contains(candidate_str)
&& normal_script_content.is_none_or(|script| !script.contains(candidate_str))
&& !template_render_fn.contains(candidate_str)
{
return candidate;
}
suffix += 1;
}
}
fn build_vapor_render_alias_candidate(suffix: usize) -> String {
let mut candidate = String::from(VAPOR_RENDER_ALIAS_BASE);
if suffix == 0 {
return candidate;
}
candidate.push('_');
append_usize(&mut candidate, suffix);
candidate
}
fn append_usize(target: &mut String, value: usize) {
let mut buffer = [0u8; 20];
let mut index = buffer.len();
let mut remaining = value;
loop {
index -= 1;
buffer[index] = b'0' + (remaining % 10) as u8;
remaining /= 10;
if remaining == 0 {
break;
}
}
let digits = std::str::from_utf8(&buffer[index..]).expect("usize digits should be ASCII");
target.push_str(digits);
}
fn collect_setup_bindings(
ctx: &ScriptCompileContext,
imports: &[String],
template: &TemplateParts<'_>,
) -> Vec<String> {
use crate::types::BindingType;
let mut bindings: Vec<String> = ctx
.bindings
.bindings
.iter()
.filter(|(_, bt)| {
matches!(
bt,
BindingType::SetupLet
| BindingType::SetupMaybeRef
| BindingType::SetupRef
| BindingType::SetupReactiveConst
| BindingType::SetupConst
| BindingType::LiteralConst
)
})
.map(|(name, _)| String::from(name.as_str()))
.collect();
let has_template = !template.render_fn.is_empty() || !template.render_body.is_empty();
let mut template_code =
String::with_capacity(template.render_fn.len() + template.render_body.len());
template_code.push_str(template.render_fn);
template_code.push_str(template.render_body);
for import in imports {
for name in extract_import_identifiers(import) {
let should_return =
!has_template || template_references_setup_binding(&template_code, name.as_str());
if should_return && !bindings.iter().any(|binding| binding == name.as_str()) {
bindings.push(name);
}
}
}
bindings
}
fn template_references_setup_binding(template_code: &str, name: &str) -> bool {
let mut setup_access = String::with_capacity(name.len() + 8);
setup_access.push_str("$setup.");
setup_access.push_str(name);
if template_code.contains(setup_access.as_str()) {
return true;
}
let mut ctx_access = String::with_capacity(name.len() + 5);
ctx_access.push_str("_ctx.");
ctx_access.push_str(name);
template_code.contains(ctx_access.as_str())
}
fn parse_script_content(content: &str, is_ts: bool) -> (Vec<String>, Vec<String>, Vec<String>) {
if let Some(sections) = extract_script_sections(content, is_ts) {
return sections;
}
let mut user_imports = Vec::new();
let mut setup_lines = Vec::new();
let mut ts_declarations: Vec<String> = Vec::new();
let mut in_import = false;
let mut import_buffer = String::default();
let mut in_destructure = false;
let mut destructure_buffer = String::default();
let mut brace_depth: i32 = 0;
let mut in_macro_call = false;
let mut macro_angle_depth: i32 = 0;
let mut in_paren_macro_call = false;
let mut paren_macro_depth: i32 = 0;
let mut waiting_for_macro_close = false;
let mut in_destructure_call = false;
let mut destructure_call_paren_depth: i32 = 0;
let mut destructure_call_keep_lines = false; let mut in_object_literal = false;
let mut object_literal_buffer = String::default();
let mut object_literal_brace_depth: i32 = 0;
let mut in_ts_interface = false;
let mut ts_interface_brace_depth: i32 = 0;
let mut in_ts_type = false;
let mut ts_type_depth: i32 = 0; let mut ts_type_pending_end = false; let mut in_template_literal = false;
for line in content.lines() {
let trimmed = line.trim();
if in_macro_call {
let cleaned = strip_comments_for_counting(trimmed);
let line_no_arrow = cleaned.replace("=>", "");
macro_angle_depth += line_no_arrow.matches('<').count() as i32;
macro_angle_depth -= line_no_arrow.matches('>').count() as i32;
let trimmed_no_semi_m = trimmed.trim_end_matches(';');
if macro_angle_depth <= 0
&& (trimmed_no_semi_m.contains("()") || trimmed_no_semi_m.ends_with(')'))
{
in_macro_call = false;
}
continue;
}
if in_destructure_call {
let cleaned = strip_comments_for_counting(trimmed);
destructure_call_paren_depth += cleaned.matches('(').count() as i32;
destructure_call_paren_depth -= cleaned.matches(')').count() as i32;
if destructure_call_keep_lines {
setup_lines.push(line.to_compact_string());
}
if destructure_call_paren_depth <= 0 {
in_destructure_call = false;
}
continue;
}
if in_paren_macro_call {
let cleaned = strip_comments_for_counting(trimmed);
paren_macro_depth += cleaned.matches('(').count() as i32;
paren_macro_depth -= cleaned.matches(')').count() as i32;
if paren_macro_depth <= 0 {
in_paren_macro_call = false;
}
continue;
}
if waiting_for_macro_close {
destructure_buffer.push_str(line);
destructure_buffer.push('\n');
let cleaned = strip_comments_for_counting(trimmed);
let line_no_arrow = cleaned.replace("=>", "");
macro_angle_depth += line_no_arrow.matches('<').count() as i32;
macro_angle_depth -= line_no_arrow.matches('>').count() as i32;
let trimmed_no_semi_w = trimmed.trim_end_matches(';');
if macro_angle_depth <= 0
&& (trimmed_no_semi_w.ends_with("()") || trimmed_no_semi_w.ends_with(')'))
{
waiting_for_macro_close = false;
destructure_buffer.clear();
}
continue;
}
if in_destructure {
destructure_buffer.push_str(line);
destructure_buffer.push('\n');
let cleaned = strip_comments_for_counting(trimmed);
let line_no_arrow = cleaned.replace("=>", "");
brace_depth += cleaned.matches('{').count() as i32;
brace_depth -= cleaned.matches('}').count() as i32;
macro_angle_depth += line_no_arrow.matches('<').count() as i32;
macro_angle_depth -= line_no_arrow.matches('>').count() as i32;
if brace_depth <= 0 && macro_angle_depth <= 0 {
let is_props_macro = destructure_buffer.contains("defineProps")
|| destructure_buffer.contains("withDefaults");
let trimmed_no_semi = trimmed.trim_end_matches(';');
if is_props_macro
&& !trimmed_no_semi.ends_with("()")
&& !trimmed_no_semi.ends_with(')')
{
waiting_for_macro_close = true;
continue;
}
in_destructure = false;
if !is_props_macro {
for buf_line in destructure_buffer.lines() {
setup_lines.push(buf_line.to_compact_string());
}
}
let paren_balance = destructure_buffer.matches('(').count() as i32
- destructure_buffer.matches(')').count() as i32;
if paren_balance > 0 {
in_destructure_call = true;
destructure_call_paren_depth = paren_balance;
destructure_call_keep_lines = !is_props_macro;
}
destructure_buffer.clear();
}
continue;
}
if is_paren_macro_start(trimmed)
&& !trimmed.starts_with("const {")
&& !trimmed.starts_with("let {")
{
in_paren_macro_call = true;
paren_macro_depth =
trimmed.matches('(').count() as i32 - trimmed.matches(')').count() as i32;
continue;
}
if is_multiline_macro_start(trimmed)
&& !trimmed.starts_with("const {")
&& !trimmed.starts_with("let {")
{
in_macro_call = true;
macro_angle_depth =
trimmed.matches('<').count() as i32 - trimmed.matches('>').count() as i32;
continue;
}
if (trimmed.starts_with("const {")
|| trimmed.starts_with("let {")
|| trimmed.starts_with("var {"))
&& (trimmed.contains("defineProps<") || trimmed.contains("withDefaults("))
{
let trimmed_no_semi_d = trimmed.trim_end_matches(';');
if !trimmed_no_semi_d.ends_with("()") && !trimmed_no_semi_d.ends_with(')') {
in_destructure = true;
destructure_buffer = line.to_compact_string() + "\n";
brace_depth =
trimmed.matches('{').count() as i32 - trimmed.matches('}').count() as i32;
macro_angle_depth =
trimmed.matches('<').count() as i32 - trimmed.matches('>').count() as i32;
continue;
} else {
continue;
}
}
if (trimmed.starts_with("const {")
|| trimmed.starts_with("let {")
|| trimmed.starts_with("var {"))
&& trimmed.contains('}')
&& trimmed.ends_with('=')
{
in_destructure = true;
destructure_buffer = line.to_compact_string() + "\n";
brace_depth = 0; macro_angle_depth = 0;
continue;
}
if (trimmed.starts_with("const {")
|| trimmed.starts_with("let {")
|| trimmed.starts_with("var {"))
&& !trimmed.contains('}')
{
in_destructure = true;
destructure_buffer = line.to_compact_string() + "\n";
brace_depth = trimmed.matches('{').count() as i32 - trimmed.matches('}').count() as i32;
macro_angle_depth = 0;
continue;
}
if is_props_destructure_line(trimmed) {
continue;
}
if in_object_literal {
object_literal_buffer.push_str(line);
object_literal_buffer.push('\n');
object_literal_brace_depth += trimmed.matches('{').count() as i32;
object_literal_brace_depth -= trimmed.matches('}').count() as i32;
if object_literal_brace_depth <= 0 {
for buf_line in object_literal_buffer.lines() {
setup_lines.push(buf_line.to_compact_string());
}
in_object_literal = false;
object_literal_buffer.clear();
}
continue;
}
if (trimmed.starts_with("const ")
|| trimmed.starts_with("let ")
|| trimmed.starts_with("var "))
&& trimmed.contains('=')
&& trimmed.ends_with('{')
&& !trimmed.contains("defineProps")
&& !trimmed.contains("defineEmits")
&& !trimmed.contains("defineModel")
{
in_object_literal = true;
object_literal_buffer = line.to_compact_string() + "\n";
object_literal_brace_depth =
trimmed.matches('{').count() as i32 - trimmed.matches('}').count() as i32;
continue;
}
let backtick_count = line
.chars()
.fold((0, false), |(count, escaped), c| {
if escaped {
(count, false)
} else if c == '\\' {
(count, true)
} else if c == '`' {
(count + 1, false)
} else {
(count, false)
}
})
.0;
let was_in_template_literal = in_template_literal;
if backtick_count % 2 == 1 {
in_template_literal = !in_template_literal;
}
if was_in_template_literal {
if !trimmed.is_empty() && !is_macro_call_line(trimmed) {
setup_lines.push(line.to_compact_string());
}
continue;
}
if trimmed.starts_with("import ") {
if !trimmed.contains(" from ") && (trimmed.contains('\'') || trimmed.contains('"')) {
let mut imp = String::with_capacity(line.len() + 1);
imp.push_str(line);
imp.push('\n');
user_imports.push(imp);
continue;
}
in_import = true;
import_buffer.clear();
}
if in_import {
import_buffer.push_str(line);
import_buffer.push('\n');
if trimmed.ends_with(';') || (trimmed.contains(" from ") && !trimmed.ends_with(',')) {
user_imports.push(import_buffer.clone());
in_import = false;
}
continue;
}
if in_ts_interface {
if is_ts {
if let Some(last) = ts_declarations.last_mut() {
last.push('\n');
last.push_str(line);
}
}
ts_interface_brace_depth += trimmed.matches('{').count() as i32;
ts_interface_brace_depth -= trimmed.matches('}').count() as i32;
if ts_interface_brace_depth <= 0 {
in_ts_interface = false;
}
continue;
}
if trimmed.starts_with("interface ") || trimmed.starts_with("export interface ") {
in_ts_interface = true;
ts_interface_brace_depth =
trimmed.matches('{').count() as i32 - trimmed.matches('}').count() as i32;
if is_ts {
ts_declarations.push(line.to_compact_string());
}
if ts_interface_brace_depth <= 0 {
in_ts_interface = false;
}
continue;
}
if trimmed.starts_with("declare ") {
let has_brace = trimmed.contains('{');
if has_brace {
let depth =
trimmed.matches('{').count() as i32 - trimmed.matches('}').count() as i32;
if depth > 0 {
in_ts_interface = true;
ts_interface_brace_depth = depth;
}
if is_ts {
ts_declarations.push(line.to_compact_string());
}
} else {
if is_ts {
ts_declarations.push(line.to_compact_string());
}
}
continue;
}
if in_ts_type {
let is_type_continuation = trimmed.starts_with('|')
|| trimmed.starts_with('&')
|| trimmed.starts_with('?')
|| trimmed.starts_with(':');
if ts_type_pending_end {
ts_type_pending_end = false;
if is_type_continuation {
if is_ts {
if let Some(last) = ts_declarations.last_mut() {
last.push('\n');
last.push_str(line);
}
}
let cleaned = strip_comments_for_counting(trimmed);
let line_no_arrow = cleaned.replace("=>", "__");
ts_type_depth += cleaned.matches('{').count() as i32;
ts_type_depth -= cleaned.matches('}').count() as i32;
ts_type_depth += line_no_arrow.matches('<').count() as i32;
ts_type_depth -= line_no_arrow.matches('>').count() as i32;
ts_type_depth += cleaned.matches('(').count() as i32;
ts_type_depth -= cleaned.matches(')').count() as i32;
continue;
} else {
in_ts_type = false;
}
}
if in_ts_type {
if is_ts {
if let Some(last) = ts_declarations.last_mut() {
last.push('\n');
last.push_str(line);
}
}
let cleaned = strip_comments_for_counting(trimmed);
let line_no_arrow = cleaned.replace("=>", "__");
ts_type_depth += cleaned.matches('{').count() as i32;
ts_type_depth -= cleaned.matches('}').count() as i32;
ts_type_depth += line_no_arrow.matches('<').count() as i32;
ts_type_depth -= line_no_arrow.matches('>').count() as i32;
ts_type_depth += cleaned.matches('(').count() as i32;
ts_type_depth -= cleaned.matches(')').count() as i32;
if ts_type_depth <= 0
&& (trimmed.ends_with(';')
|| (!is_type_continuation
&& !trimmed.ends_with('|')
&& !trimmed.ends_with('&')
&& !trimmed.ends_with(',')
&& !trimmed.ends_with('{')))
{
if trimmed.ends_with('}') && !trimmed.ends_with("};") {
ts_type_pending_end = true;
} else {
in_ts_type = false;
}
}
continue;
}
}
if (trimmed.starts_with("type ")
&& trimmed[5..]
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '{'))
|| (trimmed.starts_with("export type ")
&& trimmed[12..]
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_' || c == '{'))
{
let has_equals = trimmed.contains('=');
if has_equals {
let line_no_arrow = trimmed.replace("=>", "__");
ts_type_depth = trimmed.matches('{').count() as i32
- trimmed.matches('}').count() as i32
+ line_no_arrow.matches('<').count() as i32
- line_no_arrow.matches('>').count() as i32
+ trimmed.matches('(').count() as i32
- trimmed.matches(')').count() as i32;
if ts_type_depth <= 0 {
if trimmed.ends_with(';') {
if is_ts {
ts_declarations.push(line.to_compact_string());
}
continue;
}
if !trimmed.ends_with('|')
&& !trimmed.ends_with('&')
&& !trimmed.ends_with(',')
&& !trimmed.ends_with('{')
&& !trimmed.ends_with('=')
{
if is_ts {
ts_declarations.push(line.to_compact_string());
}
in_ts_type = true;
ts_type_pending_end = true;
continue;
}
}
if is_ts {
ts_declarations.push(line.to_compact_string());
}
in_ts_type = true;
} else {
if is_ts {
ts_declarations.push(line.to_compact_string());
}
}
continue;
}
if !trimmed.is_empty() && !is_macro_call_line(trimmed) {
setup_lines.push(line.to_compact_string());
}
}
(user_imports, setup_lines, ts_declarations)
}
fn build_props_emits(
ctx: &ScriptCompileContext,
_is_ts: bool,
needs_prop_type: bool,
needs_merge_defaults: bool,
) -> Vec<u8> {
let mut props_emits_buf: Vec<u8> = Vec::new();
let with_defaults_args = ctx
.macros
.with_defaults
.as_ref()
.map(|wd| extract_with_defaults_defaults(&wd.args));
let model_infos: Vec<(String, String, Option<String>)> = collect_model_infos(ctx);
if let Some(ref props_macro) = ctx.macros.define_props {
if let Some(ref type_args) = props_macro.type_args {
let resolved_type_args =
resolve_type_args(type_args, &ctx.interfaces, &ctx.type_aliases);
let prop_types = extract_prop_types_from_type(&resolved_type_args);
if !prop_types.is_empty() || !model_infos.is_empty() {
props_emits_buf.extend_from_slice(b" props: {\n");
let total_items = prop_types.len() + model_infos.len();
let mut item_idx = 0;
for (name, prop_type) in &prop_types {
item_idx += 1;
let resolved_js_type = if prop_type.js_type == "null" {
if let Some(ref ts_type) = prop_type.ts_type {
super::super::props::resolve_prop_js_type(
ts_type,
&ctx.interfaces,
&ctx.type_aliases,
)
.unwrap_or_else(|| prop_type.js_type.clone())
} else {
prop_type.js_type.clone()
}
} else {
prop_type.js_type.clone()
};
let runtime_js_type =
add_null_to_runtime_type(&resolved_js_type, prop_type.nullable);
props_emits_buf.extend_from_slice(b" ");
props_emits_buf.extend_from_slice(name.as_bytes());
props_emits_buf.extend_from_slice(b": { type: ");
props_emits_buf.extend_from_slice(runtime_js_type.as_bytes());
if needs_prop_type {
if let Some(ref ts_type) = prop_type.ts_type {
if resolved_js_type == "null" {
props_emits_buf.extend_from_slice(b" as unknown as PropType<");
} else {
props_emits_buf.extend_from_slice(b" as PropType<");
}
let normalized =
ts_type.split_whitespace().collect::<Vec<_>>().join(" ");
props_emits_buf.extend_from_slice(normalized.as_bytes());
props_emits_buf.push(b'>');
}
}
props_emits_buf.extend_from_slice(b", required: ");
props_emits_buf.extend_from_slice(if prop_type.optional {
b"false"
} else {
b"true"
});
let mut has_default = false;
if let Some(ref defaults) = with_defaults_args {
if let Some(default_val) = defaults.get(name.as_str()) {
props_emits_buf.extend_from_slice(b", default: ");
props_emits_buf.extend_from_slice(default_val.as_bytes());
has_default = true;
}
}
if !has_default {
if let Some(ref destructure) = ctx.macros.props_destructure {
if let Some(binding) = destructure.bindings.get(name.as_str()) {
if let Some(ref default_val) = binding.default {
props_emits_buf.extend_from_slice(b", default: ");
props_emits_buf.extend_from_slice(default_val.as_bytes());
}
}
}
}
props_emits_buf.extend_from_slice(b" }");
if item_idx < total_items {
props_emits_buf.push(b',');
}
props_emits_buf.push(b'\n');
}
for (model_name, _, options) in &model_infos {
props_emits_buf.extend_from_slice(b" \"");
props_emits_buf.extend_from_slice(model_name.as_bytes());
props_emits_buf.extend_from_slice(b"\": ");
if let Some(opts) = options {
props_emits_buf.extend_from_slice(opts.as_bytes());
} else {
props_emits_buf.extend_from_slice(b"{}");
}
props_emits_buf.extend_from_slice(b",\n");
}
if props_emits_buf.ends_with(b",\n") {
let len = props_emits_buf.len();
props_emits_buf[len - 2] = b'\n';
props_emits_buf.truncate(len - 1);
}
props_emits_buf.extend_from_slice(b" },\n");
}
} else if !props_macro.args.is_empty() {
if needs_merge_defaults {
let destructure = ctx.macros.props_destructure.as_ref().unwrap();
props_emits_buf.extend_from_slice(b" props: /*@__PURE__*/_mergeDefaults(");
props_emits_buf.extend_from_slice(props_macro.args.as_bytes());
props_emits_buf.extend_from_slice(b", {\n");
let defaults: Vec<_> = destructure
.bindings
.iter()
.filter_map(|(k, b)| b.default.as_ref().map(|d| (k.as_str(), d.as_str())))
.collect();
for (i, (key, default_val)) in defaults.iter().enumerate() {
props_emits_buf.extend_from_slice(b" ");
props_emits_buf.extend_from_slice(key.as_bytes());
props_emits_buf.extend_from_slice(b": ");
props_emits_buf.extend_from_slice(default_val.as_bytes());
if i < defaults.len() - 1 {
props_emits_buf.push(b',');
}
props_emits_buf.push(b'\n');
}
props_emits_buf.extend_from_slice(b"}),\n");
} else {
props_emits_buf.extend_from_slice(b" props: ");
props_emits_buf.extend_from_slice(props_macro.args.as_bytes());
props_emits_buf.extend_from_slice(b",\n");
}
}
}
props_emits_buf
}
fn build_model_props_emits(
ctx: &ScriptCompileContext,
model_infos: &[(String, String, Option<String>)],
_is_ts: bool,
_needs_prop_type: bool,
_needs_merge_defaults: bool,
) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
if !model_infos.is_empty() && ctx.macros.define_props.is_none() {
buf.extend_from_slice(b" props: {\n");
for (model_name, _binding_name, options) in model_infos {
buf.extend_from_slice(b" \"");
buf.extend_from_slice(model_name.as_bytes());
buf.extend_from_slice(b"\": ");
if let Some(opts) = options {
buf.extend_from_slice(opts.as_bytes());
} else {
buf.extend_from_slice(b"{}");
}
buf.extend_from_slice(b",\n");
buf.extend_from_slice(b" \"");
if model_name == "modelValue" {
buf.extend_from_slice(b"modelModifiers");
} else {
buf.extend_from_slice(model_name.as_bytes());
buf.extend_from_slice(b"Modifiers");
}
buf.extend_from_slice(b"\": {},\n");
}
buf.extend_from_slice(b" },\n");
}
let mut all_emits: Vec<String> = Vec::new();
if let Some(ref emits_macro) = ctx.macros.define_emits {
if !emits_macro.args.is_empty() {
let args = emits_macro.args.trim();
if args.starts_with('[') && args.ends_with(']') {
let inner = &args[1..args.len() - 1];
for part in inner.split(',') {
let name = part.trim().trim_matches(|c| c == '\'' || c == '"');
if !name.is_empty() {
all_emits.push(name.to_compact_string());
}
}
}
} else if let Some(ref type_args) = emits_macro.type_args {
let emit_names = extract_emit_names_from_type(type_args);
all_emits.extend(emit_names);
}
}
for (model_name, _, _) in model_infos {
let mut name = String::with_capacity(7 + model_name.len());
name.push_str("update:");
name.push_str(model_name);
all_emits.push(name);
}
if !all_emits.is_empty() {
buf.extend_from_slice(b" emits: [");
for (i, name) in all_emits.iter().enumerate() {
if i > 0 {
buf.extend_from_slice(b", ");
}
buf.push(b'"');
buf.extend_from_slice(name.as_bytes());
buf.push(b'"');
}
buf.extend_from_slice(b"],\n");
}
buf
}
fn collect_model_infos(ctx: &ScriptCompileContext) -> Vec<(String, String, Option<String>)> {
ctx.macros
.define_models
.iter()
.map(|m| {
let model_name = if m.args.trim().is_empty() {
"modelValue".to_compact_string()
} else {
let args = m.args.trim();
if args.starts_with('\'') || args.starts_with('"') {
args.trim_matches(|c| c == '\'' || c == '"')
.split(',')
.next()
.unwrap_or("modelValue")
.trim_matches(|c| c == '\'' || c == '"')
.to_compact_string()
} else {
"modelValue".to_compact_string()
}
};
let binding_name = m
.binding_name
.as_deref()
.map(String::from)
.unwrap_or_else(|| model_name.clone());
let options = if m.args.trim().is_empty() {
None
} else {
let args = m.args.trim();
if args.starts_with('{') {
Some(args.to_compact_string())
} else if args.contains(',') {
args.split_once(',')
.map(|(_, opts)| opts.trim().to_compact_string())
} else {
None
}
};
(model_name, binding_name, options)
})
.collect()
}
fn separate_hoisted_consts(
transformed_setup: &str,
ctx: &ScriptCompileContext,
) -> (Vec<String>, Vec<String>) {
let mut hoisted_lines: Vec<String> = Vec::new();
let mut setup_body_lines: Vec<String> = Vec::new();
let mut in_multiline_value = false;
for line in transformed_setup.lines() {
let trimmed = line.trim();
if in_multiline_value {
setup_body_lines.push(line.to_compact_string());
let backticks = trimmed
.chars()
.fold((0usize, false), |(count, escaped), c| {
if escaped {
(count, false)
} else if c == '\\' {
(count, true)
} else if c == '`' {
(count + 1, false)
} else {
(count, false)
}
})
.0;
if backticks % 2 == 1 {
in_multiline_value = false;
}
continue;
}
if trimmed.starts_with("const ") && !trimmed.starts_with("const {") {
if let Some(eq_pos) = trimmed.find('=') {
let value_part = trimmed[eq_pos + 1..].trim();
if value_part.is_empty() {
setup_body_lines.push(line.to_compact_string());
continue;
}
let backticks = value_part
.chars()
.fold((0usize, false), |(count, escaped), c| {
if escaped {
(count, false)
} else if c == '\\' {
(count, true)
} else if c == '`' {
(count + 1, false)
} else {
(count, false)
}
})
.0;
if backticks % 2 == 1 {
in_multiline_value = true;
setup_body_lines.push(line.to_compact_string());
continue;
}
}
if let Some(name) = extract_const_name(trimmed) {
if matches!(
ctx.bindings.bindings.get(name.as_str()),
Some(crate::types::BindingType::LiteralConst)
) {
hoisted_lines.push(line.to_compact_string());
continue;
}
}
}
setup_body_lines.push(line.to_compact_string());
}
(hoisted_lines, setup_body_lines)
}
fn transform_await_expressions(lines: &[String], is_ts: bool) -> Vec<String> {
let mut source = String::default();
for (idx, line) in lines.iter().enumerate() {
if idx > 0 {
source.push('\n');
}
source.push_str(line);
}
transform_await_source(&source, is_ts)
.lines()
.map(|line| line.to_compact_string())
.collect()
}
const AWAIT_WRAP_PREFIX: &str = "async function __vize_async_setup__() {\n";
const AWAIT_WRAP_SUFFIX: &str = "\n}";
fn transform_await_source(source: &str, is_ts: bool) -> String {
if source.trim().is_empty() {
return source.to_compact_string();
}
let mut wrapped =
String::with_capacity(AWAIT_WRAP_PREFIX.len() + source.len() + AWAIT_WRAP_SUFFIX.len());
wrapped.push_str(AWAIT_WRAP_PREFIX);
wrapped.push_str(source);
wrapped.push_str(AWAIT_WRAP_SUFFIX);
let allocator = Allocator::default();
let source_type = SourceType::default().with_typescript(is_ts);
let parse_result = Parser::new(&allocator, &wrapped, source_type).parse();
if !parse_result.errors.is_empty() {
return source.to_compact_string();
}
let Some(Statement::FunctionDeclaration(func)) = parse_result.program.body.first() else {
return source.to_compact_string();
};
let Some(body) = &func.body else {
return source.to_compact_string();
};
let offset = AWAIT_WRAP_PREFIX.len();
let mut cursor = 0usize;
let mut transformed = String::with_capacity(source.len() + 128);
for stmt in body.statements.iter() {
let stmt_span = stmt.span();
let Some(stmt_start) = stmt_span.start.try_into().ok().and_then(|start: usize| {
start
.checked_sub(offset)
.filter(|start| *start <= source.len())
}) else {
return source.to_compact_string();
};
let Some(stmt_end) = stmt_span
.end
.try_into()
.ok()
.and_then(|end: usize| end.checked_sub(offset).filter(|end| *end <= source.len()))
else {
return source.to_compact_string();
};
if stmt_start < cursor || stmt_start > stmt_end {
return source.to_compact_string();
}
transformed.push_str(&source[cursor..stmt_start]);
if let Some(replacement) = transform_await_statement(source, stmt, offset) {
transformed.push_str(&replacement);
} else {
transformed.push_str(&source[stmt_start..stmt_end]);
}
cursor = stmt_end;
}
transformed.push_str(&source[cursor..]);
transformed
}
fn transform_await_statement(source: &str, stmt: &Statement<'_>, offset: usize) -> Option<String> {
match stmt {
Statement::ExpressionStatement(expr_stmt) => {
let Expression::AwaitExpression(await_expr) = &expr_stmt.expression else {
return None;
};
build_standalone_await_replacement(source, stmt.span(), await_expr.span(), offset)
}
Statement::VariableDeclaration(var_decl) => {
if var_decl.declarations.len() != 1 {
return None;
}
let declarator = var_decl.declarations.first()?;
let init = declarator.init.as_ref()?;
let Expression::AwaitExpression(await_expr) = init else {
return None;
};
build_await_assignment_replacement(source, stmt.span(), await_expr.span(), offset)
}
_ => None,
}
}
fn build_await_assignment_replacement(
source: &str,
stmt_span: oxc_span::Span,
await_span: oxc_span::Span,
offset: usize,
) -> Option<String> {
let stmt_start = stmt_span.start as usize - offset;
let stmt_end = stmt_span.end as usize - offset;
let await_start = await_span.start as usize - offset;
let await_end = await_span.end as usize - offset;
let prefix = source.get(stmt_start..await_start)?;
let expr = await_expression_source(source, await_start, await_end)?;
let suffix = source.get(await_end..stmt_end)?;
let mut out = String::with_capacity(prefix.len() + expr.len() + suffix.len() + 96);
out.push_str(prefix);
out.push_str(" (\n");
out.push_str(" ([__temp,__restore] = _withAsyncContext(() => ");
out.push_str(expr);
out.push_str(")),\n");
out.push_str(" __temp = await __temp,\n");
out.push_str(" __restore(),\n");
out.push_str(" __temp\n");
out.push(')');
out.push_str(suffix);
Some(out)
}
fn build_standalone_await_replacement(
source: &str,
stmt_span: oxc_span::Span,
await_span: oxc_span::Span,
offset: usize,
) -> Option<String> {
let stmt_start = stmt_span.start as usize - offset;
let stmt_end = stmt_span.end as usize - offset;
let await_start = await_span.start as usize - offset;
let await_end = await_span.end as usize - offset;
if stmt_start != await_start {
return None;
}
let expr = await_expression_source(source, await_start, await_end)?;
let suffix = source.get(await_end..stmt_end)?;
let mut out = String::with_capacity(expr.len() + suffix.len() + 72);
out.push_str(";(\n");
out.push_str(" ([__temp,__restore] = _withAsyncContext(() => ");
out.push_str(expr);
out.push_str(")),\n");
out.push_str(" await __temp,\n");
out.push_str(" __restore()\n");
out.push(')');
out.push_str(suffix);
Some(out)
}
fn await_expression_source(source: &str, start: usize, end: usize) -> Option<&str> {
let await_source = source.get(start..end)?;
let expr = await_source.strip_prefix("await")?.trim_start();
if expr.is_empty() {
return None;
}
Some(expr)
}