Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions crates/oxc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ workspace = true
test = false
doctest = false

[[example]]
name = "compiler"
path = "examples/compiler.rs"
required-features = ["full"]

[dependencies]
oxc_allocator = { workspace = true }
oxc_ast = { workspace = true }
Expand All @@ -37,14 +42,18 @@ oxc_sourcemap = { workspace = true, optional = true }
oxc_isolated_declarations = { workspace = true, optional = true }

[features]
serialize = ["oxc_ast/serialize", "oxc_semantic?/serialize", "oxc_span/serialize", "oxc_syntax/serialize"]
semantic = ["oxc_semantic"]
transformer = ["oxc_transformer"]
minifier = ["oxc_mangler", "oxc_minifier"]
codegen = ["oxc_codegen"]
isolated_declarations = ["oxc_isolated_declarations"]
full = ["codegen", "minifier", "semantic", "transformer"]

semantic = ["oxc_semantic"]
transformer = ["oxc_transformer"]
minifier = ["oxc_mangler", "oxc_minifier"]
codegen = ["oxc_codegen"]

serialize = ["oxc_ast/serialize", "oxc_semantic?/serialize", "oxc_span/serialize", "oxc_syntax/serialize"]

sourcemap = ["oxc_sourcemap"]
sourcemap_concurrent = ["oxc_sourcemap/concurrent", "sourcemap"]

isolated_declarations = ["oxc_isolated_declarations"]

wasm = ["oxc_transformer/wasm"]
32 changes: 32 additions & 0 deletions crates/oxc/examples/compiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![allow(clippy::print_stdout)]

use std::{env, io, path::Path};

use oxc::{span::SourceType, Compiler};

// Instruction:
// 1. create a `test.js`
// 2. run either
// * `cargo run -p oxc --example compiler --features="full"`
// * `just watch 'run -p oxc --example compiler --features="full"'`

fn main() -> io::Result<()> {
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
let path = Path::new(&name);
let source_text = std::fs::read_to_string(path)?;
let source_type = SourceType::from_path(path).unwrap();

match Compiler::default().execute(&source_text, source_type, path) {
Ok(printed) => {
println!("{printed}");
}
Err(errors) => {
for error in errors {
let error = error.with_source_code(source_text.to_string());
println!("{error:?}");
}
}
}

Ok(())
}
225 changes: 225 additions & 0 deletions crates/oxc/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use std::{mem, ops::ControlFlow, path::Path};

use oxc_allocator::Allocator;
use oxc_ast::{ast::Program, Trivias};
use oxc_codegen::{CodeGenerator, CodegenOptions, CommentOptions, WhitespaceRemover};
use oxc_diagnostics::OxcDiagnostic;
use oxc_parser::{ParseOptions, Parser, ParserReturn};
use oxc_span::SourceType;

use oxc_minifier::{CompressOptions, Compressor};
use oxc_semantic::{ScopeTree, SemanticBuilder, SemanticBuilderReturn, SymbolTable};
use oxc_transformer::{TransformOptions, Transformer, TransformerReturn};

#[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, printed: String) {
self.printed = printed;
}
}

impl Compiler {
/// # Errors
///
/// * A list of [OxcDiagnostic].
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 parser_options(&self) -> ParseOptions {
ParseOptions::default()
}

fn transform_options(&self) -> Option<TransformOptions> {
Some(TransformOptions::default())
}

fn compress_options(&self) -> Option<CompressOptions> {
Some(CompressOptions::all_true())
}

fn codegen_options(&self) -> Option<CodegenOptions> {
Some(CodegenOptions::default())
}

fn remove_whitespace(&self) -> bool {
false
}

fn after_parse(&mut self, _parser_return: &mut ParserReturn) -> ControlFlow<()> {
ControlFlow::Continue(())
}

fn after_semantic(
&mut self,
_program: &mut Program<'_>,
_semantic_return: &mut SemanticBuilderReturn,
) -> ControlFlow<()> {
ControlFlow::Continue(())
}

fn after_transform(
&mut self,
_program: &mut Program<'_>,
_transformer_return: &mut TransformerReturn,
) -> ControlFlow<()> {
ControlFlow::Continue(())
}

fn after_codegen(&mut self, _printed: String) {}

fn compile(&mut self, source_text: &str, source_type: SourceType, source_path: &Path) {
let allocator = Allocator::default();

/* Parse */

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);
}

/* Semantic */

let mut program = parser_return.program;
let trivias = parser_return.trivias;

let mut semantic_return = self.semantic(&program, source_text, source_type, source_path);
if !semantic_return.errors.is_empty() {
self.handle_errors(semantic_return.errors);
return;
}
if self.after_semantic(&mut program, &mut semantic_return).is_break() {
return;
}

let (symbols, scopes) = semantic_return.semantic.into_symbol_table_and_scope_tree();

/* Transform */

if let Some(options) = self.transform_options() {
let mut transformer_return = self.transform(
options,
&allocator,
&mut program,
source_path,
source_text,
source_type,
&trivias,
symbols,
scopes,
);

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;
}
}

