mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 12:51:57 +00:00
342 lines
12 KiB
Rust
342 lines
12 KiB
Rust
// Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]`
|
|
#![allow(non_snake_case)]
|
|
|
|
mod options;
|
|
|
|
use std::{cell::RefCell, path::PathBuf, rc::Rc};
|
|
|
|
use oxc::{
|
|
allocator::Allocator,
|
|
ast::{CommentKind, Trivias},
|
|
codegen::{Codegen, CodegenOptions},
|
|
diagnostics::Error,
|
|
minifier::{CompressOptions, Minifier, MinifierOptions},
|
|
parser::Parser,
|
|
semantic::{ScopeId, Semantic, SemanticBuilder},
|
|
span::SourceType,
|
|
transformer::{TransformOptions, Transformer},
|
|
};
|
|
use oxc_linter::{LintContext, Linter};
|
|
use oxc_prettier::{Prettier, PrettierOptions};
|
|
use serde::Serialize;
|
|
use tsify::Tsify;
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
use crate::options::{
|
|
OxcCodegenOptions, OxcLinterOptions, OxcMinifierOptions, OxcParserOptions, OxcRunOptions,
|
|
};
|
|
|
|
#[wasm_bindgen(getter_with_clone)]
|
|
#[derive(Default, Tsify)]
|
|
pub struct Oxc {
|
|
source_text: String,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript)]
|
|
#[tsify(type = "Program")]
|
|
pub ast: JsValue,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript)]
|
|
#[tsify(type = "Statement[]")]
|
|
pub ir: JsValue,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript)]
|
|
#[tsify(type = "SymbolTable")]
|
|
pub symbols: JsValue,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript, js_name = "scopeText")]
|
|
#[serde(rename = "scopeText")]
|
|
pub scope_text: String,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript, js_name = "codegenText")]
|
|
#[serde(rename = "codegenText")]
|
|
pub codegen_text: String,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript, js_name = "formattedText")]
|
|
#[serde(rename = "formattedText")]
|
|
pub formatted_text: String,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript, js_name = "prettierFormattedText")]
|
|
#[serde(rename = "prettierFormattedText")]
|
|
pub prettier_formatted_text: String,
|
|
|
|
#[wasm_bindgen(readonly, skip_typescript, js_name = "prettierIrText")]
|
|
#[serde(rename = "prettierIrText")]
|
|
pub prettier_ir_text: String,
|
|
|
|
comments: Vec<Comment>,
|
|
|
|
diagnostics: RefCell<Vec<Error>>,
|
|
|
|
#[serde(skip)]
|
|
serializer: serde_wasm_bindgen::Serializer,
|
|
}
|
|
|
|
#[derive(Clone, Tsify, Serialize)]
|
|
#[tsify(into_wasm_abi)]
|
|
pub struct Comment {
|
|
pub r#type: CommentType,
|
|
pub value: String,
|
|
pub start: u32,
|
|
pub end: u32,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Tsify, Serialize)]
|
|
#[tsify(into_wasm_abi)]
|
|
pub enum CommentType {
|
|
Line,
|
|
Block,
|
|
}
|
|
|
|
#[derive(Default, Clone, Serialize)]
|
|
pub struct OxcDiagnostic {
|
|
pub start: usize,
|
|
pub end: usize,
|
|
pub severity: String,
|
|
pub message: String,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl Oxc {
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new() -> Self {
|
|
Self { serializer: serde_wasm_bindgen::Serializer::json_compatible(), ..Self::default() }
|
|
}
|
|
|
|
#[wasm_bindgen(getter = sourceText)]
|
|
pub fn source_text(&self) -> String {
|
|
self.source_text.clone()
|
|
}
|
|
|
|
#[wasm_bindgen(setter = sourceText)]
|
|
pub fn set_source_text(&mut self, source_text: String) {
|
|
self.diagnostics = RefCell::default();
|
|
self.source_text = source_text;
|
|
}
|
|
|
|
/// Returns Array of String
|
|
/// # Errors
|
|
/// # Panics
|
|
#[wasm_bindgen(js_name = getDiagnostics)]
|
|
pub fn get_diagnostics(&self) -> Result<Vec<JsValue>, serde_wasm_bindgen::Error> {
|
|
Ok(self
|
|
.diagnostics
|
|
.borrow()
|
|
.iter()
|
|
.flat_map(|error| {
|
|
let Some(labels) = error.labels() else { return vec![] };
|
|
labels
|
|
.map(|label| {
|
|
OxcDiagnostic {
|
|
start: label.offset(),
|
|
end: label.offset() + label.len(),
|
|
severity: format!("{:?}", error.severity().unwrap_or_default()),
|
|
message: format!("{error}"),
|
|
}
|
|
.serialize(&self.serializer)
|
|
.unwrap()
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.collect::<Vec<_>>())
|
|
}
|
|
|
|
/// Returns comments
|
|
/// # Errors
|
|
#[wasm_bindgen(js_name = getComments)]
|
|
pub fn get_comments(&self) -> Result<Vec<JsValue>, serde_wasm_bindgen::Error> {
|
|
self.comments.iter().map(|c| c.serialize(&self.serializer)).collect()
|
|
}
|
|
|
|
/// # Errors
|
|
/// Serde serialization error
|
|
#[wasm_bindgen]
|
|
pub fn run(
|
|
&mut self,
|
|
run_options: &OxcRunOptions,
|
|
parser_options: &OxcParserOptions,
|
|
_linter_options: &OxcLinterOptions,
|
|
codegen_options: &OxcCodegenOptions,
|
|
minifier_options: &OxcMinifierOptions,
|
|
) -> Result<(), serde_wasm_bindgen::Error> {
|
|
self.diagnostics = RefCell::default();
|
|
|
|
let allocator = Allocator::default();
|
|
let source_text = &self.source_text;
|
|
let path = PathBuf::from(
|
|
parser_options.source_filename.clone().unwrap_or_else(|| "test.tsx".to_string()),
|
|
);
|
|
let source_type = SourceType::from_path(&path).unwrap_or_default();
|
|
|
|
let ret = Parser::new(&allocator, source_text, source_type)
|
|
.allow_return_outside_function(parser_options.allow_return_outside_function)
|
|
.parse();
|
|
|
|
self.comments = self.map_comments(&ret.trivias);
|
|
self.save_diagnostics(ret.errors.into_iter().map(Error::from).collect::<Vec<_>>());
|
|
|
|
self.ir = format!("{:#?}", ret.program.body).into();
|
|
|
|
let program = allocator.alloc(ret.program);
|
|
|
|
let semantic_ret = SemanticBuilder::new(source_text, source_type)
|
|
.with_trivias(ret.trivias)
|
|
.with_check_syntax_error(true)
|
|
.build(program);
|
|
|
|
if run_options.syntax() {
|
|
self.save_diagnostics(
|
|
semantic_ret.errors.into_iter().map(Error::from).collect::<Vec<_>>(),
|
|
);
|
|
}
|
|
|
|
let semantic = Rc::new(semantic_ret.semantic);
|
|
// Only lint if there are not syntax errors
|
|
if run_options.lint() && self.diagnostics.borrow().is_empty() {
|
|
let lint_ctx = LintContext::new(path.clone().into_boxed_path(), &semantic);
|
|
let linter_ret = Linter::default().run(lint_ctx);
|
|
let diagnostics = linter_ret.into_iter().map(|e| Error::from(e.error)).collect();
|
|
self.save_diagnostics(diagnostics);
|
|
}
|
|
|
|
self.ast = program.serialize(&self.serializer)?;
|
|
|
|
if run_options.prettier_format() {
|
|
let ret = Parser::new(&allocator, source_text, source_type)
|
|
.allow_return_outside_function(parser_options.allow_return_outside_function)
|
|
.preserve_parens(false)
|
|
.parse();
|
|
let printed =
|
|
Prettier::new(&allocator, source_text, &ret.trivias, PrettierOptions::default())
|
|
.build(&ret.program);
|
|
self.prettier_formatted_text = printed;
|
|
}
|
|
|
|
if run_options.prettier_ir() {
|
|
let ret = Parser::new(&allocator, source_text, source_type)
|
|
.allow_return_outside_function(parser_options.allow_return_outside_function)
|
|
.preserve_parens(false)
|
|
.parse();
|
|
let prettier_doc =
|
|
Prettier::new(&allocator, source_text, &ret.trivias, PrettierOptions::default())
|
|
.doc(&ret.program)
|
|
.to_string();
|
|
self.prettier_ir_text = {
|
|
let ret = Parser::new(&allocator, &prettier_doc, SourceType::default()).parse();
|
|
Prettier::new(&allocator, &prettier_doc, &ret.trivias, PrettierOptions::default())
|
|
.build(&ret.program)
|
|
};
|
|
}
|
|
|
|
if run_options.transform() {
|
|
let options = TransformOptions::default();
|
|
let result = Transformer::new(
|
|
&allocator,
|
|
&path,
|
|
source_type,
|
|
source_text,
|
|
semantic.trivias(),
|
|
options,
|
|
)
|
|
.build(program);
|
|
if let Err(errs) = result {
|
|
self.save_diagnostics(errs);
|
|
}
|
|
}
|
|
|
|
if run_options.scope() || run_options.symbol() {
|
|
let semantic = SemanticBuilder::new(source_text, source_type)
|
|
.build_module_record(PathBuf::new(), program)
|
|
.build(program)
|
|
.semantic;
|
|
if run_options.scope() {
|
|
self.scope_text = Self::get_scope_text(&semantic);
|
|
} else if run_options.symbol() {
|
|
self.symbols = semantic.symbols().serialize(&self.serializer)?;
|
|
}
|
|
}
|
|
|
|
let program = allocator.alloc(program);
|
|
|
|
if minifier_options.compress() || minifier_options.mangle() {
|
|
let options = MinifierOptions {
|
|
mangle: minifier_options.mangle(),
|
|
compress: if minifier_options.compress() {
|
|
CompressOptions::default()
|
|
} else {
|
|
CompressOptions::all_false()
|
|
},
|
|
};
|
|
Minifier::new(options).build(&allocator, program);
|
|
}
|
|
|
|
let codegen_options = CodegenOptions {
|
|
enable_typescript: codegen_options.enable_typescript,
|
|
..CodegenOptions::default()
|
|
};
|
|
self.codegen_text = if minifier_options.whitespace() {
|
|
Codegen::<true>::new("", source_text, codegen_options).build(program).source_text
|
|
} else {
|
|
Codegen::<false>::new("", source_text, codegen_options).build(program).source_text
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_scope_text(semantic: &Semantic) -> String {
|
|
fn write_scope_text(
|
|
semantic: &Semantic,
|
|
scope_text: &mut String,
|
|
depth: usize,
|
|
scope_ids: &Vec<ScopeId>,
|
|
) {
|
|
let space = " ".repeat(depth * 2);
|
|
|
|
for scope_id in scope_ids {
|
|
let flag = semantic.scopes().get_flags(*scope_id);
|
|
let next_scope_ids = semantic.scopes().get_child_ids(*scope_id);
|
|
|
|
scope_text.push_str(&format!("{space}Scope{:?} ({flag:?}) {{\n", *scope_id + 1));
|
|
let bindings = semantic.scopes().get_bindings(*scope_id);
|
|
let binding_space = " ".repeat((depth + 1) * 2);
|
|
if !bindings.is_empty() {
|
|
scope_text.push_str(&format!("{binding_space}Bindings: {{"));
|
|
}
|
|
bindings.iter().for_each(|(name, symbol_id)| {
|
|
let symbol_flag = semantic.symbols().get_flag(*symbol_id);
|
|
scope_text.push_str(&format!("\n{binding_space} {name} ({symbol_flag:?})",));
|
|
});
|
|
if !bindings.is_empty() {
|
|
scope_text.push_str(&format!("\n{binding_space}}}\n"));
|
|
}
|
|
|
|
if let Some(next_scope_ids) = next_scope_ids {
|
|
write_scope_text(semantic, scope_text, depth + 1, next_scope_ids);
|
|
}
|
|
scope_text.push_str(&format!("{space}}}\n"));
|
|
}
|
|
}
|
|
|
|
let mut scope_text = String::default();
|
|
write_scope_text(semantic, &mut scope_text, 0, &vec![semantic.scopes().root_scope_id()]);
|
|
scope_text
|
|
}
|
|
|
|
fn save_diagnostics(&self, diagnostics: Vec<Error>) {
|
|
self.diagnostics.borrow_mut().extend(diagnostics);
|
|
}
|
|
|
|
fn map_comments(&self, trivias: &Trivias) -> Vec<Comment> {
|
|
trivias
|
|
.comments()
|
|
.map(|(kind, span)| Comment {
|
|
r#type: match kind {
|
|
CommentKind::SingleLine => CommentType::Line,
|
|
CommentKind::MultiLine => CommentType::Block,
|
|
},
|
|
value: span.source_text(&self.source_text).to_string(),
|
|
start: span.start,
|
|
end: span.end,
|
|
})
|
|
.collect()
|
|
}
|
|
}
|