From 82dbff5c7e5fc84898c875a3a569ab7189a1cc6d Mon Sep 17 00:00:00 2001 From: Ashley Hunter Date: Sat, 21 Mar 2026 22:32:37 +0000 Subject: [PATCH 1/2] fix(compiler): strip abstract keyword from class expressions in JIT mode --- .../src/component/transform.rs | 17 ++++++- .../tests/integration_test.rs | 46 +++++++++++++++++++ .../integration_test__jit_abstract_class.snap | 21 +++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs index 636906434..c2bfbba42 100644 --- a/crates/oxc_angular_compiler/src/component/transform.rs +++ b/crates/oxc_angular_compiler/src/component/transform.rs @@ -695,6 +695,8 @@ struct JitClassInfo { is_exported: bool, /// Whether the class is export default. is_default_export: bool, + /// Whether the class is abstract. + is_abstract: bool, /// Constructor parameter info for ctorParameters. ctor_params: std::vec::Vec, /// Member decorator info for propDecorators. @@ -1224,6 +1226,7 @@ fn transform_angular_file_jit( class_body_end: class.body.span.end, is_exported, is_default_export, + is_abstract: class.r#abstract, ctor_params, member_decorators, decorator_text, @@ -1343,15 +1346,25 @@ fn transform_angular_file_jit( } // 4c. Class restructuring: `export class X` → `let X = class X` + // For abstract classes, also strip the `abstract` keyword since class expressions can't be abstract. + let class_keyword_start = if jit_info.is_abstract { + let rest = &source[jit_info.class_start as usize..]; + let offset = rest.find("class").unwrap_or(0); + jit_info.class_start + offset as u32 + } else { + jit_info.class_start + }; + if jit_info.is_exported || jit_info.is_default_export { edits.push(Edit::replace( jit_info.stmt_start, - jit_info.class_start, + class_keyword_start, format!("let {} = ", jit_info.class_name), )); } else { - edits.push(Edit::insert( + edits.push(Edit::replace( jit_info.class_start, + class_keyword_start, format!("let {} = ", jit_info.class_name), )); } diff --git a/crates/oxc_angular_compiler/tests/integration_test.rs b/crates/oxc_angular_compiler/tests/integration_test.rs index 490d5c8e8..4be2806dc 100644 --- a/crates/oxc_angular_compiler/tests/integration_test.rs +++ b/crates/oxc_angular_compiler/tests/integration_test.rs @@ -6406,6 +6406,52 @@ export class TestComponent { insta::assert_snapshot!("jit_union_type_ctor_params", result.code); } +#[test] +fn test_jit_abstract_class() { + let allocator = Allocator::default(); + let source = r#" +import { Injectable } from '@angular/core'; + +@Injectable() +export abstract class BaseProvider { + protected abstract get name(): string; + protected abstract initialize(): void; + + public greet(): string { + return `Hello from ${this.name}`; + } +} +"#; + + let options = ComponentTransformOptions { jit: true, ..Default::default() }; + let result = transform_angular_file(&allocator, "base.provider.ts", source, &options, None); + assert!(!result.has_errors(), "Should not have errors: {:?}", result.diagnostics); + + // The abstract keyword should NOT appear before "class" in the output + // (JIT converts to class expression which can't be abstract) + assert!( + !result.code.contains("abstract class"), + "JIT output should not contain 'abstract class'. Got:\n{}", + result.code + ); + + // Should have proper class expression + assert!( + result.code.contains("let BaseProvider = class BaseProvider"), + "JIT output should have class expression. Got:\n{}", + result.code + ); + + // Should have __decorate call + assert!( + result.code.contains("__decorate("), + "JIT output should use __decorate. Got:\n{}", + result.code + ); + + insta::assert_snapshot!("jit_abstract_class", result.code); +} + // ========================================================================= // Source map tests // ========================================================================= diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap new file mode 100644 index 000000000..0a54dbb93 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6452 +expression: result.code +--- + +import { Injectable } from '@angular/core'; +import { __decorate } from "tslib"; + +let BaseProvider = class BaseProvider { + protected abstract get name(): string; + protected abstract initialize(): void; + + public greet(): string { + return `Hello from ${this.name}`; + } +}; +BaseProvider = __decorate([ + Injectable() +], BaseProvider); +export { BaseProvider }; From 48cecda7a8ee06aed1df4f11e58034643eac13ff Mon Sep 17 00:00:00 2001 From: Ashley Hunter Date: Mon, 23 Mar 2026 20:02:24 +0000 Subject: [PATCH 2/2] feat(compiler): strip TypeScript syntax from JIT output using oxc_transformer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `strip_typescript()` post-pass after JIT text-edits that uses oxc_transformer + oxc_codegen to convert TypeScript → JavaScript. This handles abstract members, type annotations, parameter properties, and other TS-only syntax that previously leaked into JIT output. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 306 ++++++++++++++++-- Cargo.toml | 2 + crates/oxc_angular_compiler/Cargo.toml | 2 + .../src/component/transform.rs | 36 +++ .../integration_test__jit_abstract_class.snap | 17 +- ...gration_test__jit_class_restructuring.snap | 16 +- ...ntegration_test__jit_constructor_deps.snap | 25 +- .../integration_test__jit_directive.snap | 21 +- .../integration_test__jit_full_component.snap | 41 ++- ...integration_test__jit_inline_template.snap | 16 +- ...integration_test__jit_prop_decorators.snap | 37 +-- .../integration_test__jit_style_url.snap | 16 +- .../integration_test__jit_template_url.snap | 16 +- ...tion_test__jit_union_type_ctor_params.snap | 36 +-- 14 files changed, 432 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60f0062ee..64ccc2f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.7.8" @@ -59,6 +65,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64-simd" version = "0.7.0" @@ -99,6 +111,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.12.1" @@ -154,9 +175,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -177,6 +198,15 @@ dependencies = [ "cc", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -201,7 +231,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -248,6 +278,24 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -273,6 +321,16 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" version = "0.33.0" @@ -349,6 +407,16 @@ dependencies = [ "matches", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -402,6 +470,18 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -421,7 +501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -445,6 +525,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.10.0" @@ -569,6 +659,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -822,9 +922,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" @@ -865,9 +965,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -978,6 +1078,16 @@ dependencies = [ "libmimalloc-sys2", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "napi" version = "3.8.3" @@ -1084,9 +1194,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "outref" @@ -1106,6 +1216,19 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "oxc-browserslist" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f358f5705f4da4bce0af2792b6d24d64be9c465bdc70a752b2f168c5ff721" +dependencies = [ + "flate2", + "postcard", + "rustc-hash", + "serde", + "thiserror", +] + [[package]] name = "oxc-miette" version = "2.7.0" @@ -1156,12 +1279,14 @@ dependencies = [ "oxc-miette", "oxc_allocator", "oxc_ast", + "oxc_codegen", "oxc_diagnostics", "oxc_parser", "oxc_resolver", "oxc_semantic", "oxc_sourcemap", "oxc_span", + "oxc_transformer", "pathdiff", "rustc-hash", "semver", @@ -1242,11 +1367,48 @@ dependencies = [ "oxc_syntax", ] +[[package]] +name = "oxc_codegen" +version = "0.122.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8af47790edfd7cc2d35ff47b70a1746c73388cc498c7f470a9cdc35f89375c" +dependencies = [ + "bitflags", + "cow-utils", + "dragonbox_ecma", + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_data_structures", + "oxc_index", + "oxc_semantic", + "oxc_sourcemap", + "oxc_span", + "oxc_syntax", + "rustc-hash", +] + +[[package]] +name = "oxc_compat" +version = "0.122.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3103453f49b58f20dfb5d0d7be109c44975b436ad056fdb046db03e971ee9f64" +dependencies = [ + "cow-utils", + "oxc-browserslist", + "oxc_syntax", + "rustc-hash", + "serde", +] + [[package]] name = "oxc_data_structures" version = "0.122.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "623bffc9732a0d39f248a2e7655d6d1704201790e5a8777aa188a678f1746fe8" +dependencies = [ + "ropey", +] [[package]] name = "oxc_diagnostics" @@ -1459,6 +1621,54 @@ dependencies = [ "unicode-id-start", ] +[[package]] +name = "oxc_transformer" +version = "0.122.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a216c0a1291fcb42f6be51ce32d928921cf2a6e232e43e6339c8e48d0e4048f" +dependencies = [ + "base64", + "compact_str", + "indexmap", + "itoa", + "memchr", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_compat", + "oxc_data_structures", + "oxc_diagnostics", + "oxc_ecmascript", + "oxc_regular_expression", + "oxc_semantic", + "oxc_span", + "oxc_syntax", + "oxc_traverse", + "rustc-hash", + "serde", + "serde_json", + "sha1", +] + +[[package]] +name = "oxc_traverse" +version = "0.122.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1d4f7d8539ccc032bf20a837b075a301a7846c6ded266a7a1889f0cfcae038" +dependencies = [ + "itoa", + "oxc_allocator", + "oxc_ast", + "oxc_ast_visit", + "oxc_data_structures", + "oxc_ecmascript", + "oxc_semantic", + "oxc_span", + "oxc_str", + "oxc_syntax", + "rustc-hash", +] + [[package]] name = "papaya" version = "0.2.3" @@ -1637,6 +1847,18 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -1843,6 +2065,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -1859,7 +2091,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1902,7 +2134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1976,6 +2208,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1991,6 +2234,12 @@ dependencies = [ "outref 0.1.0", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simd-json" version = "0.17.0" @@ -2051,6 +2300,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + [[package]] name = "syn" version = "1.0.109" @@ -2100,7 +2355,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2199,6 +2454,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-id-start" version = "1.4.0" @@ -2412,7 +2673,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2525,6 +2786,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2726,18 +2996,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0f58164fc..fab56acfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,8 @@ oxc_parser = "0.122" oxc_semantic = "0.122" oxc_span = "0.122" oxc_sourcemap = "6.0.1" +oxc_transformer = "0.122" +oxc_codegen = "0.122" # Internal oxc_angular_compiler = { path = "crates/oxc_angular_compiler" } diff --git a/crates/oxc_angular_compiler/Cargo.toml b/crates/oxc_angular_compiler/Cargo.toml index 0712ece2b..d2db6c6c6 100644 --- a/crates/oxc_angular_compiler/Cargo.toml +++ b/crates/oxc_angular_compiler/Cargo.toml @@ -23,6 +23,8 @@ oxc_parser = { workspace = true } oxc_semantic = { workspace = true } oxc_span = { workspace = true } oxc_sourcemap = { workspace = true } +oxc_transformer = { workspace = true } +oxc_codegen = { workspace = true } miette = { workspace = true } rustc-hash = { workspace = true } indexmap = { workspace = true } diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs index c2bfbba42..fc3f903d9 100644 --- a/crates/oxc_angular_compiler/src/component/transform.rs +++ b/crates/oxc_angular_compiler/src/component/transform.rs @@ -5,6 +5,8 @@ use std::collections::HashMap; +use std::path::Path; + use oxc_allocator::{Allocator, Vec as OxcVec}; use oxc_ast::ast::{ Argument, ArrayExpressionElement, Declaration, ExportDefaultDeclarationKind, Expression, @@ -1140,6 +1142,37 @@ fn build_jit_decorator_text( /// Transform an Angular TypeScript file in JIT (Just-In-Time) compilation mode. /// +/// Strip TypeScript syntax from JIT output using oxc_transformer. +/// +/// This runs as a post-pass after JIT text-edits, converting TypeScript → JavaScript. +/// It handles abstract members, type annotations, parameter properties, etc. +fn strip_typescript(allocator: &Allocator, path: &str, code: &str) -> String { + let source_type = SourceType::from_path(path).unwrap_or_default(); + let parser_ret = Parser::new(allocator, code, source_type).parse(); + if parser_ret.panicked { + return code.to_string(); + } + + let mut program = parser_ret.program; + + let semantic_ret = + oxc_semantic::SemanticBuilder::new().with_excess_capacity(2.0).build(&program); + + let ts_options = + oxc_transformer::TypeScriptOptions { only_remove_type_imports: true, ..Default::default() }; + + let transform_options = + oxc_transformer::TransformOptions { typescript: ts_options, ..Default::default() }; + + let transformer = + oxc_transformer::Transformer::new(allocator, Path::new(path), &transform_options); + transformer.build_with_scoping(semantic_ret.semantic.into_scoping(), &mut program); + + let codegen_ret = oxc_codegen::Codegen::new().with_source_text(code).build(&program); + + codegen_ret.code +} + /// JIT mode produces output compatible with Angular's JIT runtime compiler: /// - Decorators are downleveled using `__decorate` from tslib /// - `templateUrl` is replaced with `angular:jit:template:file;` imports @@ -1408,6 +1441,9 @@ fn transform_angular_file_jit( result.code = apply_edits(source, edits); } + // 5. Strip TypeScript syntax from JIT output + result.code = strip_typescript(allocator, path, &result.code); + result } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap index 0a54dbb93..ee2c69e80 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap @@ -3,19 +3,12 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs assertion_line: 6452 expression: result.code --- - -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; import { __decorate } from "tslib"; - let BaseProvider = class BaseProvider { - protected abstract get name(): string; - protected abstract initialize(): void; - - public greet(): string { - return `Hello from ${this.name}`; - } + greet() { + return `Hello from ${this.name}`; + } }; -BaseProvider = __decorate([ - Injectable() -], BaseProvider); +BaseProvider = __decorate([Injectable()], BaseProvider); export { BaseProvider }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_class_restructuring.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_class_restructuring.snap index 3f5f2f3e7..8cad539a8 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_class_restructuring.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_class_restructuring.snap @@ -1,17 +1,15 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6201 expression: result.code --- -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; import { __decorate } from "tslib"; - let AppComponent = class AppComponent { - title = 'app'; + title = "app"; }; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: '

Hello

', -}) -], AppComponent); +AppComponent = __decorate([Component({ + selector: "app-root", + template: "

Hello

" +})], AppComponent); export { AppComponent }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_constructor_deps.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_constructor_deps.snap index 418dddae1..aa55f61f4 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_constructor_deps.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_constructor_deps.snap @@ -1,22 +1,19 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6164 expression: result.code --- -import { Component } from '@angular/core'; -import { TitleService } from './title.service'; +import { Component } from "@angular/core"; +import { TitleService } from "./title.service"; import { __decorate } from "tslib"; - let AppComponent = class AppComponent { - constructor(private titleService: TitleService) {} - -static ctorParameters = () => [ - { type: TitleService } -]; + constructor(titleService) { + this.titleService = titleService; + } + static ctorParameters = () => [{ type: TitleService }]; }; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: '

Hello

', -}) -], AppComponent); +AppComponent = __decorate([Component({ + selector: "app-root", + template: "

Hello

" +})], AppComponent); export { AppComponent }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_directive.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_directive.snap index 7d7147027..19fcd82d8 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_directive.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_directive.snap @@ -1,21 +1,16 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6239 expression: result.code --- -import { Directive, Input } from '@angular/core'; +import { Directive, Input } from "@angular/core"; import { __decorate } from "tslib"; - let HighlightDirective = class HighlightDirective { - color: string = 'yellow'; - -static propDecorators = { - color: [{ type: Input }] + color = "yellow"; + static propDecorators = { color: [{ type: Input }] }; }; -}; -HighlightDirective = __decorate([ - Directive({ - selector: '[appHighlight]', - standalone: true, -}) -], HighlightDirective); +HighlightDirective = __decorate([Directive({ + selector: "[appHighlight]", + standalone: true +})], HighlightDirective); export { HighlightDirective }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_full_component.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_full_component.snap index 43522bbed..fc1b97be4 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_full_component.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_full_component.snap @@ -1,33 +1,28 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6295 expression: result.code --- -import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; -import { Lib1 } from 'lib1'; -import { TitleService } from './title.service'; +import { Component, signal } from "@angular/core"; +import { RouterOutlet } from "@angular/router"; +import { Lib1 } from "lib1"; +import { TitleService } from "./title.service"; import { __decorate } from "tslib"; import __NG_CLI_RESOURCE__0 from "angular:jit:template:file;./app.html"; import __NG_CLI_RESOURCE__1 from "angular:jit:style:file;./app.css"; - let App = class App { - titleService; - title = signal('app'); - constructor(titleService: TitleService) { - this.titleService = titleService; - this.title.set(this.titleService.getTitle()); - } - -static ctorParameters = () => [ - { type: TitleService } -]; + titleService; + title = signal("app"); + constructor(titleService) { + this.titleService = titleService; + this.title.set(this.titleService.getTitle()); + } + static ctorParameters = () => [{ type: TitleService }]; }; -App = __decorate([ - Component({ - selector: 'app-root', - imports: [RouterOutlet, Lib1], - template: __NG_CLI_RESOURCE__0, - styles: [__NG_CLI_RESOURCE__1], -}) -], App); +App = __decorate([Component({ + selector: "app-root", + imports: [RouterOutlet, Lib1], + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1] +})], App); export { App }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_inline_template.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_inline_template.snap index 0589aaf1c..6516bad0d 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_inline_template.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_inline_template.snap @@ -1,16 +1,14 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6059 expression: result.code --- -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; import { __decorate } from "tslib"; - let AppComponent = class AppComponent {}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: '

Hello

', - standalone: true, -}) -], AppComponent); +AppComponent = __decorate([Component({ + selector: "app-root", + template: "

Hello

", + standalone: true +})], AppComponent); export { AppComponent }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_prop_decorators.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_prop_decorators.snap index 3b25eeaff..eeba7c537 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_prop_decorators.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_prop_decorators.snap @@ -1,26 +1,27 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6351 expression: result.code --- -import { Directive, Input, Output, HostBinding, EventEmitter } from '@angular/core'; +import { Directive, Input, Output, HostBinding, EventEmitter } from "@angular/core"; import { __decorate } from "tslib"; - let HighlightDirective = class HighlightDirective { - color: string = 'yellow'; - title: string = ''; - colorChange = new EventEmitter(); - isActive = false; - -static propDecorators = { - color: [{ type: Input }], - title: [{ type: Input, args: ['aliasName'] }], - colorChange: [{ type: Output }], - isActive: [{ type: HostBinding, args: ['class.active'] }] + color = "yellow"; + title = ""; + colorChange = new EventEmitter(); + isActive = false; + static propDecorators = { + color: [{ type: Input }], + title: [{ + type: Input, + args: ["aliasName"] + }], + colorChange: [{ type: Output }], + isActive: [{ + type: HostBinding, + args: ["class.active"] + }] + }; }; -}; -HighlightDirective = __decorate([ - Directive({ - selector: '[appHighlight]', -}) -], HighlightDirective); +HighlightDirective = __decorate([Directive({ selector: "[appHighlight]" })], HighlightDirective); export { HighlightDirective }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_style_url.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_style_url.snap index f4deebe5f..2b6ad2aed 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_style_url.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_style_url.snap @@ -1,17 +1,15 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6126 expression: result.code --- -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; import { __decorate } from "tslib"; import __NG_CLI_RESOURCE__0 from "angular:jit:style:file;./app.css"; - let AppComponent = class AppComponent {}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: '

Hello

', - styles: [__NG_CLI_RESOURCE__0], -}) -], AppComponent); +AppComponent = __decorate([Component({ + selector: "app-root", + template: "

Hello

", + styles: [__NG_CLI_RESOURCE__0] +})], AppComponent); export { AppComponent }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_template_url.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_template_url.snap index e484ba5c7..36550eec9 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_template_url.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_template_url.snap @@ -1,17 +1,15 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs +assertion_line: 6096 expression: result.code --- -import { Component } from '@angular/core'; +import { Component } from "@angular/core"; import { __decorate } from "tslib"; import __NG_CLI_RESOURCE__0 from "angular:jit:template:file;./app.html"; - let AppComponent = class AppComponent {}; -AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: __NG_CLI_RESOURCE__0, - standalone: true, -}) -], AppComponent); +AppComponent = __decorate([Component({ + selector: "app-root", + template: __NG_CLI_RESOURCE__0, + standalone: true +})], AppComponent); export { AppComponent }; diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_union_type_ctor_params.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_union_type_ctor_params.snap index 7b1d8cc21..7a34b853c 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_union_type_ctor_params.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_union_type_ctor_params.snap @@ -1,29 +1,23 @@ --- source: crates/oxc_angular_compiler/tests/integration_test.rs -assertion_line: 6360 +assertion_line: 6406 expression: result.code --- - -import { Component } from '@angular/core'; -import { ServiceA } from './a.service'; -import { ServiceB } from './b.service'; -import { ServiceC } from './c.service'; +import { Component } from "@angular/core"; +import { ServiceA } from "./a.service"; +import { ServiceB } from "./b.service"; +import { ServiceC } from "./c.service"; import { __decorate } from "tslib"; - let TestComponent = class TestComponent { - constructor( - svcA: undefined | ServiceA, - svcB: null | undefined | ServiceB, - svcC: ServiceC | null, - ) {} - -static ctorParameters = () => [ - { type: undefined }, - { type: undefined }, - { type: ServiceC } -]; + constructor(svcA, svcB, svcC) {} + static ctorParameters = () => [ + { type: undefined }, + { type: undefined }, + { type: ServiceC } + ]; }; -TestComponent = __decorate([ - Component({ selector: 'test', template: '' }) -], TestComponent); +TestComponent = __decorate([Component({ + selector: "test", + template: "" +})], TestComponent); export { TestComponent };