if let Some(options) = self.compress_options() {
self.compress(&allocator, &mut program, options);
}

if let Some(options) = self.codegen_options() {
let printed = self.codegen(&program, source_text, &trivias, options);
self.after_codegen(printed);
}
}

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.parser_options()).parse()
}

fn semantic<'a>(
&self,
program: &Program<'a>,
source_text: &'a str,
source_type: SourceType,
source_path: &Path,
) -> SemanticBuilderReturn<'a> {
SemanticBuilder::new(source_text, source_type)
.with_check_syntax_error(true)
.build_module_record(source_path.to_path_buf(), program)
.build(program)
}

#[allow(clippy::too_many_arguments)]
fn transform<'a>(
&self,
options: TransformOptions,
allocator: &'a Allocator,
program: &mut Program<'a>,
source_path: &Path,
source_text: &'a str,
source_type: SourceType,
trivias: &Trivias,
symbols: SymbolTable,
scopes: ScopeTree,
) -> TransformerReturn {
Transformer::new(allocator, source_path, source_type, source_text, trivias.clone(), options)
.build_with_symbols_and_scopes(symbols, scopes, program)
}

fn compress<'a>(
&self,
allocator: &'a Allocator,
program: &mut Program<'a>,
options: CompressOptions,
) {
Compressor::new(allocator, options).build(program);
}

fn codegen<'a>(
&self,
program: &Program<'a>,
source_text: &'a str,
trivias: &Trivias,
options: CodegenOptions,
) -> String {
let comment_options = CommentOptions { preserve_annotate_comments: true };

if self.remove_whitespace() {
WhitespaceRemover::new().with_options(options).build(program).source_text
} else {
CodeGenerator::new()
.with_options(options)
.enable_comment(source_text, trivias.clone(), comment_options)
.build(program)
.source_text
}
}
}
6 changes: 6 additions & 0 deletions crates/oxc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
//!
//! <https://github.com/oxc-project/oxc>

#[cfg(feature = "full")]
mod compiler;

#[cfg(feature = "full")]
pub use compiler::{Compiler, CompilerInterface};

pub mod allocator {
#[doc(inline)]
pub use oxc_allocator::*;
Expand Down
18 changes: 12 additions & 6 deletions crates/oxc_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub struct ParserReturn<'a> {

/// Parser options
#[derive(Clone, Copy)]
struct ParserOptions {
pub struct ParseOptions {
pub allow_return_outside_function: bool,
/// Emit `ParenthesizedExpression` in AST.
///
Expand All @@ -117,7 +117,7 @@ struct ParserOptions {
pub preserve_parens: bool,
}

impl Default for ParserOptions {
impl Default for ParseOptions {
fn default() -> Self {
Self { allow_return_outside_function: false, preserve_parens: true }
}
Expand All @@ -130,16 +130,22 @@ pub struct Parser<'a> {
allocator: &'a Allocator,
source_text: &'a str,
source_type: SourceType,
options: ParserOptions,
options: ParseOptions,
}

impl<'a> Parser<'a> {
/// Create a new parser
pub fn new(allocator: &'a Allocator, source_text: &'a str, source_type: SourceType) -> Self {
let options = ParserOptions::default();
let options = ParseOptions::default();
Self { allocator, source_text, source_type, options }
}

#[must_use]
pub fn with_options(mut self, options: ParseOptions) -> Self {
self.options = options;
self
}

/// Allow return outside of function
///
/// By default, a return statement at the top level raises an error.
Expand Down Expand Up @@ -279,7 +285,7 @@ impl<'a> ParserImpl<'a> {
allocator: &'a Allocator,
source_text: &'a str,
source_type: SourceType,
options: ParserOptions,
options: ParseOptions,
unique: UniquePromise,
) -> Self {
Self {
Expand Down Expand Up @@ -347,7 +353,7 @@ impl<'a> ParserImpl<'a> {
Ok(self.ast.program(span, self.source_type, hashbang, directives, statements))
}

fn default_context(source_type: SourceType, options: ParserOptions) -> Context {
fn default_context(source_type: SourceType, options: ParseOptions) -> Context {
let mut ctx = Context::default().and_ambient(source_type.is_typescript_definition());
if source_type.module_kind() == ModuleKind::Module {
// for [top-level-await](https://tc39.es/proposal-top-level-await/)
Expand Down
12 changes: 2 additions & 10 deletions tasks/coverage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,8 @@ test = false
doctest = false

[dependencies]
oxc = { workspace = true, features = [
"codegen",
"isolated_declarations",
"minifier",
"semantic",
"serialize",
"sourcemap",
"transformer",
] }
oxc_prettier = { workspace = true }
oxc = { workspace = true, features = ["full", "isolated_declarations", "serialize", "sourcemap"] }
oxc_prettier = { workspace = true }
oxc_tasks_common = { workspace = true }

serde = { workspace = true, features = ["derive"] }
Expand Down
Loading