@@ -685,8 +685,8 @@ enum AngularDecoratorKind {
685685struct JitClassInfo {
686686 /// The class name.
687687 class_name : String ,
688- /// Span of the decorator (including @).
689- decorator_span : Span ,
688+ /// Spans of ALL class-level decorators (including @) to be removed .
689+ all_class_decorator_spans : std :: vec :: Vec < Span > ,
690690 /// Start of the statement (includes export keyword if present).
691691 stmt_start : u32 ,
692692 /// Start of the class keyword.
@@ -701,10 +701,12 @@ struct JitClassInfo {
701701 is_abstract : bool ,
702702 /// Constructor parameter info for ctorParameters.
703703 ctor_params : std:: vec:: Vec < JitCtorParam > ,
704- /// Member decorator info for propDecorators.
704+ /// Member decorator info for propDecorators (Angular decorators like @Input, @Output) .
705705 member_decorators : std:: vec:: Vec < JitMemberDecorator > ,
706- /// The modified decorator expression text for __decorate call.
707- decorator_text : String ,
706+ /// All class-level decorator expression texts for __decorate call, in source order.
707+ all_class_decorator_texts : std:: vec:: Vec < String > ,
708+ /// Non-Angular member decorators that need __decorate() calls.
709+ non_angular_member_decorators : std:: vec:: Vec < JitNonAngularMemberDecorator > ,
708710}
709711
710712/// Constructor parameter info for JIT ctorParameters generation.
@@ -731,6 +733,19 @@ struct JitMemberDecorator {
731733 decorators : std:: vec:: Vec < JitParamDecorator > ,
732734}
733735
736+ /// A non-Angular member decorator that needs to be lowered via __decorate().
737+ struct JitNonAngularMemberDecorator {
738+ /// The member name.
739+ member_name : String ,
740+ /// Whether the member is static.
741+ is_static : bool ,
742+ /// Whether this is a property (field) vs a method/accessor.
743+ /// TypeScript uses `void 0` for properties and `null` for methods/accessors.
744+ is_property : bool ,
745+ /// The decorator expression texts (e.g., "Selector()", "Action(AddTodo)").
746+ decorator_texts : std:: vec:: Vec < String > ,
747+ }
748+
734749/// Find any Angular decorator on a class and return its kind and the decorator reference.
735750fn find_angular_decorator < ' a > (
736751 class : & ' a oxc_ast:: ast:: Class < ' a > ,
@@ -824,38 +839,71 @@ fn extract_jit_ctor_params(
824839 params
825840}
826841
827- /// Extract Angular member decorators for JIT propDecorators generation.
842+ /// Angular field decorators that go into `static propDecorators`.
843+ /// Matches Angular's official `FIELD_DECORATORS` constant from `@angular/compiler-cli`.
844+ const ANGULAR_FIELD_DECORATORS : & [ & str ] = & [
845+ "Input" ,
846+ "Output" ,
847+ "HostBinding" ,
848+ "HostListener" ,
849+ "ViewChild" ,
850+ "ViewChildren" ,
851+ "ContentChild" ,
852+ "ContentChildren" ,
853+ ] ;
854+
855+ /// All Angular decorator names from `@angular/core`.
856+ /// Any decorator with one of these names is treated as Angular and excluded from
857+ /// non-Angular `__decorate()` lowering. Angular identifies decorators by import source;
858+ /// we use names since they're unique to `@angular/core`.
859+ const ANGULAR_DECORATOR_NAMES : & [ & str ] = & [
860+ // Field decorators (→ propDecorators)
861+ "Input" ,
862+ "Output" ,
863+ "HostBinding" ,
864+ "HostListener" ,
865+ "ViewChild" ,
866+ "ViewChildren" ,
867+ "ContentChild" ,
868+ "ContentChildren" ,
869+ // Parameter decorators (→ ctorParameters)
870+ "Inject" ,
871+ "Optional" ,
872+ "Self" ,
873+ "SkipSelf" ,
874+ "Host" ,
875+ "Attribute" ,
876+ // Class decorators (→ class __decorate)
877+ "Component" ,
878+ "Directive" ,
879+ "Pipe" ,
880+ "Injectable" ,
881+ "NgModule" ,
882+ ] ;
883+
884+ /// Extract all member decorators for JIT transformation in a single pass.
828885///
829- /// Collects all Angular-relevant decorators from class properties/methods
830- /// (excluding constructor) so they can be emitted as a `static propDecorators` property.
831- fn extract_jit_member_decorators (
886+ /// Returns two collections:
887+ /// - Angular field decorators → emitted as `static propDecorators = { ... }`
888+ /// - Non-Angular decorators → emitted as `__decorate([...], target, "name", desc)` calls
889+ fn extract_all_jit_member_decorators (
832890 source : & str ,
833891 class : & oxc_ast:: ast:: Class < ' _ > ,
834- ) -> std:: vec:: Vec < JitMemberDecorator > {
892+ ) -> ( std:: vec:: Vec < JitMemberDecorator > , std :: vec :: Vec < JitNonAngularMemberDecorator > ) {
835893 use oxc_ast:: ast:: { ClassElement , MethodDefinitionKind , PropertyKey } ;
836894
837- const ANGULAR_MEMBER_DECORATORS : & [ & str ] = & [
838- "Input" ,
839- "Output" ,
840- "HostBinding" ,
841- "HostListener" ,
842- "ViewChild" ,
843- "ViewChildren" ,
844- "ContentChild" ,
845- "ContentChildren" ,
846- ] ;
847-
848- let mut result: std:: vec:: Vec < JitMemberDecorator > = std:: vec:: Vec :: new ( ) ;
895+ let mut angular_members: std:: vec:: Vec < JitMemberDecorator > = std:: vec:: Vec :: new ( ) ;
896+ let mut non_angular_members: std:: vec:: Vec < JitNonAngularMemberDecorator > = std:: vec:: Vec :: new ( ) ;
849897
850898 for element in & class. body . body {
851- let ( member_name, decorators) = match element {
899+ let ( member_name, is_static , is_property , decorators) = match element {
852900 ClassElement :: PropertyDefinition ( prop) => {
853901 let name = match & prop. key {
854902 PropertyKey :: StaticIdentifier ( id) => id. name . to_string ( ) ,
855903 PropertyKey :: StringLiteral ( s) => s. value . to_string ( ) ,
856904 _ => continue ,
857905 } ;
858- ( name, & prop. decorators )
906+ ( name, prop . r#static , true , & prop. decorators )
859907 }
860908 ClassElement :: MethodDefinition ( method) => {
861909 if method. kind == MethodDefinitionKind :: Constructor {
@@ -866,20 +914,21 @@ fn extract_jit_member_decorators(
866914 PropertyKey :: StringLiteral ( s) => s. value . to_string ( ) ,
867915 _ => continue ,
868916 } ;
869- ( name, & method. decorators )
917+ ( name, method . r#static , false , & method. decorators )
870918 }
871919 ClassElement :: AccessorProperty ( accessor) => {
872920 let name = match & accessor. key {
873921 PropertyKey :: StaticIdentifier ( id) => id. name . to_string ( ) ,
874922 PropertyKey :: StringLiteral ( s) => s. value . to_string ( ) ,
875923 _ => continue ,
876924 } ;
877- ( name, & accessor. decorators )
925+ ( name, accessor . r#static , false , & accessor. decorators )
878926 }
879927 _ => continue ,
880928 } ;
881929
882930 let mut angular_decs: std:: vec:: Vec < JitParamDecorator > = std:: vec:: Vec :: new ( ) ;
931+ let mut non_angular_texts: std:: vec:: Vec < String > = std:: vec:: Vec :: new ( ) ;
883932
884933 for decorator in decorators {
885934 let ( dec_name, call_args) = match & decorator. expression {
@@ -902,17 +951,37 @@ fn extract_jit_member_decorators(
902951 _ => continue ,
903952 } ;
904953
905- if ANGULAR_MEMBER_DECORATORS . contains ( & dec_name. as_str ( ) ) {
954+ if ANGULAR_FIELD_DECORATORS . contains ( & dec_name. as_str ( ) ) {
955+ // Angular field decorator → goes into propDecorators
906956 angular_decs. push ( JitParamDecorator { name : dec_name, args : call_args } ) ;
957+ } else if !ANGULAR_DECORATOR_NAMES . contains ( & dec_name. as_str ( ) ) {
958+ // Non-Angular decorator → goes into __decorate() call
959+ let expr_start = decorator. expression . span ( ) . start ;
960+ let expr_end = decorator. expression . span ( ) . end ;
961+ non_angular_texts. push ( source[ expr_start as usize ..expr_end as usize ] . to_string ( ) ) ;
907962 }
963+ // Angular non-field decorators (e.g. @Inject on a member) are silently dropped
964+ // since they have no meaningful effect on members.
908965 }
909966
910967 if !angular_decs. is_empty ( ) {
911- result. push ( JitMemberDecorator { member_name, decorators : angular_decs } ) ;
968+ angular_members. push ( JitMemberDecorator {
969+ member_name : member_name. clone ( ) ,
970+ decorators : angular_decs,
971+ } ) ;
972+ }
973+
974+ if !non_angular_texts. is_empty ( ) {
975+ non_angular_members. push ( JitNonAngularMemberDecorator {
976+ member_name,
977+ is_static,
978+ is_property,
979+ decorator_texts : non_angular_texts,
980+ } ) ;
912981 }
913982 }
914983
915- result
984+ ( angular_members , non_angular_members )
916985}
917986
918987/// Build the propDecorators static property text for JIT member decorator metadata.
@@ -1232,28 +1301,46 @@ fn transform_angular_file_jit(
12321301 continue ;
12331302 } ;
12341303
1235- let Some ( ( decorator_kind, decorator ) ) = find_angular_decorator ( class) else {
1304+ let Some ( ( decorator_kind, angular_decorator ) ) = find_angular_decorator ( class) else {
12361305 continue ;
12371306 } ;
12381307
1239- // Build modified decorator text (replaces templateUrl/styleUrl with resource imports)
1240- let decorator_text = build_jit_decorator_text (
1241- source,
1242- decorator,
1243- decorator_kind,
1244- & mut resource_counter,
1245- & mut resource_imports,
1246- ) ;
1308+ // Collect ALL class-level decorator spans and texts (in source order)
1309+ let mut all_class_decorator_spans: std:: vec:: Vec < Span > = std:: vec:: Vec :: new ( ) ;
1310+ let mut all_class_decorator_texts: std:: vec:: Vec < String > = std:: vec:: Vec :: new ( ) ;
1311+
1312+ for dec in & class. decorators {
1313+ all_class_decorator_spans. push ( dec. span ) ;
1314+
1315+ // Check if this is the Angular decorator that needs special text transformation
1316+ if dec. span == angular_decorator. span {
1317+ let text = build_jit_decorator_text (
1318+ source,
1319+ dec,
1320+ decorator_kind,
1321+ & mut resource_counter,
1322+ & mut resource_imports,
1323+ ) ;
1324+ all_class_decorator_texts. push ( text) ;
1325+ } else {
1326+ // Non-Angular decorator: extract expression text from source (without @)
1327+ let expr_start = dec. expression . span ( ) . start ;
1328+ let expr_end = dec. expression . span ( ) . end ;
1329+ all_class_decorator_texts
1330+ . push ( source[ expr_start as usize ..expr_end as usize ] . to_string ( ) ) ;
1331+ }
1332+ }
12471333
12481334 // Extract constructor parameters for ctorParameters
12491335 let ctor_params = extract_jit_ctor_params ( source, class) ;
12501336
1251- // Extract member decorators for propDecorators
1252- let member_decorators = extract_jit_member_decorators ( source, class) ;
1337+ // Extract Angular and non-Angular member decorators
1338+ let ( member_decorators, non_angular_member_decorators) =
1339+ extract_all_jit_member_decorators ( source, class) ;
12531340
12541341 jit_classes. push ( JitClassInfo {
12551342 class_name,
1256- decorator_span : decorator . span ,
1343+ all_class_decorator_spans ,
12571344 stmt_start,
12581345 class_start : class. span . start ,
12591346 class_body_end : class. body . span . end ,
@@ -1262,7 +1349,8 @@ fn transform_angular_file_jit(
12621349 is_abstract : class. r#abstract ,
12631350 ctor_params,
12641351 member_decorators,
1265- decorator_text,
1352+ all_class_decorator_texts,
1353+ non_angular_member_decorators,
12661354 } ) ;
12671355
12681356 result. component_count +=
@@ -1343,9 +1431,9 @@ fn transform_angular_file_jit(
13431431 continue ;
13441432 } ;
13451433
1346- // 4a. Remove the Angular decorator (including @ and trailing whitespace)
1347- {
1348- let mut end = jit_info . decorator_span . end as usize ;
1434+ // 4a. Remove ALL class-level decorators (including @ and trailing whitespace)
1435+ for decorator_span in & jit_info . all_class_decorator_spans {
1436+ let mut end = decorator_span. end as usize ;
13491437 let bytes = source. as_bytes ( ) ;
13501438 while end < bytes. len ( ) {
13511439 let c = bytes[ end] ;
@@ -1355,14 +1443,14 @@ fn transform_angular_file_jit(
13551443 break ;
13561444 }
13571445 }
1358- edits. push ( Edit :: delete ( jit_info . decorator_span . start , end as u32 ) ) ;
1446+ edits. push ( Edit :: delete ( decorator_span. start , end as u32 ) ) ;
13591447 }
13601448
1361- // 4b. Remove member decorators (@Input, @Output, etc.) and constructor param decorators
1449+ // 4b. Remove ALL member decorators and constructor param decorators
13621450 {
13631451 let mut decorator_spans: std:: vec:: Vec < Span > = std:: vec:: Vec :: new ( ) ;
13641452 super :: decorator:: collect_constructor_decorator_spans ( class, & mut decorator_spans) ;
1365- super :: decorator:: collect_member_decorator_spans ( class, & mut decorator_spans) ;
1453+ super :: decorator:: collect_all_member_decorator_spans ( class, & mut decorator_spans) ;
13661454 for span in & decorator_spans {
13671455 let mut end = span. end as usize ;
13681456 let bytes = source. as_bytes ( ) ;
@@ -1417,11 +1505,41 @@ fn transform_angular_file_jit(
14171505 }
14181506 }
14191507
1420- // 4e. After class body, add __decorate call and export
1421- let mut after_class = format ! (
1422- ";\n {} = __decorate([\n {}\n ], {});\n " ,
1423- jit_info. class_name, jit_info. decorator_text, jit_info. class_name
1424- ) ;
1508+ // 4e. After class body, add member __decorate calls, then class __decorate call, then export
1509+ let mut after_class = String :: from ( ";\n " ) ;
1510+
1511+ // Emit __decorate() for non-Angular member decorators (before class __decorate).
1512+ // Match TypeScript's ordering: instance (prototype) members first, then static members.
1513+ // Within each group, preserve source declaration order.
1514+ for member_dec in jit_info
1515+ . non_angular_member_decorators
1516+ . iter ( )
1517+ . filter ( |m| !m. is_static )
1518+ . chain ( jit_info. non_angular_member_decorators . iter ( ) . filter ( |m| m. is_static ) )
1519+ {
1520+ let target = if member_dec. is_static {
1521+ jit_info. class_name . clone ( )
1522+ } else {
1523+ format ! ( "{}.prototype" , jit_info. class_name)
1524+ } ;
1525+ // TypeScript uses `null` for methods/accessors (reads existing descriptor)
1526+ // and `void 0` for properties (no existing descriptor).
1527+ let desc = if member_dec. is_property { "void 0" } else { "null" } ;
1528+ after_class. push_str ( & format ! (
1529+ "__decorate([{}], {}, \" {}\" , {});\n " ,
1530+ member_dec. decorator_texts. join( ", " ) ,
1531+ target,
1532+ member_dec. member_name,
1533+ desc
1534+ ) ) ;
1535+ }
1536+
1537+ // Emit class-level __decorate() with ALL class decorators
1538+ let all_decorator_text = jit_info. all_class_decorator_texts . join ( ",\n " ) ;
1539+ after_class. push_str ( & format ! (
1540+ "{} = __decorate([\n {}\n ], {});\n " ,
1541+ jit_info. class_name, all_decorator_text, jit_info. class_name
1542+ ) ) ;
14251543
14261544 if jit_info. is_exported {
14271545 after_class. push_str ( & format ! ( "export {{ {} }};\n " , jit_info. class_name) ) ;
0 commit comments