@@ -93,6 +93,7 @@ public class ExportSwift {
9393 var name : String ?
9494 var cases : [ EnumCase ] = [ ]
9595 var rawType : String ?
96+ var staticMethods : [ ExportedFunction ] = [ ]
9697 }
9798 var currentEnum = CurrentEnum ( )
9899
@@ -152,28 +153,54 @@ public class ExportSwift {
152153 }
153154
154155 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+
155165 switch state {
156166 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 ) {
160172 exportedFunctions. append ( exportedFunction)
161173 }
162174 return . skipChildren
163- case . classBody( _ , let classKey) :
175+ case . classBody( let className , let classKey) :
164176 if let exportedFunction = visitFunction (
165- node: node
177+ node: node,
178+ isStatic: isStatic,
179+ className: className,
180+ classKey: classKey
166181 ) {
167182 exportedClassByName [ classKey] ? . methods. append ( exportedFunction)
168183 }
169184 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+ }
172193 return . skipChildren
173194 }
174195 }
175196
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 ? {
177204 guard let jsAttribute = node. attributes. firstJSAttribute else {
178205 return nil
179206 }
@@ -189,6 +216,14 @@ public class ExportSwift {
189216 )
190217 }
191218
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+
192227 var parameters : [ Parameter ] = [ ]
193228 for param in node. signature. parameterClause. parameters {
194229 let resolvedType = self . parent. lookupType ( for: param. type)
@@ -226,20 +261,52 @@ public class ExportSwift {
226261 }
227262
228263 let abiName : String
264+ let staticContext : StaticContext ?
265+
229266 switch state {
230267 case . topLevel:
231268 abiName = " bjs_ \( name) "
269+ staticContext = nil
232270 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+ }
240307 }
241308
242- guard let effects = collectEffects ( signature: node. signature) else {
309+ guard let effects = collectEffects ( signature: node. signature, isStatic : isStatic ) else {
243310 return nil
244311 }
245312
@@ -249,11 +316,12 @@ public class ExportSwift {
249316 parameters: parameters,
250317 returnType: returnType,
251318 effects: effects,
252- namespace: namespace
319+ namespace: namespace,
320+ staticContext: staticContext
253321 )
254322 }
255323
256- private func collectEffects( signature: FunctionSignatureSyntax ) -> Effects ? {
324+ private func collectEffects( signature: FunctionSignatureSyntax , isStatic : Bool = false ) -> Effects ? {
257325 let isAsync = signature. effectSpecifiers? . asyncSpecifier != nil
258326 var isThrows = false
259327 if let throwsClause: ThrowsClauseSyntax = signature. effectSpecifiers? . throwsClause {
@@ -274,7 +342,7 @@ public class ExportSwift {
274342 }
275343 isThrows = true
276344 }
277- return Effects ( isAsync: isAsync, isThrows: isThrows)
345+ return Effects ( isAsync: isAsync, isThrows: isThrows, isStatic : isStatic )
278346 }
279347
280348 private func extractNamespace(
@@ -537,15 +605,25 @@ public class ExportSwift {
537605 }
538606
539607 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+ }
549627 }
550628
551629 if currentEnum. cases. contains ( where: { !$0. associatedValues. isEmpty } ) {
@@ -597,7 +675,8 @@ public class ExportSwift {
597675 cases: currentEnum. cases,
598676 rawType: currentEnum. rawType,
599677 namespace: effectiveNamespace,
600- emitStyle: emitStyle
678+ emitStyle: emitStyle,
679+ staticMethods: currentEnum. staticMethods
601680 )
602681 exportedEnumByName [ enumName] = exportedEnum
603682 exportedEnumNames. append ( enumName)
@@ -862,6 +941,10 @@ public class ExportSwift {
862941 case . namespace:
863942 ( )
864943 }
944+
945+ for staticMethod in enumDef. staticMethods {
946+ decls. append ( try renderSingleExportedFunction ( function: staticMethod) )
947+ }
865948 }
866949
867950 for function in exportedFunctions {
@@ -1269,7 +1352,24 @@ public class ExportSwift {
12691352 for param in function. parameters {
12701353 try builder. liftParameter ( param: param)
12711354 }
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+
12731373 try builder. lowerReturnValue ( returnType: function. returnType)
12741374 return builder. render ( abiName: function. abiName)
12751375 }
@@ -1335,17 +1435,25 @@ public class ExportSwift {
13351435 }
13361436 for method in klass. methods {
13371437 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+ )
13431456 }
1344- builder. callMethod (
1345- klassName: klass. swiftCallName,
1346- methodName: method. name,
1347- returnType: method. returnType
1348- )
13491457 try builder. lowerReturnValue ( returnType: method. returnType)
13501458 decls. append ( builder. render ( abiName: method. abiName) )
13511459 }
0 commit comments