Skip to content

Commit 5d9864d

Browse files
ashley-hunterclaude
andcommitted
feat(compiler): strip TypeScript syntax from JIT output using oxc_transformer
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) <noreply@anthropic.com>
1 parent 1856309 commit 5d9864d

14 files changed

+405
-137
lines changed

Cargo.lock

Lines changed: 261 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ oxc_parser = "0.121"
9696
oxc_semantic = "0.121"
9797
oxc_span = "0.121"
9898
oxc_sourcemap = "6.0.1"
99+
oxc_transformer = "0.121"
100+
oxc_codegen = "0.121"
99101

100102
# Internal
101103
oxc_angular_compiler = { path = "crates/oxc_angular_compiler" }

crates/oxc_angular_compiler/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ oxc_parser = { workspace = true }
2323
oxc_semantic = { workspace = true }
2424
oxc_span = { workspace = true }
2525
oxc_sourcemap = { workspace = true }
26+
oxc_transformer = { workspace = true }
27+
oxc_codegen = { workspace = true }
2628
miette = { workspace = true }
2729
rustc-hash = { workspace = true }
2830
indexmap = { workspace = true }

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
use std::collections::HashMap;
77

8+
use std::path::Path;
9+
810
use oxc_allocator::{Allocator, Vec as OxcVec};
911
use oxc_ast::ast::{
1012
Argument, ArrayExpressionElement, Declaration, ExportDefaultDeclarationKind, Expression,
@@ -1140,6 +1142,37 @@ fn build_jit_decorator_text(
11401142

11411143
/// Transform an Angular TypeScript file in JIT (Just-In-Time) compilation mode.
11421144
///
1145+
/// Strip TypeScript syntax from JIT output using oxc_transformer.
1146+
///
1147+
/// This runs as a post-pass after JIT text-edits, converting TypeScript → JavaScript.
1148+
/// It handles abstract members, type annotations, parameter properties, etc.
1149+
fn strip_typescript(allocator: &Allocator, path: &str, code: &str) -> String {
1150+
let source_type = SourceType::from_path(path).unwrap_or_default();
1151+
let parser_ret = Parser::new(allocator, code, source_type).parse();
1152+
if parser_ret.panicked {
1153+
return code.to_string();
1154+
}
1155+
1156+
let mut program = parser_ret.program;
1157+
1158+
let semantic_ret =
1159+
oxc_semantic::SemanticBuilder::new().with_excess_capacity(2.0).build(&program);
1160+
1161+
let ts_options =
1162+
oxc_transformer::TypeScriptOptions { only_remove_type_imports: true, ..Default::default() };
1163+
1164+
let transform_options =
1165+
oxc_transformer::TransformOptions { typescript: ts_options, ..Default::default() };
1166+
1167+
let transformer =
1168+
oxc_transformer::Transformer::new(allocator, Path::new(path), &transform_options);
1169+
transformer.build_with_scoping(semantic_ret.semantic.into_scoping(), &mut program);
1170+
1171+
let codegen_ret = oxc_codegen::Codegen::new().with_source_text(code).build(&program);
1172+
1173+
codegen_ret.code
1174+
}
1175+
11431176
/// JIT mode produces output compatible with Angular's JIT runtime compiler:
11441177
/// - Decorators are downleveled using `__decorate` from tslib
11451178
/// - `templateUrl` is replaced with `angular:jit:template:file;` imports
@@ -1408,6 +1441,9 @@ fn transform_angular_file_jit(
14081441
result.code = apply_edits(source, edits);
14091442
}
14101443

1444+
// 5. Strip TypeScript syntax from JIT output
1445+
result.code = strip_typescript(allocator, path, &result.code);
1446+
14111447
result
14121448
}
14131449

crates/oxc_angular_compiler/tests/snapshots/integration_test__jit_abstract_class.snap

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,12 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs
33
assertion_line: 6452
44
expression: result.code
55
---
6-
7-
import { Injectable } from '@angular/core';
6+
import { Injectable } from "@angular/core";
87
import { __decorate } from "tslib";
9-
108
let BaseProvider = class BaseProvider {
11-
protected abstract get name(): string;
12-
protected abstract initialize(): void;
13-
14-
public greet(): string {
15-
return `Hello from ${this.name}`;
16-
}
9+
greet() {
10+
return `Hello from ${this.name}`;
11+
}
1712
};
18-
BaseProvider = __decorate([
19-
Injectable()
20-
], BaseProvider);
13+
BaseProvider = __decorate([Injectable()], BaseProvider);
2114
export { BaseProvider };
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
---
22
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6201
34
expression: result.code
45
---
5-
import { Component } from '@angular/core';
6+
import { Component } from "@angular/core";
67
import { __decorate } from "tslib";
7-
88
let AppComponent = class AppComponent {
9-
title = 'app';
9+
title = "app";
1010
};
11-
AppComponent = __decorate([
12-
Component({
13-
selector: 'app-root',
14-
template: '<h1>Hello</h1>',
15-
})
16-
], AppComponent);
11+
AppComponent = __decorate([Component({
12+
selector: "app-root",
13+
template: "<h1>Hello</h1>"
14+
})], AppComponent);
1715
export { AppComponent };
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
---
22
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6164
34
expression: result.code
45
---
5-
import { Component } from '@angular/core';
6-
import { TitleService } from './title.service';
6+
import { Component } from "@angular/core";
7+
import { TitleService } from "./title.service";
78
import { __decorate } from "tslib";
8-
99
let AppComponent = class AppComponent {
10-
constructor(private titleService: TitleService) {}
11-
12-
static ctorParameters = () => [
13-
{ type: TitleService }
14-
];
10+
constructor(titleService) {
11+
this.titleService = titleService;
12+
}
13+
static ctorParameters = () => [{ type: TitleService }];
1514
};
16-
AppComponent = __decorate([
17-
Component({
18-
selector: 'app-root',
19-
template: '<h1>Hello</h1>',
20-
})
21-
], AppComponent);
15+
AppComponent = __decorate([Component({
16+
selector: "app-root",
17+
template: "<h1>Hello</h1>"
18+
})], AppComponent);
2219
export { AppComponent };
Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
---
22
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6239
34
expression: result.code
45
---
5-
import { Directive, Input } from '@angular/core';
6+
import { Directive, Input } from "@angular/core";
67
import { __decorate } from "tslib";
7-
88
let HighlightDirective = class HighlightDirective {
9-
color: string = 'yellow';
10-
11-
static propDecorators = {
12-
color: [{ type: Input }]
9+
color = "yellow";
10+
static propDecorators = { color: [{ type: Input }] };
1311
};
14-
};
15-
HighlightDirective = __decorate([
16-
Directive({
17-
selector: '[appHighlight]',
18-
standalone: true,
19-
})
20-
], HighlightDirective);
12+
HighlightDirective = __decorate([Directive({
13+
selector: "[appHighlight]",
14+
standalone: true
15+
})], HighlightDirective);
2116
export { HighlightDirective };
Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,28 @@
11
---
22
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6295
34
expression: result.code
45
---
5-
import { Component, signal } from '@angular/core';
6-
import { RouterOutlet } from '@angular/router';
7-
import { Lib1 } from 'lib1';
8-
import { TitleService } from './title.service';
6+
import { Component, signal } from "@angular/core";
7+
import { RouterOutlet } from "@angular/router";
8+
import { Lib1 } from "lib1";
9+
import { TitleService } from "./title.service";
910
import { __decorate } from "tslib";
1011
import __NG_CLI_RESOURCE__0 from "angular:jit:template:file;./app.html";
1112
import __NG_CLI_RESOURCE__1 from "angular:jit:style:file;./app.css";
12-
1313
let App = class App {
14-
titleService;
15-
title = signal('app');
16-
constructor(titleService: TitleService) {
17-
this.titleService = titleService;
18-
this.title.set(this.titleService.getTitle());
19-
}
20-
21-
static ctorParameters = () => [
22-
{ type: TitleService }
23-
];
14+
titleService;
15+
title = signal("app");
16+
constructor(titleService) {
17+
this.titleService = titleService;
18+
this.title.set(this.titleService.getTitle());
19+
}
20+
static ctorParameters = () => [{ type: TitleService }];
2421
};
25-
App = __decorate([
26-
Component({
27-
selector: 'app-root',
28-
imports: [RouterOutlet, Lib1],
29-
template: __NG_CLI_RESOURCE__0,
30-
styles: [__NG_CLI_RESOURCE__1],
31-
})
32-
], App);
22+
App = __decorate([Component({
23+
selector: "app-root",
24+
imports: [RouterOutlet, Lib1],
25+
template: __NG_CLI_RESOURCE__0,
26+
styles: [__NG_CLI_RESOURCE__1]
27+
})], App);
3328
export { App };
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
---
22
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6059
34
expression: result.code
45
---
5-
import { Component } from '@angular/core';
6+
import { Component } from "@angular/core";
67
import { __decorate } from "tslib";
7-
88
let AppComponent = class AppComponent {};
9-
AppComponent = __decorate([
10-
Component({
11-
selector: 'app-root',
12-
template: '<h1>Hello</h1>',
13-
standalone: true,
14-
})
15-
], AppComponent);
9+
AppComponent = __decorate([Component({
10+
selector: "app-root",
11+
template: "<h1>Hello</h1>",
12+
standalone: true
13+
})], AppComponent);
1614
export { AppComponent };

0 commit comments

Comments
 (0)