use std::{mem, ops::ControlFlow, path::Path};
use oxc_allocator::Allocator;
use oxc_ast::ast::Program;
use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn};
use oxc_diagnostics::OxcDiagnostic;
use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions};
use oxc_mangler::{MangleOptions, Mangler, ManglerReturn};
use oxc_minifier::{CompressOptions, Compressor};
use oxc_parser::{ParseOptions, Parser, ParserReturn};
use oxc_semantic::{Scoping, SemanticBuilder, SemanticBuilderReturn};
use oxc_span::SourceType;
use oxc_transformer::{TransformOptions, Transformer, TransformerReturn};
use oxc_transformer_plugins::{
InjectGlobalVariables, InjectGlobalVariablesConfig, ReplaceGlobalDefines,
ReplaceGlobalDefinesConfig,
};
#[derive(Default)]
pub struct Compiler {
printed: String,
errors: Vec<OxcDiagnostic>,
}
impl CompilerInterface for Compiler {
fn handle_errors(&mut self, errors: Vec<OxcDiagnostic>) {
self.errors.extend(errors);
}
fn after_codegen(&mut self, ret: CodegenReturn) {
self.printed = ret.code;
}
}
impl Compiler {
pub fn execute(
&mut self,
source_text: &str,
source_type: SourceType,
source_path: &Path,
) -> Result<String, Vec<OxcDiagnostic>> {
self.compile(source_text, source_type, source_path);
if self.errors.is_empty() {
Ok(mem::take(&mut self.printed))
} else {
Err(mem::take(&mut self.errors))
}
}
}
pub trait CompilerInterface {
fn handle_errors(&mut self, _errors: Vec<OxcDiagnostic>) {}
fn enable_sourcemap(&self) -> bool {
false
}
fn parse_options(&self) -> ParseOptions {
ParseOptions::default()
}
fn isolated_declaration_options(&self) -> Option<IsolatedDeclarationsOptions> {
None
}
fn transform_options(&self) -> Option<&TransformOptions> {
None
}
fn define_options(&self) -> Option<ReplaceGlobalDefinesConfig> {
None
}
fn inject_options(&self) -> Option<InjectGlobalVariablesConfig> {
None
}
fn compress_options(&self) -> Option<CompressOptions> {
None
}
fn mangle_options(&self) -> Option<MangleOptions> {
None
}
fn codegen_options(&self) -> Option<CodegenOptions> {
Some(CodegenOptions::default())
}
fn check_semantic_error(&self) -> bool {
true
}
fn after_parse(&mut self, _parser_return: &mut ParserReturn) -> ControlFlow<()> {
ControlFlow::Continue(())
}
fn after_semantic(&mut self, _semantic_return: &mut SemanticBuilderReturn) -> ControlFlow<()> {
ControlFlow::Continue(())
}
fn after_isolated_declarations(&mut self, _ret: CodegenReturn) {}
fn after_transform(
&mut self,
_program: &mut Program<'_>,
_transformer_return: &mut TransformerReturn,
) -> ControlFlow<()> {
ControlFlow::Continue(())
}
fn after_codegen(&mut self, _ret: CodegenReturn) {}
fn compile(&mut self, source_text: &str, source_type: SourceType, source_path: &Path) {
let allocator = Allocator::default();
let mut parser_return = self.parse(&allocator, source_text, source_type);
if self.after_parse(&mut parser_return).is_break() {
return;
}
if !parser_return.errors.is_empty() {
self.handle_errors(parser_return.errors);
}
let mut program = parser_return.program;
if let Some(options) = self.isolated_declaration_options() {
self.isolated_declaration(options, &allocator, &program, source_path);
}
let mut semantic_return = self.semantic(&program);
if !semantic_return.errors.is_empty() {
self.handle_errors(semantic_return.errors);
return;
}
if self.after_semantic(&mut semantic_return).is_break() {
return;
}
let stats = semantic_return.semantic.stats();
let mut scoping = semantic_return.semantic.into_scoping();
if let Some(options) = self.transform_options() {
let mut transformer_return =
self.transform(options, &allocator, &mut program, source_path, scoping);
if !transformer_return.errors.is_empty() {
self.handle_errors(transformer_return.errors);
return;
}
if self.after_transform(&mut program, &mut transformer_return).is_break() {
return;
}
(scoping) = transformer_return.scoping;
}
let inject_options = self.inject_options();
let define_options = self.define_options();
if inject_options.is_some() || define_options.is_some() {
scoping =
SemanticBuilder::new().with_stats(stats).build(&program).semantic.into_scoping();
}
if let Some(options) = inject_options {
let ret = InjectGlobalVariables::new(&allocator, options).build(scoping, &mut program);
scoping = ret.scoping;
}
if let Some(options) = define_options {
let ret = ReplaceGlobalDefines::new(&allocator, options).build(scoping, &mut program);
scoping = ret.scoping;
if self.compress_options().is_none() {
Compressor::new(&allocator).dead_code_elimination_with_scoping(
&mut program,
scoping,
CompressOptions::dce(),
);
}
}
if let Some(options) = self.compress_options() {
self.compress(&allocator, &mut program, options);
}
let mangler = self.mangle_options().map(|options| self.mangle(&mut program, options));
if let Some(options) = self.codegen_options() {
let ret = self.codegen(&program, source_path, mangler, options);
self.after_codegen(ret);
}
}
fn parse<'a>(
&self,
allocator: &'a Allocator,
source_text: &'a str,
source_type: SourceType,
) -> ParserReturn<'a> {
Parser::new(allocator, source_text, source_type).with_options(self.parse_options()).parse()
}
fn semantic<'a>(&self, program: &'a Program<'a>) -> SemanticBuilderReturn<'a> {
let mut builder = SemanticBuilder::new();
if self.transform_options().is_some() {
builder = builder.with_excess_capacity(2.0).with_enum_eval(true);
}
builder.with_check_syntax_error(self.check_semantic_error()).build(program)
}
fn isolated_declaration<'a>(
&mut self,
options: IsolatedDeclarationsOptions,
allocator: &'a Allocator,
program: &Program<'a>,
source_path: &Path,
) {
let ret = IsolatedDeclarations::new(allocator, options).build(program);
self.handle_errors(ret.errors);
let ret = self.codegen(
&ret.program,
source_path,
None,
self.codegen_options().unwrap_or_default(),
);
self.after_isolated_declarations(ret);
}
fn transform<'a>(
&self,
options: &TransformOptions,
allocator: &'a Allocator,
program: &mut Program<'a>,
source_path: &Path,
scoping: Scoping,
) -> TransformerReturn {
Transformer::new(allocator, source_path, options).build_with_scoping(scoping, program)
}
fn compress<'a>(
&self,
allocator: &'a Allocator,
program: &mut Program<'a>,
options: CompressOptions,
) {
Compressor::new(allocator).build(program, options);
}
fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> ManglerReturn {
Mangler::new().with_options(options).build(program)
}
fn codegen(
&self,
program: &Program<'_>,
source_path: &Path,
mangler_return: Option<ManglerReturn>,
options: CodegenOptions,
) -> CodegenReturn {
let mut options = options;
if self.enable_sourcemap() {
options.source_map_path = Some(source_path.to_path_buf());
}
let (scoping, class_private_mappings) = mangler_return
.map(|m| (Some(m.scoping), Some(m.class_private_mappings)))
.unwrap_or_default();
Codegen::new()
.with_options(options)
.with_scoping(scoping)
.with_private_member_mappings(class_private_mappings)
.build(program)
}
}