refactor(transformer): remove the requirement of Semantic (#3140)

It seems like we need to rebuild the scopes and symbols while
traversing. We can't utilize the scopes and symbols built by semantic
because they are immutable.
This commit is contained in:
Boshen 2024-04-30 12:48:21 +08:00 committed by GitHub
parent 733361822e
commit a63a45d5b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 88 additions and 113 deletions

2
Cargo.lock generated
View file

@ -1631,7 +1631,6 @@ dependencies = [
"oxc_codegen",
"oxc_diagnostics",
"oxc_parser",
"oxc_semantic",
"oxc_span",
"oxc_tasks_common",
"oxc_transformer",
@ -1651,7 +1650,6 @@ dependencies = [
"oxc_codegen",
"oxc_diagnostics",
"oxc_parser",
"oxc_semantic",
"oxc_span",
"oxc_syntax",
"rustc-hash",

View file

@ -22,7 +22,6 @@ doctest = false
oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_allocator = { workspace = true }
oxc_semantic = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_syntax = { workspace = true, features = ["to_js_string"] }

View file

@ -1,12 +1,8 @@
use std::{
env,
path::{Path, PathBuf},
};
use std::{env, path::Path};
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use oxc_transformer::{TransformOptions, Transformer};
@ -35,18 +31,14 @@ fn main() {
println!("Original:\n");
println!("{source_text}\n");
let semantic = SemanticBuilder::new(&source_text, source_type)
.with_trivias(ret.trivias)
.build_module_record(PathBuf::new(), &ret.program)
.build(&ret.program)
.semantic;
let program = allocator.alloc(ret.program);
let mut program = ret.program;
let transform_options = TransformOptions::default();
Transformer::new(&allocator, path, semantic, transform_options).build(program).unwrap();
Transformer::new(&allocator, path, source_type, &source_text, &ret.trivias, transform_options)
.build(&mut program)
.unwrap();
let printed = Codegen::<false>::new("", &source_text, CodegenOptions::default())
.build(program)
.build(&program)
.source_text;
println!("Transformed:\n");
println!("{printed}");

View file

@ -6,9 +6,8 @@ use std::{
};
use oxc_allocator::Allocator;
use oxc_ast::AstBuilder;
use oxc_ast::{AstBuilder, Trivias};
use oxc_diagnostics::Error;
use oxc_semantic::Semantic;
use oxc_span::SourceType;
use crate::{helpers::module_imports::ModuleImports, TransformOptions};
@ -16,17 +15,21 @@ use crate::{helpers::module_imports::ModuleImports, TransformOptions};
pub type Ctx<'a> = Rc<TransformCtx<'a>>;
pub struct TransformCtx<'a> {
errors: RefCell<Vec<Error>>,
pub trivias: &'a Trivias,
pub ast: AstBuilder<'a>,
pub semantic: Semantic<'a>,
/// <https://babeljs.io/docs/options#filename>
filename: String,
pub filename: String,
/// Source path in the form of `<CWD>/path/to/file/input.js`
source_path: PathBuf,
pub source_path: PathBuf,
errors: RefCell<Vec<Error>>,
pub source_type: SourceType,
pub source_text: &'a str,
// Helpers
/// Manage import statement globally
@ -37,7 +40,9 @@ impl<'a> TransformCtx<'a> {
pub fn new(
allocator: &'a Allocator,
source_path: &Path,
semantic: Semantic<'a>,
source_type: SourceType,
source_text: &'a str,
trivias: &'a Trivias,
options: &TransformOptions,
) -> Self {
let filename = source_path
@ -49,11 +54,13 @@ impl<'a> TransformCtx<'a> {
.map_or_else(|_| source_path.to_path_buf(), |p| Path::new("<CWD>").join(p));
Self {
errors: RefCell::new(vec![]),
ast: AstBuilder::new(allocator),
semantic,
filename,
source_path,
errors: RefCell::new(vec![]),
source_type,
source_text,
trivias,
module_imports: ModuleImports::new(allocator),
}
}
@ -62,21 +69,9 @@ impl<'a> TransformCtx<'a> {
mem::take(&mut self.errors.borrow_mut())
}
pub fn filename(&self) -> &str {
&self.filename
}
pub fn source_path(&self) -> &Path {
&self.source_path
}
/// Add an Error
#[allow(unused)]
pub fn error<T: Into<Error>>(&self, error: T) {
self.errors.borrow_mut().push(error.into());
}
pub fn source_type(&self) -> &SourceType {
self.semantic.source_type()
}
}

View file

@ -28,9 +28,11 @@ use oxc_allocator::{Allocator, Vec};
use oxc_ast::{
ast::*,
visit::{walk_mut, VisitMut},
Trivias,
};
use oxc_diagnostics::Error;
use oxc_semantic::Semantic;
use oxc_span::SourceType;
use oxc_syntax::scope::ScopeFlags;
pub use crate::{
compiler_assumptions::CompilerAssumptions, es2015::ES2015Options, options::TransformOptions,
@ -55,10 +57,19 @@ impl<'a> Transformer<'a> {
pub fn new(
allocator: &'a Allocator,
source_path: &Path,
semantic: Semantic<'a>,
source_type: SourceType,
source_text: &'a str,
trivias: &'a Trivias,
options: TransformOptions,
) -> Self {
let ctx = Rc::new(TransformCtx::new(allocator, source_path, semantic, &options));
let ctx = Rc::new(TransformCtx::new(
allocator,
source_path,
source_type,
source_text,
trivias,
&options,
));
Self {
ctx: Rc::clone(&ctx),
x0_typescript: TypeScript::new(options.typescript, &ctx),
@ -154,7 +165,7 @@ impl<'a> VisitMut<'a> for Transformer<'a> {
walk_mut::walk_formal_parameter_mut(self, param);
}
fn visit_function(&mut self, func: &mut Function<'a>, flags: Option<oxc_semantic::ScopeFlags>) {
fn visit_function(&mut self, func: &mut Function<'a>, flags: Option<ScopeFlags>) {
self.x0_typescript.transform_function(func, flags);
walk_mut::walk_function_mut(self, func, flags);

View file

@ -78,7 +78,7 @@ impl<'a> ReactDisplayName<'a> {
pub fn transform_export_default_declaration(&self, decl: &mut ExportDefaultDeclaration<'a>) {
let Some(expr) = decl.declaration.as_expression_mut() else { return };
let Some(obj_expr) = Self::get_object_from_create_class(expr) else { return };
let name = self.ctx.ast.new_atom(self.ctx.filename());
let name = self.ctx.ast.new_atom(&self.ctx.filename);
self.add_display_name(obj_expr, name);
}
}

View file

@ -91,7 +91,7 @@ impl<'a> ReactJsx<'a> {
}
fn is_script(&self) -> bool {
self.ctx.semantic.source_type().is_script()
self.ctx.source_type.is_script()
}
fn ast(&self) -> &AstBuilder<'a> {

View file

@ -132,8 +132,7 @@ impl<'a> ReactJsxSource<'a> {
self.ctx.ast.binding_pattern(ident, None, false)
};
let decl = {
let string =
self.ctx.ast.string_literal(SPAN, &self.ctx.source_path().to_string_lossy());
let string = self.ctx.ast.string_literal(SPAN, &self.ctx.source_path.to_string_lossy());
let init = self.ctx.ast.literal_string_expression(string);
let decl = self.ctx.ast.variable_declarator(SPAN, var_kind, id, Some(init), false);
self.ctx.ast.new_vec_single(decl)

View file

@ -159,9 +159,8 @@ impl ReactOptions {
///
/// This behavior is aligned with babel.
pub(crate) fn update_with_comments(&mut self, ctx: &Ctx) {
let semantic = &ctx.semantic;
for (_, span) in semantic.trivias().comments() {
let mut comment = span.source_text(semantic.source_text()).trim_start();
for (_, span) in ctx.trivias.comments() {
let mut comment = span.source_text(ctx.source_text).trim_start();
// strip leading jsdoc comment `*` and then whitespaces
while let Some(cur_comment) = comment.strip_prefix('*') {
comment = cur_comment.trim_start();

View file

@ -8,7 +8,7 @@ use crate::TypeScriptOptions;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_span::{Atom, SPAN};
use oxc_syntax::operator::AssignmentOperator;
use oxc_syntax::{operator::AssignmentOperator, scope::ScopeFlags};
use rustc_hash::FxHashSet;
use super::collector::TypeScriptReferenceCollector;
@ -217,11 +217,7 @@ impl<'a> TypeScriptAnnotations<'a> {
param.accessibility = None;
}
pub fn transform_function(
&mut self,
func: &mut Function<'a>,
_flags: Option<oxc_semantic::ScopeFlags>,
) {
pub fn transform_function(&mut self, func: &mut Function<'a>, _flags: Option<ScopeFlags>) {
func.this_param = None;
func.type_parameters = None;
func.return_type = None;

View file

@ -11,6 +11,7 @@ use serde::Deserialize;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_syntax::scope::ScopeFlags;
use crate::context::Ctx;
@ -114,11 +115,7 @@ impl<'a> TypeScript<'a> {
self.annotations.transform_formal_parameter(param);
}
pub fn transform_function(
&mut self,
func: &mut Function<'a>,
flags: Option<oxc_semantic::ScopeFlags>,
) {
pub fn transform_function(&mut self, func: &mut Function<'a>, flags: Option<ScopeFlags>) {
self.annotations.transform_function(func, flags);
}

View file

@ -49,7 +49,7 @@ impl<'a> TypeScript<'a> {
self.transform_ts_type_name(&mut *type_name.to_ts_type_name_mut())
}
TSModuleReference::ExternalModuleReference(reference) => {
if self.ctx.source_type().is_module() {
if self.ctx.source_type.is_module() {
self.ctx.error(ImportEqualsRequireUnsupported(decl_span));
}
@ -80,7 +80,7 @@ impl<'a> TypeScript<'a> {
&mut self,
export_assignment: &mut TSExportAssignment<'a>,
) {
if self.ctx.source_type().is_module() {
if self.ctx.source_type.is_module() {
self.ctx.error(ExportAssignmentUnsupported(export_assignment.span));
}
}

View file

@ -187,9 +187,9 @@ impl Oxc {
self.save_diagnostics(semantic_ret.errors);
}
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 semantic = Rc::new(semantic_ret.semantic);
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| e.error).collect();
@ -226,14 +226,16 @@ impl Oxc {
}
if run_options.transform() {
// FIXME: this should not be duplicated with the linter semantic,
// we need to fix the API so symbols and scopes can be shared.
let semantic = SemanticBuilder::new(source_text, source_type)
.build_module_record(PathBuf::new(), program)
.build(program)
.semantic;
let options = TransformOptions::default();
let result = Transformer::new(&allocator, &path, semantic, options).build(program);
let result = Transformer::new(
&allocator,
&path,
source_type,
source_text,
semantic.trivias(),
options,
)
.build(program);
if let Err(errs) = result {
self.save_diagnostics(errs);
}

View file

@ -2,8 +2,7 @@ use std::path::Path;
use oxc_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_parser::{Parser, ParserReturn};
use oxc_span::SourceType;
use oxc_tasks_common::TestFiles;
use oxc_transformer::{TransformOptions, Transformer};
@ -20,15 +19,16 @@ fn bench_transformer(criterion: &mut Criterion) {
// transforming an already transformed AST.
b.iter_with_large_drop(|| {
let allocator = Allocator::default();
let program = Parser::new(&allocator, source_text, source_type).parse().program;
let semantic =
SemanticBuilder::new(source_text, source_type).build(&program).semantic;
let program = allocator.alloc(program);
let ParserReturn { trivias, program, .. } =
Parser::new(&allocator, source_text, source_type).parse();
let transform_options = TransformOptions::default();
let program = allocator.alloc(program);
Transformer::new(
&allocator,
Path::new(&file.file_name),
semantic,
source_type,
source_text,
&trivias,
transform_options,
)
.build(program)

View file

@ -2,7 +2,6 @@ use std::path::{Path, PathBuf};
use oxc_allocator::Allocator;
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
use oxc_transformer::{TransformOptions, Transformer};
@ -18,18 +17,12 @@ use crate::{
/// TODO: add codegen to turn on idempotency test.
fn get_result(source_text: &str, source_type: SourceType, source_path: &Path) -> TestResult {
let allocator = Allocator::default();
let parser_ret = Parser::new(&allocator, source_text, source_type).parse();
let semantic_ret = SemanticBuilder::new(source_text, source_type)
.with_trivias(parser_ret.trivias)
.with_check_syntax_error(true)
.build(&parser_ret.program);
let mut program = parser_ret.program;
let ret = Parser::new(&allocator, source_text, source_type).parse();
let mut program = ret.program;
let options = TransformOptions::default();
let _ = Transformer::new(&allocator, source_path, semantic_ret.semantic, options)
.build(&mut program);
let _ =
Transformer::new(&allocator, source_path, source_type, source_text, &ret.trivias, options)
.build(&mut program);
TestResult::Passed
}

View file

@ -24,7 +24,6 @@ test = false
oxc_span = { workspace = true }
oxc_allocator = { workspace = true }
oxc_parser = { workspace = true }
oxc_semantic = { workspace = true }
oxc_codegen = { workspace = true }
oxc_transformer = { workspace = true }
oxc_tasks_common = { workspace = true }

View file

@ -10,7 +10,6 @@ use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_diagnostics::{miette::miette, Error};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::{SourceType, VALID_EXTENSIONS};
use oxc_tasks_common::{normalize_path, print_diff_in_terminal, BabelOptions, TestOs};
use oxc_transformer::{
@ -206,21 +205,20 @@ pub trait TestCase {
);
let ret = Parser::new(&allocator, &source_text, source_type).parse();
let semantic = SemanticBuilder::new(&source_text, source_type)
.with_trivias(ret.trivias)
.build_module_record(PathBuf::new(), &ret.program)
.build(&ret.program)
.semantic;
let transformed_program = allocator.alloc(ret.program);
let result = Transformer::new(&allocator, path, semantic, transform_options.clone())
.build(transformed_program);
let mut program = ret.program;
let result = Transformer::new(
&allocator,
path,
source_type,
&source_text,
&ret.trivias,
transform_options.clone(),
)
.build(&mut program);
result.map(|()| {
Codegen::<false>::new("", &source_text, CodegenOptions::default())
.build(transformed_program)
.build(&program)
.source_text
})
}
@ -290,23 +288,20 @@ impl TestCase for ConformanceTestCase {
Ok(transform_options) => {
let ret = Parser::new(&allocator, &input, source_type).parse();
if ret.errors.is_empty() {
let semantic = SemanticBuilder::new(&input, source_type)
.with_trivias(ret.trivias)
.build_module_record(PathBuf::new(), &ret.program)
.build(&ret.program)
.semantic;
let program = allocator.alloc(ret.program);
let mut program = ret.program;
let transformer = Transformer::new(
&allocator,
&self.path,
semantic,
source_type,
&input,
&ret.trivias,
transform_options.clone(),
);
let result = transformer.build(program);
let result = transformer.build(&mut program);
if result.is_ok() {
transformed_code =
Codegen::<false>::new("", &input, codegen_options.clone())
.build(program)
.build(&program)
.source_text;
} else {
let error = result