refactor(transformer): pass in symbols and scopes (#3978)

This PR adds a new method `build_with_symbols_and_scopes` to make semantic building optional, there may be prior steps that has the semantic data already built.
This commit is contained in:
Boshen 2024-06-30 06:33:48 +00:00
parent b234ddd47c
commit 5845057bff
10 changed files with 64 additions and 46 deletions

1
Cargo.lock generated
View file

@ -1786,6 +1786,7 @@ dependencies = [
"oxc_codegen",
"oxc_diagnostics",
"oxc_parser",
"oxc_semantic",
"oxc_span",
"oxc_syntax",
"oxc_traverse",

View file

@ -27,6 +27,7 @@ oxc_allocator = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_syntax = { workspace = true, features = ["to_js_string"] }
oxc_traverse = { workspace = true }
oxc_semantic = { workspace = true }
dashmap = { workspace = true }
indexmap = { workspace = true }

View file

@ -7,7 +7,7 @@ use std::{
use oxc_allocator::Allocator;
use oxc_ast::{AstBuilder, Trivias};
use oxc_diagnostics::{Error, OxcDiagnostic};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::SourceType;
use crate::{helpers::module_imports::ModuleImports, TransformOptions};
@ -65,9 +65,8 @@ impl<'a> TransformCtx<'a> {
}
}
pub fn take_errors(&self) -> Vec<Error> {
let errors: Vec<OxcDiagnostic> = mem::take(&mut self.errors.borrow_mut());
errors.into_iter().map(Error::from).collect()
pub fn take_errors(&self) -> Vec<OxcDiagnostic> {
mem::take(&mut self.errors.borrow_mut())
}
/// Add an Error

View file

@ -25,10 +25,10 @@ mod helpers {
use std::{path::Path, rc::Rc};
use es2015::ES2015;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{ast::*, AstBuilder, Trivias};
use oxc_diagnostics::Error;
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::{ScopeTree, SemanticBuilder, SymbolTable};
use oxc_span::SourceType;
use oxc_traverse::{traverse_mut, Traverse, TraverseCtx};
@ -42,10 +42,17 @@ pub use crate::{
};
use crate::{
context::{Ctx, TransformCtx},
es2015::ES2015,
react::React,
typescript::TypeScript,
};
pub struct TransformerReturn {
pub errors: std::vec::Vec<OxcDiagnostic>,
pub symbols: SymbolTable,
pub scopes: ScopeTree,
}
pub struct Transformer<'a> {
ctx: Ctx<'a>,
// NOTE: all callbacks must run in order.
@ -79,20 +86,25 @@ impl<'a> Transformer<'a> {
}
}
/// # Errors
///
/// Returns `Vec<Error>` if any errors were collected during the transformation.
pub fn build(mut self, program: &mut Program<'a>) -> Result<(), std::vec::Vec<Error>> {
let TransformCtx { ast: AstBuilder { allocator }, source_text, source_type, .. } =
*self.ctx;
traverse_mut(&mut self, program, source_text, source_type, allocator);
pub fn build(mut self, program: &mut Program<'a>) -> TransformerReturn {
let (symbols, scopes) = SemanticBuilder::new(self.ctx.source_text, self.ctx.source_type)
.build(program)
.semantic
.into_symbol_table_and_scope_tree();
let TransformCtx { ast: AstBuilder { allocator }, .. } = *self.ctx;
let (symbols, scopes) = traverse_mut(&mut self, allocator, program, symbols, scopes);
TransformerReturn { errors: self.ctx.take_errors(), symbols, scopes }
}
let errors = self.ctx.take_errors();
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
pub fn build_with_symbols_and_scopes(
mut self,
symbols: SymbolTable,
scopes: ScopeTree,
program: &mut Program<'a>,
) -> TransformerReturn {
let TransformCtx { ast: AstBuilder { allocator }, .. } = *self.ctx;
let (symbols, scopes) = traverse_mut(&mut self, allocator, program, symbols, scopes);
TransformerReturn { errors: self.ctx.take_errors(), symbols, scopes }
}
}

View file

@ -30,6 +30,10 @@ pub struct TraverseScoping {
// Public methods
impl TraverseScoping {
pub fn into_symbol_table_and_scope_tree(self) -> (SymbolTable, ScopeTree) {
(self.symbols, self.scopes)
}
/// Get current scope ID
#[inline]
pub fn current_scope_id(&self) -> ScopeId {

View file

@ -62,8 +62,7 @@
use oxc_allocator::Allocator;
use oxc_ast::ast::Program;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use oxc_semantic::{ScopeTree, SymbolTable};
pub mod ancestor;
pub use ancestor::Ancestor;
@ -140,16 +139,14 @@ mod walk;
#[allow(unsafe_code)]
pub fn traverse_mut<'a, Tr: Traverse<'a>>(
traverser: &mut Tr,
program: &mut Program<'a>,
source_text: &'a str,
source_type: SourceType,
allocator: &'a Allocator,
) {
let semantic = SemanticBuilder::new(source_text, source_type).build(program).semantic;
let (symbols, scopes) = semantic.into_symbol_table_and_scope_tree();
program: &mut Program<'a>,
symbols: SymbolTable,
scopes: ScopeTree,
) -> (SymbolTable, ScopeTree) {
let mut ctx = TraverseCtx::new(scopes, symbols, allocator);
// SAFETY: Walk functions are constructed to avoid unsoundness
unsafe { walk::walk_program(traverser, program as *mut Program, &mut ctx) };
debug_assert!(ctx.ancestors_depth() == 1);
ctx.scoping.into_symbol_table_and_scope_tree()
}

View file

@ -244,8 +244,9 @@ impl Oxc {
options,
)
.build(program);
if let Err(errs) = result {
self.save_diagnostics(errs);
if !result.errors.is_empty() {
let errors = result.errors.into_iter().map(Error::from).collect::<Vec<_>>();
self.save_diagnostics(errors);
}
}

View file

@ -149,7 +149,7 @@ pub fn transform(
let mut program = parser_ret.program;
let transform_options = TransformOptions::from(options);
if let Err(e) = Transformer::new(
let ret = Transformer::new(
&allocator,
source_path,
source_type,
@ -157,9 +157,10 @@ pub fn transform(
parser_ret.trivias.clone(),
transform_options,
)
.build(&mut program)
{
errors.extend(e.into_iter().map(|error| error.to_string()));
.build(&mut program);
if !ret.errors.is_empty() {
errors.extend(ret.errors.into_iter().map(|error| error.to_string()));
}
let mut codegen = CodeGenerator::new();

View file

@ -31,8 +31,7 @@ fn bench_transformer(criterion: &mut Criterion) {
trivias,
transform_options,
)
.build(program)
.unwrap();
.build(program);
allocator
});
});

View file

@ -148,11 +148,11 @@ pub trait TestCase {
false
}
fn transform(&self, path: &Path) -> Result<String, Vec<Error>> {
fn transform(&self, path: &Path) -> Result<String, Vec<OxcDiagnostic>> {
let transform_options = match self.transform_options() {
Ok(transform_options) => transform_options,
Err(json_err) => {
return Err(vec![OxcDiagnostic::error(format!("{json_err:?}")).into()]);
return Err(vec![OxcDiagnostic::error(format!("{json_err:?}"))]);
}
};
@ -180,7 +180,11 @@ pub trait TestCase {
transform_options.clone(),
)
.build(&mut program);
result.map(|()| CodeGenerator::new().build(&program).source_text)
if result.errors.is_empty() {
Ok(CodeGenerator::new().build(&program).source_text)
} else {
Err(result.errors)
}
}
}
@ -262,15 +266,14 @@ impl TestCase for ConformanceTestCase {
ret.trivias.clone(),
transform_options.clone(),
);
let result = transformer.build(&mut program);
if result.is_ok() {
let ret = transformer.build(&mut program);
if ret.errors.is_empty() {
transformed_code = CodeGenerator::new().build(&program).source_text;
} else {
let error = result
.err()
.unwrap()
.iter()
.map(ToString::to_string)
let error = ret
.errors
.into_iter()
.map(|e| Error::from(e).to_string())
.collect::<Vec<_>>()
.join("\n");
actual_errors = get_babel_error(&error);