@@ -93,6 +93,7 @@ public class ExportSwift {
93
93
var name : String ?
94
94
var cases : [ EnumCase ] = [ ]
95
95
var rawType : String ?
96
+ var staticMethods : [ ExportedFunction ] = [ ]
96
97
}
97
98
var currentEnum = CurrentEnum ( )
98
99
@@ -152,28 +153,54 @@ public class ExportSwift {
152
153
}
153
154
154
155
override func visit( _ node: FunctionDeclSyntax ) -> SyntaxVisitorContinueKind {
156
+ guard node. attributes. hasJSAttribute ( ) else {
157
+ return . skipChildren
158
+ }
159
+
160
+ let isStatic = node. modifiers. contains { modifier in
161
+ modifier. name. tokenKind == . keyword( . static) ||
162
+ modifier. name. tokenKind == . keyword( . class)
163
+ }
164
+
155
165
switch state {
156
166
case . topLevel:
157
- if let exportedFunction = visitFunction (
158
- node: node
159
- ) {
167
+ if isStatic {
168
+ diagnose ( node: node, message: " Top-level functions cannot be static " )
169
+ return . skipChildren
170
+ }
171
+ if let exportedFunction = visitFunction ( node: node, isStatic: false ) {
160
172
exportedFunctions. append ( exportedFunction)
161
173
}
162
174
return . skipChildren
163
- case . classBody( _ , let classKey) :
175
+ case . classBody( let className , let classKey) :
164
176
if let exportedFunction = visitFunction (
165
- node: node
177
+ node: node,
178
+ isStatic: isStatic,
179
+ className: className,
180
+ classKey: classKey
166
181
) {
167
182
exportedClassByName [ classKey] ? . methods. append ( exportedFunction)
168
183
}
169
184
return . skipChildren
170
- case . enumBody:
171
- diagnose ( node: node, message: " Functions are not supported inside enums " )
185
+ case . enumBody( let enumName) :
186
+ if !isStatic {
187
+ diagnose ( node: node, message: " Only static functions are supported in enums " )
188
+ return . skipChildren
189
+ }
190
+ if let exportedFunction = visitFunction ( node: node, isStatic: isStatic, enumName: enumName) {
191
+ currentEnum. staticMethods. append ( exportedFunction)
192
+ }
172
193
return . skipChildren
173
194
}
174
195
}
175
196
176
- private func visitFunction( node: FunctionDeclSyntax ) -> ExportedFunction ? {
197
+ private func visitFunction(
198
+ node: FunctionDeclSyntax ,
199
+ isStatic: Bool ,
200
+ className: String ? = nil ,
201
+ classKey: String ? = nil ,
202
+ enumName: String ? = nil
203
+ ) -> ExportedFunction ? {
177
204
guard let jsAttribute = node. attributes. firstJSAttribute else {
178
205
return nil
179
206
}
@@ -189,6 +216,14 @@ public class ExportSwift {
189
216
)
190
217
}
191
218
219
+ if namespace != nil , case . enumBody = state {
220
+ diagnose (
221
+ node: jsAttribute,
222
+ message: " Namespace is not supported for enum static functions " ,
223
+ hint: " Remove the namespace from @JS attribute - enum functions inherit namespace from enum "
224
+ )
225
+ }
226
+
192
227
var parameters : [ Parameter ] = [ ]
193
228
for param in node. signature. parameterClause. parameters {
194
229
let resolvedType = self . parent. lookupType ( for: param. type)
@@ -226,20 +261,52 @@ public class ExportSwift {
226
261
}
227
262
228
263
let abiName : String
264
+ let staticContext : StaticContext ?
265
+
229
266
switch state {
230
267
case . topLevel:
231
268
abiName = " bjs_ \( name) "
269
+ staticContext = nil
232
270
case . classBody( let className, _) :
233
- abiName = " bjs_ \( className) _ \( name) "
234
- case . enumBody:
235
- abiName = " "
236
- diagnose (
237
- node: node,
238
- message: " Functions are not supported inside enums "
239
- )
271
+ if isStatic {
272
+ abiName = " bjs_ \( className) _static_ \( name) "
273
+ staticContext = . className( className)
274
+ } else {
275
+ abiName = " bjs_ \( className) _ \( name) "
276
+ staticContext = nil
277
+ }
278
+ case . enumBody( let enumName) :
279
+ if !isStatic {
280
+ diagnose ( node: node, message: " Only static functions are supported in enums " )
281
+ return nil
282
+ }
283
+
284
+ let isNamespaceEnum = currentEnum. cases. isEmpty
285
+
286
+ if isNamespaceEnum {
287
+ // For namespace enums, compute the full Swift call path manually
288
+ var swiftPath : [ String ] = [ ]
289
+ var currentNode : Syntax ? = node. parent
290
+ while let parent = currentNode {
291
+ if let enumDecl = parent. as ( EnumDeclSyntax . self) ,
292
+ enumDecl. attributes. hasJSAttribute ( )
293
+ {
294
+ swiftPath. insert ( enumDecl. name. text, at: 0 )
295
+ }
296
+ currentNode = parent. parent
297
+ }
298
+ let fullEnumCallName = swiftPath. joined ( separator: " . " )
299
+
300
+ // ABI name should include full namespace path to avoid conflicts
301
+ abiName = " bjs_ \( swiftPath. joined ( separator: " _ " ) ) _ \( name) "
302
+ staticContext = . namespaceEnum( fullEnumCallName)
303
+ } else {
304
+ abiName = " bjs_ \( enumName) _static_ \( name) "
305
+ staticContext = . enumName( enumName)
306
+ }
240
307
}
241
308
242
- guard let effects = collectEffects ( signature: node. signature) else {
309
+ guard let effects = collectEffects ( signature: node. signature, isStatic : isStatic ) else {
243
310
return nil
244
311
}
245
312
@@ -249,11 +316,12 @@ public class ExportSwift {
249
316
parameters: parameters,
250
317
returnType: returnType,
251
318
effects: effects,
252
- namespace: namespace
319
+ namespace: namespace,
320
+ staticContext: staticContext
253
321
)
254
322
}
255
323
256
- private func collectEffects( signature: FunctionSignatureSyntax ) -> Effects ? {
324
+ private func collectEffects( signature: FunctionSignatureSyntax , isStatic : Bool = false ) -> Effects ? {
257
325
let isAsync = signature. effectSpecifiers? . asyncSpecifier != nil
258
326
var isThrows = false
259
327
if let throwsClause: ThrowsClauseSyntax = signature. effectSpecifiers? . throwsClause {
@@ -274,7 +342,7 @@ public class ExportSwift {
274
342
}
275
343
isThrows = true
276
344
}
277
- return Effects ( isAsync: isAsync, isThrows: isThrows)
345
+ return Effects ( isAsync: isAsync, isThrows: isThrows, isStatic : isStatic )
278
346
}
279
347
280
348
private func extractNamespace(
@@ -537,15 +605,25 @@ public class ExportSwift {
537
605
}
538
606
539
607
let emitStyle = extractEnumStyle ( from: jsAttribute) ?? . const
540
- if case . tsEnum = emitStyle,
541
- let raw = currentEnum. rawType,
542
- let rawEnum = SwiftEnumRawType . from ( raw) , rawEnum == . bool
543
- {
544
- diagnose (
545
- node: jsAttribute,
546
- message: " TypeScript enum style is not supported for Bool raw-value enums " ,
547
- hint: " Use enumStyle: .const or change the raw type to String or a numeric type "
548
- )
608
+
609
+ if case . tsEnum = emitStyle {
610
+ if let raw = currentEnum. rawType,
611
+ let rawEnum = SwiftEnumRawType . from ( raw) , rawEnum == . bool
612
+ {
613
+ diagnose (
614
+ node: jsAttribute,
615
+ message: " TypeScript enum style is not supported for Bool raw-value enums " ,
616
+ hint: " Use enumStyle: .const or change the raw type to String or a numeric type "
617
+ )
618
+ }
619
+
620
+ if !currentEnum. staticMethods. isEmpty {
621
+ diagnose (
622
+ node: jsAttribute,
623
+ message: " TypeScript enum style does not support static functions " ,
624
+ hint: " Use enumStyle: .const to generate a const object that supports static functions "
625
+ )
626
+ }
549
627
}
550
628
551
629
if currentEnum. cases. contains ( where: { !$0. associatedValues. isEmpty } ) {
@@ -597,7 +675,8 @@ public class ExportSwift {
597
675
cases: currentEnum. cases,
598
676
rawType: currentEnum. rawType,
599
677
namespace: effectiveNamespace,
600
- emitStyle: emitStyle
678
+ emitStyle: emitStyle,
679
+ staticMethods: currentEnum. staticMethods
601
680
)
602
681
exportedEnumByName [ enumName] = exportedEnum
603
682
exportedEnumNames. append ( enumName)
@@ -862,6 +941,10 @@ public class ExportSwift {
862
941
case . namespace:
863
942
( )
864
943
}
944
+
945
+ for staticMethod in enumDef. staticMethods {
946
+ decls. append ( try renderSingleExportedFunction ( function: staticMethod) )
947
+ }
865
948
}
866
949
867
950
for function in exportedFunctions {
@@ -1269,7 +1352,24 @@ public class ExportSwift {
1269
1352
for param in function. parameters {
1270
1353
try builder. liftParameter ( param: param)
1271
1354
}
1272
- builder. call ( name: function. name, returnType: function. returnType)
1355
+
1356
+ if function. effects. isStatic, let staticContext = function. staticContext {
1357
+ let callName : String
1358
+ switch staticContext {
1359
+ case . className( let className) :
1360
+ callName = " \( className) . \( function. name) "
1361
+ case . enumName( let enumName) :
1362
+ callName = " \( enumName) . \( function. name) "
1363
+ case . namespaceEnum( let enumName) :
1364
+ callName = " \( enumName) . \( function. name) "
1365
+ case . explicitNamespace( let namespace) :
1366
+ callName = " \( namespace. joined ( separator: " . " ) ) . \( function. name) "
1367
+ }
1368
+ builder. call ( name: callName, returnType: function. returnType)
1369
+ } else {
1370
+ builder. call ( name: function. name, returnType: function. returnType)
1371
+ }
1372
+
1273
1373
try builder. lowerReturnValue ( returnType: function. returnType)
1274
1374
return builder. render ( abiName: function. abiName)
1275
1375
}
@@ -1335,17 +1435,25 @@ public class ExportSwift {
1335
1435
}
1336
1436
for method in klass. methods {
1337
1437
let builder = ExportedThunkBuilder ( effects: method. effects)
1338
- try builder. liftParameter (
1339
- param: Parameter ( label: nil , name: " _self " , type: BridgeType . swiftHeapObject ( klass. swiftCallName) )
1340
- )
1341
- for param in method. parameters {
1342
- try builder. liftParameter ( param: param)
1438
+
1439
+ if method. effects. isStatic {
1440
+ for param in method. parameters {
1441
+ try builder. liftParameter ( param: param)
1442
+ }
1443
+ builder. call ( name: " \( klass. swiftCallName) . \( method. name) " , returnType: method. returnType)
1444
+ } else {
1445
+ try builder. liftParameter (
1446
+ param: Parameter ( label: nil , name: " _self " , type: BridgeType . swiftHeapObject ( klass. swiftCallName) )
1447
+ )
1448
+ for param in method. parameters {
1449
+ try builder. liftParameter ( param: param)
1450
+ }
1451
+ builder. callMethod (
1452
+ klassName: klass. swiftCallName,
1453
+ methodName: method. name,
1454
+ returnType: method. returnType
1455
+ )
1343
1456
}
1344
- builder. callMethod (
1345
- klassName: klass. swiftCallName,
1346
- methodName: method. name,
1347
- returnType: method. returnType
1348
- )
1349
1457
try builder. lowerReturnValue ( returnType: method. returnType)
1350
1458
decls. append ( builder. render ( abiName: method. abiName) )
1351
1459
}
0 commit comments