11import * as ts from "typescript" ;
22import * as lua from "../../../LuaAST" ;
33import { TransformationContext } from "../../context" ;
4- import { decoratorInvalidContext } from "../../utils/diagnostics" ;
4+ import { decoratorInvalidContext , incompleteFieldDecoratorWarning } from "../../utils/diagnostics" ;
55import { ContextType , getFunctionContextType } from "../../utils/function-context" ;
66import { LuaLibFeature , transformLuaLibFunction } from "../../utils/lualib" ;
7+ import { isNonNull } from "../../../utils" ;
8+ import { transformMemberExpressionOwnerName , transformMethodName } from "./members/method" ;
9+ import { transformPropertyName } from "../literal" ;
10+ import { isPrivateNode , isStaticNode } from "./utils" ;
711
812export function transformDecoratorExpression ( context : TransformationContext , decorator : ts . Decorator ) : lua . Expression {
913 const expression = decorator . expression ;
@@ -16,7 +20,161 @@ export function transformDecoratorExpression(context: TransformationContext, dec
1620 return context . transformExpression ( expression ) ;
1721}
1822
19- export function createDecoratingExpression (
23+ export function createClassDecoratingExpression (
24+ context : TransformationContext ,
25+ classDeclaration : ts . ClassDeclaration | ts . ClassExpression ,
26+ className : lua . Expression
27+ ) : lua . Expression {
28+ const classDecorators =
29+ ts . getDecorators ( classDeclaration ) ?. map ( d => transformDecoratorExpression ( context , d ) ) ?? [ ] ;
30+
31+ // If experimentalDecorators flag is set, decorate with legacy decorator logic
32+ if ( context . options . experimentalDecorators ) {
33+ return createLegacyDecoratingExpression ( context , classDeclaration . kind , classDecorators , className ) ;
34+ }
35+
36+ // Else: TypeScript 5.0 decorator
37+ return createDecoratingExpression ( context , className , className , classDecorators , {
38+ kind : lua . createStringLiteral ( "class" ) ,
39+ name : lua . createStringLiteral ( classDeclaration . name ?. getText ( ) ?? "" ) ,
40+ } ) ;
41+ }
42+
43+ export function createClassMethodDecoratingExpression (
44+ context : TransformationContext ,
45+ methodDeclaration : ts . MethodDeclaration ,
46+ originalMethod : lua . Expression ,
47+ className : lua . Identifier
48+ ) : lua . Expression {
49+ const parameterDecorators = getParameterDecorators ( context , methodDeclaration ) ;
50+ const methodDecorators =
51+ ts . getDecorators ( methodDeclaration ) ?. map ( d => transformDecoratorExpression ( context , d ) ) ?? [ ] ;
52+
53+ const methodName = transformMethodName ( context , methodDeclaration ) ;
54+
55+ // If experimentalDecorators flag is set, decorate with legacy decorator logic
56+ if ( context . options . experimentalDecorators ) {
57+ const methodTable = transformMemberExpressionOwnerName ( methodDeclaration , className ) ;
58+ return createLegacyDecoratingExpression (
59+ context ,
60+ methodDeclaration . kind ,
61+ [ ...methodDecorators , ...parameterDecorators ] ,
62+ methodTable ,
63+ methodName
64+ ) ;
65+ }
66+
67+ // Else: TypeScript 5.0 decorator
68+ return createDecoratingExpression ( context , className , originalMethod , methodDecorators , {
69+ kind : lua . createStringLiteral ( "method" ) ,
70+ name : methodName ,
71+ private : lua . createBooleanLiteral ( isPrivateNode ( methodDeclaration ) ) ,
72+ static : lua . createBooleanLiteral ( isStaticNode ( methodDeclaration ) ) ,
73+ } ) ;
74+ }
75+
76+ export function createClassAccessorDecoratingExpression (
77+ context : TransformationContext ,
78+ accessor : ts . AccessorDeclaration ,
79+ originalAccessor : lua . Expression ,
80+ className : lua . Identifier
81+ ) : lua . Expression {
82+ const accessorDecorators = ts . getDecorators ( accessor ) ?. map ( d => transformDecoratorExpression ( context , d ) ) ?? [ ] ;
83+ const propertyName = transformPropertyName ( context , accessor . name ) ;
84+
85+ // If experimentalDecorators flag is set, decorate with legacy decorator logic
86+ if ( context . options . experimentalDecorators ) {
87+ const propertyOwnerTable = transformMemberExpressionOwnerName ( accessor , className ) ;
88+
89+ return createLegacyDecoratingExpression (
90+ context ,
91+ accessor . kind ,
92+ accessorDecorators ,
93+ propertyOwnerTable ,
94+ propertyName
95+ ) ;
96+ }
97+
98+ // Else: TypeScript 5.0 decorator
99+ return createDecoratingExpression ( context , className , originalAccessor , accessorDecorators , {
100+ kind : lua . createStringLiteral ( accessor . kind === ts . SyntaxKind . SetAccessor ? "setter" : "getter" ) ,
101+ name : propertyName ,
102+ private : lua . createBooleanLiteral ( isPrivateNode ( accessor ) ) ,
103+ static : lua . createBooleanLiteral ( isStaticNode ( accessor ) ) ,
104+ } ) ;
105+ }
106+
107+ export function createClassPropertyDecoratingExpression (
108+ context : TransformationContext ,
109+ property : ts . PropertyDeclaration ,
110+ className : lua . Identifier
111+ ) : lua . Expression {
112+ const decorators = ts . getDecorators ( property ) ?? [ ] ;
113+ const propertyDecorators = decorators . map ( d => transformDecoratorExpression ( context , d ) ) ;
114+
115+ // If experimentalDecorators flag is set, decorate with legacy decorator logic
116+ if ( context . options . experimentalDecorators ) {
117+ const propertyName = transformPropertyName ( context , property . name ) ;
118+ const propertyOwnerTable = transformMemberExpressionOwnerName ( property , className ) ;
119+
120+ return createLegacyDecoratingExpression (
121+ context ,
122+ property . kind ,
123+ propertyDecorators ,
124+ propertyOwnerTable ,
125+ propertyName
126+ ) ;
127+ }
128+
129+ // Else: TypeScript 5.0 decorator
130+
131+ // Add a diagnostic when something is returned from a field decorator
132+ for ( const decorator of decorators ) {
133+ const signature = context . checker . getResolvedSignature ( decorator ) ;
134+ const decoratorReturnType = signature ?. getReturnType ( ) ;
135+ // If return type of decorator is NOT void
136+ if ( decoratorReturnType && ( decoratorReturnType . flags & ts . TypeFlags . Void ) === 0 ) {
137+ context . diagnostics . push ( incompleteFieldDecoratorWarning ( property ) ) ;
138+ }
139+ }
140+
141+ return createDecoratingExpression ( context , className , lua . createNilLiteral ( ) , propertyDecorators , {
142+ kind : lua . createStringLiteral ( "field" ) ,
143+ name : lua . createStringLiteral ( property . name . getText ( ) ) ,
144+ private : lua . createBooleanLiteral ( isPrivateNode ( property ) ) ,
145+ static : lua . createBooleanLiteral ( isStaticNode ( property ) ) ,
146+ } ) ;
147+ }
148+
149+ function createDecoratingExpression < TValue extends lua . Expression > (
150+ context : TransformationContext ,
151+ className : lua . Expression ,
152+ originalValue : TValue ,
153+ decorators : lua . Expression [ ] ,
154+ decoratorContext : Record < string , lua . Expression >
155+ ) : lua . Expression {
156+ const decoratorTable = lua . createTableExpression ( decorators . map ( d => lua . createTableFieldExpression ( d ) ) ) ;
157+ const decoratorContextTable = objectToLuaTableLiteral ( decoratorContext ) ;
158+
159+ return transformLuaLibFunction (
160+ context ,
161+ LuaLibFeature . Decorate ,
162+ undefined ,
163+ className ,
164+ originalValue ,
165+ decoratorTable ,
166+ decoratorContextTable
167+ ) ;
168+ }
169+
170+ function objectToLuaTableLiteral ( obj : Record < string , lua . Expression > ) : lua . Expression {
171+ return lua . createTableExpression (
172+ Object . entries ( obj ) . map ( ( [ key , value ] ) => lua . createTableFieldExpression ( value , lua . createStringLiteral ( key ) ) )
173+ ) ;
174+ }
175+
176+ // Legacy decorators:
177+ function createLegacyDecoratingExpression (
20178 context : TransformationContext ,
21179 kind : ts . SyntaxKind ,
22180 decorators : lua . Expression [ ] ,
@@ -35,5 +193,39 @@ export function createDecoratingExpression(
35193 trailingExpressions . push ( isMethodOrAccessor ? lua . createBooleanLiteral ( true ) : lua . createNilLiteral ( ) ) ;
36194 }
37195
38- return transformLuaLibFunction ( context , LuaLibFeature . Decorate , undefined , ...trailingExpressions ) ;
196+ return transformLuaLibFunction ( context , LuaLibFeature . DecorateLegacy , undefined , ...trailingExpressions ) ;
197+ }
198+
199+ function getParameterDecorators (
200+ context : TransformationContext ,
201+ node : ts . FunctionLikeDeclarationBase
202+ ) : lua . CallExpression [ ] {
203+ return node . parameters
204+ . flatMap ( ( parameter , index ) =>
205+ ts
206+ . getDecorators ( parameter )
207+ ?. map ( decorator =>
208+ transformLuaLibFunction (
209+ context ,
210+ LuaLibFeature . DecorateParam ,
211+ node ,
212+ lua . createNumericLiteral ( index ) ,
213+ transformDecoratorExpression ( context , decorator )
214+ )
215+ )
216+ )
217+ . filter ( isNonNull ) ;
218+ }
219+
220+ export function createConstructorDecoratingExpression (
221+ context : TransformationContext ,
222+ node : ts . ConstructorDeclaration ,
223+ className : lua . Identifier
224+ ) : lua . Statement | undefined {
225+ const parameterDecorators = getParameterDecorators ( context , node ) ;
226+
227+ if ( parameterDecorators . length > 0 ) {
228+ const decorateMethod = createLegacyDecoratingExpression ( context , node . kind , parameterDecorators , className ) ;
229+ return lua . createExpressionStatement ( decorateMethod ) ;
230+ }
39231}
0 commit comments