Skip to content

Commit 82dbff5

Browse files
committed
fix(compiler): strip abstract keyword from class expressions in JIT mode
1 parent 5cc1a58 commit 82dbff5

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,8 @@ struct JitClassInfo {
695695
is_exported: bool,
696696
/// Whether the class is export default.
697697
is_default_export: bool,
698+
/// Whether the class is abstract.
699+
is_abstract: bool,
698700
/// Constructor parameter info for ctorParameters.
699701
ctor_params: std::vec::Vec<JitCtorParam>,
700702
/// Member decorator info for propDecorators.
@@ -1224,6 +1226,7 @@ fn transform_angular_file_jit(
12241226
class_body_end: class.body.span.end,
12251227
is_exported,
12261228
is_default_export,
1229+
is_abstract: class.r#abstract,
12271230
ctor_params,
12281231
member_decorators,
12291232
decorator_text,
@@ -1343,15 +1346,25 @@ fn transform_angular_file_jit(
13431346
}
13441347

13451348
// 4c. Class restructuring: `export class X` → `let X = class X`
1349+
// For abstract classes, also strip the `abstract` keyword since class expressions can't be abstract.
1350+
let class_keyword_start = if jit_info.is_abstract {
1351+
let rest = &source[jit_info.class_start as usize..];
1352+
let offset = rest.find("class").unwrap_or(0);
1353+
jit_info.class_start + offset as u32
1354+
} else {
1355+
jit_info.class_start
1356+
};
1357+
13461358
if jit_info.is_exported || jit_info.is_default_export {
13471359
edits.push(Edit::replace(
13481360
jit_info.stmt_start,
1349-
jit_info.class_start,
1361+
class_keyword_start,
13501362
format!("let {} = ", jit_info.class_name),
13511363
));
13521364
} else {
1353-
edits.push(Edit::insert(
1365+
edits.push(Edit::replace(
13541366
jit_info.class_start,
1367+
class_keyword_start,
13551368
format!("let {} = ", jit_info.class_name),
13561369
));
13571370
}

crates/oxc_angular_compiler/tests/integration_test.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6406,6 +6406,52 @@ export class TestComponent {
64066406
insta::assert_snapshot!("jit_union_type_ctor_params", result.code);
64076407
}
64086408

6409+
#[test]
6410+
fn test_jit_abstract_class() {
6411+
let allocator = Allocator::default();
6412+
let source = r#"
6413+
import { Injectable } from '@angular/core';
6414+
6415+
@Injectable()
6416+
export abstract class BaseProvider {
6417+
protected abstract get name(): string;
6418+
protected abstract initialize(): void;
6419+
6420+
public greet(): string {
6421+
return `Hello from ${this.name}`;
6422+
}
6423+
}
6424+
"#;
6425+
6426+
let options = ComponentTransformOptions { jit: true, ..Default::default() };
6427+
let result = transform_angular_file(&allocator, "base.provider.ts", source, &options, None);
6428+
assert!(!result.has_errors(), "Should not have errors: {:?}", result.diagnostics);
6429+
6430+
// The abstract keyword should NOT appear before "class" in the output
6431+
// (JIT converts to class expression which can't be abstract)
6432+
assert!(
6433+
!result.code.contains("abstract class"),
6434+
"JIT output should not contain 'abstract class'. Got:\n{}",
6435+
result.code
6436+
);
6437+
6438+
// Should have proper class expression
6439+
assert!(
6440+
result.code.contains("let BaseProvider = class BaseProvider"),
6441+
"JIT output should have class expression. Got:\n{}",
6442+
result.code
6443+
);
6444+
6445+
// Should have __decorate call
6446+
assert!(
6447+
result.code.contains("__decorate("),
6448+
"JIT output should use __decorate. Got:\n{}",
6449+
result.code
6450+
);
6451+
6452+
insta::assert_snapshot!("jit_abstract_class", result.code);
6453+
}
6454+
64096455
// =========================================================================
64106456
// Source map tests
64116457
// =========================================================================
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
assertion_line: 6452
4+
expression: result.code
5+
---
6+
7+
import { Injectable } from '@angular/core';
8+
import { __decorate } from "tslib";
9+
10+
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+
}
17+
};
18+
BaseProvider = __decorate([
19+
Injectable()
20+
], BaseProvider);
21+
export { BaseProvider };

0 commit comments

Comments
 (0)