use vize_carton::{profile, String, ToCompactString};
pub(super) fn extract_normal_script_content(
content: &str,
source_is_ts: bool,
output_is_ts: bool,
) -> String {
use oxc_allocator::Allocator;
use oxc_ast::ast::Statement;
use oxc_codegen::Codegen;
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::{GetSpan, SourceType};
use oxc_transformer::{TransformOptions, Transformer, TypeScriptOptions};
let source_type = if source_is_ts {
SourceType::ts()
} else {
SourceType::mjs()
};
let allocator = Allocator::default();
let ret = profile!(
"atelier.normal_script.extract.parse",
Parser::new(&allocator, content, source_type).parse()
);
if !ret.errors.is_empty() {
return content
.lines()
.filter(|line| !line.trim().starts_with("export default"))
.collect::<Vec<_>>()
.join("\n")
.into();
}
let program = ret.program;
let mut output = String::default();
let mut last_end = 0;
let mut skip_spans: Vec<(u32, u32)> = Vec::new();
let mut rewrites: Vec<(u32, u32, String)> = Vec::new();
for stmt in program.body.iter() {
match stmt {
Statement::ExportDefaultDeclaration(decl) => {
let stmt_start = stmt.span().start;
let stmt_end = stmt.span().end;
let stmt_text = &content[stmt_start as usize..stmt_end as usize];
let rewritten: String = stmt_text
.replacen("export default", "const __default__ =", 1)
.into();
rewrites.push((stmt_start, stmt_end, rewritten));
let _ = decl; }
Statement::ExportNamedDeclaration(decl) => {
let has_default_export = decl.specifiers.iter().any(|s| {
matches!(&s.exported, oxc_ast::ast::ModuleExportName::IdentifierName(name) if name.name == "default")
|| matches!(&s.exported, oxc_ast::ast::ModuleExportName::IdentifierReference(name) if name.name == "default")
});
if has_default_export {
skip_spans.push((stmt.span().start, stmt.span().end));
}
}
_ => {}
}
}
let mut modifications: Vec<(u32, u32, Option<String>)> = Vec::new();
for (start, end, replacement) in rewrites {
modifications.push((start, end, Some(replacement)));
}
for (start, end) in &skip_spans {
modifications.push((*start, *end, None));
}
modifications.sort_by_key(|m| m.0);
for (start, end, replacement) in &modifications {
output.push_str(&content[last_end..*start as usize]);
if let Some(repl) = replacement {
output.push_str(repl);
}
last_end = *end as usize;
}
if last_end < content.len() {
output.push_str(&content[last_end..]);
}
let extracted = output.trim().to_compact_string();
if source_is_ts && !output_is_ts {
let allocator2 = Allocator::default();
let ret2 = profile!(
"atelier.normal_script.extract.ts_parse",
Parser::new(&allocator2, &extracted, SourceType::ts()).parse()
);
if ret2.errors.is_empty() {
let mut program2 = ret2.program;
let semantic_ret = profile!(
"atelier.normal_script.extract.ts_semantic",
SemanticBuilder::new().build(&program2)
);
if semantic_ret.errors.is_empty() {
let scoping = semantic_ret.semantic.into_scoping();
let transform_options = TransformOptions {
typescript: TypeScriptOptions {
only_remove_type_imports: true,
..Default::default()
},
..Default::default()
};
let transform_ret = profile!(
"atelier.normal_script.extract.ts_transform",
Transformer::new(&allocator2, std::path::Path::new(""), &transform_options)
.build_with_scoping(scoping, &mut program2)
);
if transform_ret.errors.is_empty() {
return profile!(
"atelier.normal_script.extract.ts_codegen",
Codegen::new().build(&program2)
)
.code
.into();
}
}
}
}
extracted
}