Skip to content

Commit 8216cd7

Browse files
ashh640claude
authored andcommitted
fix(jit): exclude all Angular decorators from non-Angular __decorate lowering
Angular parameter decorators (@Inject, @optional, @self, @SkipSelf, @host, @Attribute) and class decorators (@component, @directive, @pipe, @Injectable, @NgModule) should never be emitted in member __decorate() calls. Previously only the 8 field decorators were excluded; now all 19 Angular decorators are excluded from the non-Angular member decorator extraction. This matches Angular's official behavior where decorators are identified by their @angular/core import source — any Angular decorator on a member should be handled via propDecorators/ctorParameters, not __decorate. https://claude.ai/code/session_01BbwLMsG3SjXcCbvDxAyW2Z
1 parent d499460 commit 8216cd7

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,13 @@ fn extract_non_angular_member_decorators(
931931
) -> std::vec::Vec<JitNonAngularMemberDecorator> {
932932
use oxc_ast::ast::{ClassElement, MethodDefinitionKind, PropertyKey};
933933

934-
const ANGULAR_MEMBER_DECORATORS: &[&str] = &[
934+
// All Angular decorators that should NOT be lowered via __decorate().
935+
// This includes field decorators (handled via propDecorators), parameter decorators
936+
// (handled via ctorParameters), and class decorators (handled via class __decorate).
937+
// Angular identifies these by import source (@angular/core); we use names since
938+
// they're unique enough and matches the official FIELD_DECORATORS list.
939+
const ANGULAR_DECORATORS: &[&str] = &[
940+
// Field decorators (go into propDecorators)
935941
"Input",
936942
"Output",
937943
"HostBinding",
@@ -940,6 +946,19 @@ fn extract_non_angular_member_decorators(
940946
"ViewChildren",
941947
"ContentChild",
942948
"ContentChildren",
949+
// Parameter decorators (go into ctorParameters, but could appear on members)
950+
"Inject",
951+
"Optional",
952+
"Self",
953+
"SkipSelf",
954+
"Host",
955+
"Attribute",
956+
// Class decorators (shouldn't appear on members, but exclude defensively)
957+
"Component",
958+
"Directive",
959+
"Pipe",
960+
"Injectable",
961+
"NgModule",
943962
];
944963

945964
let mut result: std::vec::Vec<JitNonAngularMemberDecorator> = std::vec::Vec::new();
@@ -991,7 +1010,7 @@ fn extract_non_angular_member_decorators(
9911010

9921011
let is_angular = dec_name
9931012
.as_ref()
994-
.is_some_and(|n| ANGULAR_MEMBER_DECORATORS.contains(&n.as_str()));
1013+
.is_some_and(|n| ANGULAR_DECORATORS.contains(&n.as_str()));
9951014

9961015
if !is_angular {
9971016
// Extract the decorator expression text from source (without the @)

crates/oxc_angular_compiler/tests/integration_test.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7039,6 +7039,58 @@ export class TestService {
70397039
insta::assert_snapshot!("jit_complex_decorator_arguments", result.code);
70407040
}
70417041

7042+
#[test]
7043+
fn test_jit_angular_param_decorators_not_in_member_decorate() {
7044+
// Angular parameter decorators (@Inject, @Optional, @Self, @SkipSelf, @Host, @Attribute)
7045+
// should NOT be emitted in __decorate() calls if they appear on a member.
7046+
// While these are designed for constructor params, if someone puts them on a member,
7047+
// they should be treated as Angular decorators (not lowered via __decorate).
7048+
let allocator = Allocator::default();
7049+
let source = r#"
7050+
import { Injectable, Inject, Optional } from '@angular/core';
7051+
7052+
function Custom() { return function(t: any, k: string) {}; }
7053+
7054+
@Injectable()
7055+
export class MyService {
7056+
@Inject('TOKEN')
7057+
token: any;
7058+
7059+
@Optional()
7060+
optionalDep: any;
7061+
7062+
@Custom()
7063+
customProp: string = '';
7064+
}
7065+
"#;
7066+
7067+
let options = ComponentTransformOptions { jit: true, ..Default::default() };
7068+
let result = transform_angular_file(&allocator, "my.service.ts", source, &options, None);
7069+
assert!(!result.has_errors(), "Should not have errors: {:?}", result.diagnostics);
7070+
7071+
// @Custom should be lowered via __decorate (it's non-Angular)
7072+
assert!(
7073+
result.code.contains("__decorate([Custom()], MyService.prototype, \"customProp\", void 0)"),
7074+
"Non-Angular decorator should be in __decorate. Got:\n{}",
7075+
result.code
7076+
);
7077+
7078+
// @Inject and @Optional should NOT appear in __decorate calls for members
7079+
// They are Angular decorators and should not be treated as non-Angular
7080+
let member_decorate_calls: Vec<&str> = result.code.lines()
7081+
.filter(|l| l.contains("__decorate(") && l.contains(".prototype"))
7082+
.collect();
7083+
for call in &member_decorate_calls {
7084+
assert!(
7085+
!call.contains("Inject(") && !call.contains("Optional()"),
7086+
"Angular param decorators should not appear in member __decorate calls. Got:\n{}",
7087+
call
7088+
);
7089+
}
7090+
7091+
insta::assert_snapshot!("jit_angular_param_decorators_on_members", result.code);
7092+
}
7093+
70427094
// =========================================================================
70437095
// Reference output comparison tests
70447096
// =========================================================================
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
6+
import { Injectable, Inject, Optional } from '@angular/core';
7+
import { __decorate } from "tslib";
8+
9+
function Custom() { return function(t: any, k: string) {}; }
10+
11+
let MyService = class MyService {
12+
token: any;
13+
14+
optionalDep: any;
15+
16+
customProp: string = '';
17+
};
18+
__decorate([Custom()], MyService.prototype, "customProp", void 0);
19+
MyService = __decorate([
20+
Injectable()
21+
], MyService);
22+
export { MyService };

0 commit comments

Comments
 (0)