diff --git a/Cargo.lock b/Cargo.lock index 0ec04c94009aa..24c3a228ddf67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1794,9 +1794,11 @@ version = "0.0.0" dependencies = [ "indexmap", "oxc_allocator", + "oxc_ast", "oxc_codegen", "oxc_diagnostics", "oxc_parser", + "oxc_semantic", "oxc_span", "oxc_tasks_common", "oxc_transformer", diff --git a/tasks/transform_conformance/Cargo.toml b/tasks/transform_conformance/Cargo.toml index e3a83f5544b84..9bc880c04ca33 100644 --- a/tasks/transform_conformance/Cargo.toml +++ b/tasks/transform_conformance/Cargo.toml @@ -22,10 +22,12 @@ 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_tasks_common = { workspace = true } oxc_diagnostics = { workspace = true } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index caab34c9dce50..4a12c218e42fb 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,9 +1,8 @@ commit: 12619ffe -Passed: 474/927 +Passed: 350/927 # All Passed: -* babel-preset-react * babel-plugin-transform-react-display-name * babel-plugin-transform-react-jsx-self * babel-plugin-transform-react-jsx-source @@ -445,16 +444,56 @@ Passed: 474/927 * opts/optimizeConstEnums/input.ts * opts/rewriteImportExtensions/input.ts -# babel-plugin-transform-typescript (130/151) +# babel-plugin-transform-typescript (90/151) +* catch-clause/param-type/input.ts * class/accessor-allowDeclareFields-false/input.ts * class/accessor-allowDeclareFields-true/input.ts +* enum/boolean-value/input.ts +* enum/const/input.ts +* enum/constant-folding/input.ts +* enum/enum-merging-inner-references/input.ts +* enum/enum-merging-inner-references-shadow/input.ts +* enum/export/input.ts +* enum/inferred/input.ts +* enum/inner-references/input.ts * enum/mix-references/input.ts +* enum/non-foldable-constant/input.ts +* enum/non-scoped/input.ts +* enum/outer-references/input.ts +* enum/scoped/input.ts +* enum/string-value/input.ts +* enum/string-value-template/input.ts +* enum/string-values-computed/input.ts * enum/ts5.0-const-foldable/input.ts * exports/declared-types/input.ts +* exports/export-const-enums/input.ts +* exports/export-import=/input.ts * exports/interface/input.ts +* imports/elide-type-referenced-in-imports-equal-no/input.ts +* imports/enum-id/input.ts +* imports/enum-value/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 @@ -468,9 +507,95 @@ Passed: 474/927 * optimize-const-enums/merged-exported/input.ts * regression/15768/input.ts -# babel-plugin-transform-react-jsx (141/142) +# babel-preset-react (6/9) +* preset-options/development-runtime-automatic/input.js +* preset-options/empty-options/input.js +* preset-options/runtime-automatic/input.js + +# babel-plugin-transform-react-jsx (68/142) +* autoImport/after-polyfills/input.mjs +* autoImport/after-polyfills-2/input.mjs +* autoImport/after-polyfills-script-not-supported/input.js +* autoImport/auto-import-react-source-type-module/input.js +* autoImport/auto-import-react-source-type-script/input.js +* autoImport/complicated-scope-module/input.js +* autoImport/complicated-scope-script/input.js +* autoImport/import-source/input.js +* autoImport/import-source-pragma/input.js +* autoImport/react-defined/input.js +* pure/false-default-pragma-automatic-runtime/input.js +* pure/false-pragma-comment-automatic-runtime/input.js +* pure/false-pragma-option-automatic-runtime/input.js +* pure/true-default-pragma-automatic-runtime/input.js +* pure/true-pragma-comment-automatic-runtime/input.js +* pure/true-pragma-option-automatic-runtime/input.js +* pure/unset-default-pragma-automatic-runtime/input.js +* pure/unset-pragma-comment-automatic-runtime/input.js +* pure/unset-pragma-option-automatic-runtime/input.js +* react-automatic/adds-appropriate-newlines-when-using-spread-attribute/input.js +* react-automatic/arrow-functions/input.js +* react-automatic/assignment/input.js +* react-automatic/concatenates-adjacent-string-literals/input.js * react-automatic/does-not-add-source-self-automatic/input.mjs +* react-automatic/dont-coerce-expression-containers/input.js +* react-automatic/duplicate-props/input.js +* react-automatic/flattens-spread/input.js +* react-automatic/handle-fragments/input.js +* react-automatic/handle-fragments-with-key/input.js +* react-automatic/handle-fragments-with-no-children/input.js +* react-automatic/handle-nonstatic-children/input.js +* react-automatic/handle-spread-with-proto/input.js +* react-automatic/handle-static-children/input.js +* react-automatic/jsx-with-retainlines-option/input.js +* react-automatic/jsx-without-retainlines-option/input.js +* react-automatic/key-undefined-works/input.js +* react-automatic/pragma-works-with-no-space-at-the-end/input.js +* react-automatic/should-allow-constructor-as-prop/input.js +* react-automatic/should-allow-deeper-js-namespacing/input.js +* react-automatic/should-allow-elements-as-attributes/input.js +* react-automatic/should-allow-js-namespacing/input.js +* react-automatic/should-allow-nested-fragments/input.js +* react-automatic/should-avoid-wrapping-in-extra-parens-if-not-needed/input.js +* react-automatic/should-convert-simple-tags/input.js +* react-automatic/should-convert-simple-text/input.js +* react-automatic/should-disallow-spread-children/input.js +* react-automatic/should-disallow-valueless-key/input.js +* react-automatic/should-disallow-xml-namespacing/input.js +* react-automatic/should-escape-xhtml-jsxattribute/input.js +* react-automatic/should-escape-xhtml-jsxtext/input.js +* react-automatic/should-handle-attributed-elements/input.js +* react-automatic/should-handle-has-own-property-correctly/input.js +* react-automatic/should-have-correct-comma-in-nested-children/input.js +* react-automatic/should-insert-commas-after-expressions-before-whitespace/input.js +* react-automatic/should-not-add-quotes-to-identifier-names/input.js +* react-automatic/should-not-mangle-expressioncontainer-attribute-values/input.js +* react-automatic/should-not-strip-nbsp-even-coupled-with-other-whitespace/input.js +* react-automatic/should-not-strip-tags-with-a-single-child-of-nbsp/input.js +* react-automatic/should-properly-handle-comments-between-props/input.js +* react-automatic/should-properly-handle-keys/input.js +* react-automatic/should-properly-handle-null-prop-spread/input.js +* react-automatic/should-quote-jsx-attributes/input.js +* react-automatic/should-support-xml-namespaces-if-flag/input.js +* react-automatic/should-throw-error-namespaces-if-not-flag/input.js +* react-automatic/should-transform-known-hyphenated-tags/input.js +* react-automatic/should-use-createElement-when-key-comes-after-spread/input.js +* react-automatic/should-use-jsx-when-key-comes-before-spread/input.js +* react-automatic/should-warn-when-pragma-or-pragmaFrag-is-set/input.js +* react-automatic/this-tag-name/input.js +* react-automatic/weird-symbols/input.js +* react-automatic/wraps-props-in-react-spread-for-last-spread-attributes/input.js +* react-automatic/wraps-props-in-react-spread-for-middle-spread-attributes/input.js +* runtime/defaults-to-automatic/input.js +* runtime/runtime-automatic/input.js -# babel-plugin-transform-react-jsx-development (9/10) +# babel-plugin-transform-react-jsx-development (1/10) +* cross-platform/auto-import-dev/input.js +* cross-platform/disallow-__self-as-jsx-attribute/input.js +* cross-platform/disallow-__source-as-jsx-attribute/input.js +* cross-platform/fragments/input.js +* cross-platform/handle-fragments-with-key/input.js +* cross-platform/handle-nonstatic-children/input.js +* cross-platform/handle-static-children/input.js +* cross-platform/within-derived-classes-constructor/input.js * cross-platform/within-ts-module-block/input.ts diff --git a/tasks/transform_conformance/oxc.snap.md b/tasks/transform_conformance/oxc.snap.md index a520ee4c368b4..b6c6b0460f430 100644 --- a/tasks/transform_conformance/oxc.snap.md +++ b/tasks/transform_conformance/oxc.snap.md @@ -1,9 +1,16 @@ commit: 12619ffe -Passed: 7/7 +Passed: 3/7 # All Passed: -* babel-plugin-transform-typescript -* babel-plugin-transform-react-jsx + +# babel-plugin-transform-typescript (3/6) +* computed-constant-value/input.ts +* enum-member-reference/input.ts +* export-elimination/input.ts + +# babel-plugin-transform-react-jsx (0/1) +* unicode/input.jsx + diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index a61092aa93989..298ffc158b54e 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -13,6 +13,7 @@ use oxc_tasks_common::{normalize_path, project_root, Snapshot}; use test_case::TestCaseKind; use walkdir::WalkDir; +mod semantic; mod test_case; #[test] diff --git a/tasks/transform_conformance/src/semantic.rs b/tasks/transform_conformance/src/semantic.rs new file mode 100644 index 0000000000000..4f1332202622a --- /dev/null +++ b/tasks/transform_conformance/src/semantic.rs @@ -0,0 +1,86 @@ +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, +} + +impl SemanticTester { + pub fn new(scopes: ScopeTree, symbols: SymbolTable) -> Self { + Self { scopes, symbols, errors: Vec::new() } + } + + pub fn test(mut self, program: &Program) -> Vec { + 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 + )); + } + } + } + } +} diff --git a/tasks/transform_conformance/src/test_case.rs b/tasks/transform_conformance/src/test_case.rs index 9e9539d5f3a00..21b1d2de0cfb0 100644 --- a/tasks/transform_conformance/src/test_case.rs +++ b/tasks/transform_conformance/src/test_case.rs @@ -13,7 +13,9 @@ use oxc_transformer::{BabelOptions, TransformOptions, Transformer}; use crate::{ constants::{PLUGINS_NOT_SUPPORTED_YET, SKIP_TESTS}, - fixture_root, packages_root, TestRunnerEnv, + fixture_root, packages_root, + semantic::SemanticTester, + TestRunnerEnv, }; #[derive(Debug)] @@ -252,6 +254,7 @@ 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) => { @@ -267,6 +270,9 @@ impl TestCase for ConformanceTestCase { 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 { @@ -313,8 +319,10 @@ impl TestCase for ConformanceTestCase { }, ); - let passed = - transformed_code == output || (!output.is_empty() && actual_errors.contains(&output)); + let passed = semantic_errors.is_empty() + && (transformed_code == output + || (!output.is_empty() && actual_errors.contains(&output))); + if filtered { println!("Options:"); println!("{transform_options:#?}\n"); @@ -341,6 +349,11 @@ impl TestCase for ConformanceTestCase { print_diff_in_terminal(&output, &transformed_code); } } + + if !semantic_errors.is_empty() { + println!("\nSemantic Errors:\n\n{}\n", semantic_errors.join("\n")); + } + println!("Passed: {passed}"); } passed