mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
refactor(transform_conformance): add driver (#4969)
This commit is contained in:
parent
37b9b0e21d
commit
4fdf26dac8
9 changed files with 129 additions and 197 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -1877,15 +1877,8 @@ name = "oxc_transform_conformance"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_codegen",
|
||||
"oxc_diagnostics",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"oxc",
|
||||
"oxc_tasks_common",
|
||||
"oxc_transformer",
|
||||
"pico-args",
|
||||
"walkdir",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ pub trait CompilerInterface {
|
|||
}
|
||||
|
||||
fn compress_options(&self) -> Option<CompressOptions> {
|
||||
Some(CompressOptions::all_true())
|
||||
None
|
||||
}
|
||||
|
||||
fn codegen_options(&self) -> Option<CodegenOptions> {
|
||||
|
|
@ -69,6 +69,10 @@ pub trait CompilerInterface {
|
|||
false
|
||||
}
|
||||
|
||||
fn check_semantic_error(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn after_parse(&mut self, _parser_return: &mut ParserReturn) -> ControlFlow<()> {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
|
@ -172,7 +176,7 @@ pub trait CompilerInterface {
|
|||
source_path: &Path,
|
||||
) -> SemanticBuilderReturn<'a> {
|
||||
SemanticBuilder::new(source_text, source_type)
|
||||
.with_check_syntax_error(true)
|
||||
.with_check_syntax_error(self.check_semantic_error())
|
||||
.build_module_record(source_path.to_path_buf(), program)
|
||||
.build(program)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,15 +22,8 @@ test = false
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
oxc_ast = { workspace = true }
|
||||
oxc_span = { workspace = true }
|
||||
oxc_allocator = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_codegen = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_transformer = { workspace = true }
|
||||
oxc = { workspace = true, features = ["full"] }
|
||||
oxc_tasks_common = { workspace = true }
|
||||
oxc_diagnostics = { workspace = true }
|
||||
|
||||
walkdir = { workspace = true }
|
||||
pico-args = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 12619ffe
|
||||
|
||||
Passed: 466/953
|
||||
Passed: 487/953
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
|
|
@ -465,37 +465,16 @@ Passed: 466/953
|
|||
* opts/optimizeConstEnums/input.ts
|
||||
* opts/rewriteImportExtensions/input.ts
|
||||
|
||||
# babel-plugin-transform-typescript (109/151)
|
||||
# babel-plugin-transform-typescript (130/151)
|
||||
* class/accessor-allowDeclareFields-false/input.ts
|
||||
* class/accessor-allowDeclareFields-true/input.ts
|
||||
* enum/mix-references/input.ts
|
||||
* enum/ts5.0-const-foldable/input.ts
|
||||
* exports/declared-types/input.ts
|
||||
* exports/export-import=/input.ts
|
||||
* exports/interface/input.ts
|
||||
* imports/elide-type-referenced-in-imports-equal-no/input.ts
|
||||
* imports/import=-module/input.ts
|
||||
* imports/only-remove-type-imports/input.ts
|
||||
* imports/type-only-export-specifier-2/input.ts
|
||||
* imports/type-only-import-specifier-4/input.ts
|
||||
* namespace/alias/input.ts
|
||||
* namespace/clobber-class/input.ts
|
||||
* namespace/clobber-enum/input.ts
|
||||
* namespace/clobber-export/input.ts
|
||||
* namespace/contentious-names/input.ts
|
||||
* namespace/declare/input.ts
|
||||
* namespace/declare-global-nested-namespace/input.ts
|
||||
* namespace/empty-removed/input.ts
|
||||
* namespace/export/input.ts
|
||||
* namespace/module-nested/input.ts
|
||||
* namespace/module-nested-export/input.ts
|
||||
* namespace/multiple/input.ts
|
||||
* namespace/mutable-fail/input.ts
|
||||
* namespace/nested/input.ts
|
||||
* namespace/nested-namespace/input.ts
|
||||
* namespace/nested-shorthand/input.ts
|
||||
* namespace/same-name/input.ts
|
||||
* namespace/undeclared/input.ts
|
||||
* optimize-const-enums/custom-values/input.ts
|
||||
* optimize-const-enums/custom-values-exported/input.ts
|
||||
* optimize-const-enums/declare/input.ts
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
commit: 12619ffe
|
||||
|
||||
Passed: 28/35
|
||||
Passed: 31/35
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
* babel-plugin-transform-typescript
|
||||
|
||||
|
||||
# babel-plugin-transform-typescript (4/7)
|
||||
* computed-constant-value/input.ts
|
||||
* enum-member-reference/input.ts
|
||||
* export-elimination/input.ts
|
||||
|
||||
# babel-plugin-transform-react-jsx (23/27)
|
||||
* refresh/can-handle-implicit-arrow-returns/input.jsx
|
||||
* refresh/registers-identifiers-used-in-jsx-at-definition-site/input.jsx
|
||||
|
|
|
|||
94
tasks/transform_conformance/src/driver.rs
Normal file
94
tasks/transform_conformance/src/driver.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use std::{mem, ops::ControlFlow, path::Path};
|
||||
|
||||
use oxc::{
|
||||
ast::ast::Program,
|
||||
diagnostics::OxcDiagnostic,
|
||||
semantic::{post_transform_checker::PostTransformChecker, SemanticBuilderReturn},
|
||||
span::SourceType,
|
||||
transformer::{TransformOptions, TransformerReturn},
|
||||
CompilerInterface,
|
||||
};
|
||||
|
||||
pub struct Driver {
|
||||
options: TransformOptions,
|
||||
printed: String,
|
||||
errors: Vec<OxcDiagnostic>,
|
||||
check_semantic: bool,
|
||||
checker: PostTransformChecker,
|
||||
}
|
||||
|
||||
impl CompilerInterface for Driver {
|
||||
fn transform_options(&self) -> Option<TransformOptions> {
|
||||
Some(self.options.clone())
|
||||
}
|
||||
|
||||
fn check_semantic_error(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_errors(&mut self, errors: Vec<OxcDiagnostic>) {
|
||||
self.errors.extend(errors);
|
||||
}
|
||||
|
||||
fn after_codegen(&mut self, printed: String) {
|
||||
self.printed = printed;
|
||||
}
|
||||
|
||||
fn after_semantic(
|
||||
&mut self,
|
||||
program: &mut Program<'_>,
|
||||
_semantic_return: &mut SemanticBuilderReturn,
|
||||
) -> ControlFlow<()> {
|
||||
if self.check_semantic {
|
||||
if let Some(errors) = self.checker.before_transform(program) {
|
||||
self.errors.extend(errors);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn after_transform(
|
||||
&mut self,
|
||||
program: &mut Program<'_>,
|
||||
transformer_return: &mut TransformerReturn,
|
||||
) -> ControlFlow<()> {
|
||||
if self.check_semantic {
|
||||
if let Some(errors) = self.checker.after_transform(
|
||||
&transformer_return.symbols,
|
||||
&transformer_return.scopes,
|
||||
program,
|
||||
) {
|
||||
self.errors.extend(errors);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
pub fn new(options: TransformOptions) -> Self {
|
||||
Self {
|
||||
options,
|
||||
printed: String::new(),
|
||||
errors: vec![],
|
||||
check_semantic: false,
|
||||
checker: PostTransformChecker::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ use oxc_tasks_common::{normalize_path, project_root, Snapshot};
|
|||
use test_case::TestCaseKind;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
mod semantic;
|
||||
mod driver;
|
||||
mod test_case;
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
use oxc_ast::{
|
||||
ast::{BindingIdentifier, ExportSpecifier, ImportSpecifier, ModuleExportName, Program},
|
||||
visit::walk::walk_import_specifier,
|
||||
Visit,
|
||||
};
|
||||
use oxc_semantic::{ScopeTree, SymbolTable};
|
||||
|
||||
pub struct SemanticTester {
|
||||
scopes: ScopeTree,
|
||||
symbols: SymbolTable,
|
||||
errors: Vec<String>,
|
||||
}
|
||||
|
||||
impl SemanticTester {
|
||||
pub fn new(scopes: ScopeTree, symbols: SymbolTable) -> Self {
|
||||
Self { scopes, symbols, errors: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn test(mut self, program: &Program) -> Vec<String> {
|
||||
self.visit_program(program);
|
||||
self.errors
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visit<'a> for SemanticTester {
|
||||
fn visit_binding_identifier(&mut self, it: &BindingIdentifier<'a>) {
|
||||
let symbol_id = it.symbol_id.get();
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
if self.symbols.get_flag(symbol_id).is_empty() {
|
||||
self.errors.push(format!(
|
||||
"Expect SymbolFlags for BindingIdentifier({}) to not be empty",
|
||||
it.name
|
||||
));
|
||||
}
|
||||
if !self.scopes.has_binding(self.symbols.get_scope_id(symbol_id), &it.name) {
|
||||
self.errors.push(format!(
|
||||
"Cannot find BindingIdentifier({}) in the Scope corresponding to the Symbol",
|
||||
it.name
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.errors.push(format!("Expect BindingIdentifier({}) to have a symbol_id", it.name));
|
||||
}
|
||||
}
|
||||
fn visit_identifier_reference(&mut self, it: &oxc_ast::ast::IdentifierReference<'a>) {
|
||||
if let Some(reference_id) = it.reference_id.get() {
|
||||
let reference = self.symbols.get_reference(reference_id);
|
||||
if reference.flag().is_empty() {
|
||||
self.errors.push(format!(
|
||||
"Expect ReferenceFlags for IdentifierReference({}) to not be empty",
|
||||
it.name
|
||||
));
|
||||
}
|
||||
} else {
|
||||
self.errors
|
||||
.push(format!("Expect IdentifierReference({}) to have a reference_id", it.name));
|
||||
}
|
||||
}
|
||||
fn visit_import_specifier(&mut self, it: &ImportSpecifier<'a>) {
|
||||
let symbol_id = it.local.symbol_id.get();
|
||||
if let Some(symbol_id) = symbol_id {
|
||||
if !self.symbols.get_flag(symbol_id).is_import() {
|
||||
self.errors.push(format!(
|
||||
"Expect SymbolFlags for ImportSpecifier({}) should contain SymbolFlags::Import",
|
||||
it.local.name
|
||||
));
|
||||
}
|
||||
}
|
||||
walk_import_specifier(self, it);
|
||||
}
|
||||
fn visit_export_specifier(&mut self, it: &ExportSpecifier<'a>) {
|
||||
if let ModuleExportName::IdentifierReference(ident) = &it.local {
|
||||
let reference_id = ident.reference_id.get();
|
||||
if let Some(symbol_id) = reference_id
|
||||
.and_then(|reference_id| self.symbols.get_reference(reference_id).symbol_id())
|
||||
{
|
||||
if self.symbols.get_flag(symbol_id).is_empty() {
|
||||
self.errors.push(format!(
|
||||
"Expect SymbolFlags for ExportSpecifier({}) should contain SymbolFlags::Import",
|
||||
it.local
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,19 +3,18 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::CodeGenerator;
|
||||
use oxc_diagnostics::{Error, OxcDiagnostic};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::{SourceType, VALID_EXTENSIONS};
|
||||
use oxc::allocator::Allocator;
|
||||
use oxc::codegen::CodeGenerator;
|
||||
use oxc::diagnostics::{Error, OxcDiagnostic};
|
||||
use oxc::parser::Parser;
|
||||
use oxc::span::{SourceType, VALID_EXTENSIONS};
|
||||
use oxc::transformer::{BabelOptions, TransformOptions};
|
||||
use oxc_tasks_common::{normalize_path, print_diff_in_terminal};
|
||||
use oxc_transformer::{BabelOptions, TransformOptions, Transformer};
|
||||
|
||||
use crate::{
|
||||
constants::{PLUGINS_NOT_SUPPORTED_YET, SKIP_TESTS},
|
||||
fixture_root, packages_root,
|
||||
semantic::SemanticTester,
|
||||
TestRunnerEnv,
|
||||
driver::Driver,
|
||||
fixture_root, packages_root, TestRunnerEnv,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -158,7 +157,6 @@ pub trait TestCase {
|
|||
}
|
||||
};
|
||||
|
||||
let allocator = Allocator::default();
|
||||
let source_text = fs::read_to_string(path).unwrap();
|
||||
|
||||
// Some babel test cases have a js extension, but contain typescript code.
|
||||
|
|
@ -171,22 +169,7 @@ pub trait TestCase {
|
|||
source_type = source_type.with_typescript(true);
|
||||
}
|
||||
|
||||
let ret = Parser::new(&allocator, &source_text, source_type).parse();
|
||||
let mut program = ret.program;
|
||||
let result = Transformer::new(
|
||||
&allocator,
|
||||
path,
|
||||
source_type,
|
||||
&source_text,
|
||||
ret.trivias.clone(),
|
||||
transform_options.clone(),
|
||||
)
|
||||
.build(&mut program);
|
||||
if result.errors.is_empty() {
|
||||
Ok(CodeGenerator::new().build(&program).source_text)
|
||||
} else {
|
||||
Err(result.errors)
|
||||
}
|
||||
Driver::new(transform_options.clone()).execute(&source_text, source_type, path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,44 +237,25 @@ impl TestCase for ConformanceTestCase {
|
|||
|
||||
let mut transformed_code = String::new();
|
||||
let mut actual_errors = String::new();
|
||||
let mut semantic_errors = Vec::default();
|
||||
|
||||
let transform_options = match self.transform_options() {
|
||||
Ok(transform_options) => {
|
||||
let ret = Parser::new(&allocator, &input, source_type).parse();
|
||||
if ret.errors.is_empty() {
|
||||
let mut program = ret.program;
|
||||
let transformer = Transformer::new(
|
||||
&allocator,
|
||||
&self.path,
|
||||
source_type,
|
||||
&input,
|
||||
ret.trivias.clone(),
|
||||
transform_options.clone(),
|
||||
);
|
||||
let ret = transformer.build(&mut program);
|
||||
|
||||
semantic_errors = SemanticTester::new(ret.scopes, ret.symbols).test(&program);
|
||||
|
||||
if ret.errors.is_empty() {
|
||||
transformed_code = CodeGenerator::new().build(&program).source_text;
|
||||
} else {
|
||||
let error = ret
|
||||
.errors
|
||||
match Driver::new(transform_options.clone()).execute(
|
||||
&input,
|
||||
source_type,
|
||||
&self.path,
|
||||
) {
|
||||
Ok(printed) => {
|
||||
transformed_code = printed;
|
||||
}
|
||||
Err(errors) => {
|
||||
let error = errors
|
||||
.into_iter()
|
||||
.map(|e| Error::from(e).to_string())
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
actual_errors = get_babel_error(&error);
|
||||
}
|
||||
} else {
|
||||
let error = ret
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
actual_errors = get_babel_error(&error);
|
||||
}
|
||||
Some(transform_options.clone())
|
||||
}
|
||||
|
|
@ -319,9 +283,8 @@ impl TestCase for ConformanceTestCase {
|
|||
},
|
||||
);
|
||||
|
||||
let passed = semantic_errors.is_empty()
|
||||
&& (transformed_code == output
|
||||
|| (!output.is_empty() && actual_errors.contains(&output)));
|
||||
let passed =
|
||||
transformed_code == output || (!output.is_empty() && actual_errors.contains(&output));
|
||||
|
||||
if filtered {
|
||||
println!("Options:");
|
||||
|
|
@ -350,10 +313,6 @@ impl TestCase for ConformanceTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
if !semantic_errors.is_empty() {
|
||||
println!("\nSemantic Errors:\n\n{}\n", semantic_errors.join("\n"));
|
||||
}
|
||||
|
||||
println!("Passed: {passed}");
|
||||
}
|
||||
passed
|
||||
|
|
|
|||
Loading…
Reference in a new issue