From 6990143e74bbd28f7e7bc8c99f22efd40e504c2c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 01:22:49 +0900 Subject: [PATCH 1/6] BridgeJS: Support `@JSFunction static func` --- .../Generated/JavaScript/BridgeJS.json | 3 + .../Sources/BridgeJSCore/ImportTS.swift | 22 ++++ .../BridgeJSCore/SwiftToSkeleton.swift | 51 ++++++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 42 ++++++- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 11 +- .../TS2Swift/JavaScript/src/processor.js | 6 +- .../MacroSwift/JSClassStaticFunctions.swift | 13 +++ .../BridgeJSCodegenTests/GlobalGetter.json | 3 + .../GlobalThisImports.json | 6 + .../ImportedTypeInExportedInterface.json | 3 + .../InvalidPropertyNames.json | 6 + .../BridgeJSCodegenTests/JSClass.json | 6 + .../JSClassStaticFunctions.json | 108 ++++++++++++++++++ .../JSClassStaticFunctions.swift | 86 ++++++++++++++ .../Generated/BridgeJS.Macros.swift | 9 ++ .../Generated/BridgeJS.swift | 105 +++++++++++++++++ .../Generated/JavaScript/BridgeJS.json | 100 ++++++++++++++++ .../BridgeJSRuntimeTests/ImportAPITests.swift | 13 +++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 9 ++ Tests/prelude.mjs | 22 ++++ 20 files changed, 615 insertions(+), 9 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json index 1cf8e64c6..2952e2157 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json @@ -191,6 +191,9 @@ "name" : "TS2Swift", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 7c391eea0..234bdc66c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -398,6 +398,24 @@ public struct ImportTS { ] } + func renderStaticMethod(method: ImportedFunctionSkeleton) throws -> [DeclSyntax] { + let abiName = method.abiName(context: type, operation: "static") + let builder = CallJSEmission(moduleName: moduleName, abiName: abiName) + for param in method.parameters { + try builder.lowerParameter(param: param) + } + try builder.call(returnType: method.returnType) + try builder.liftReturnValue(returnType: method.returnType) + topLevelDecls.append(builder.renderImportDecl()) + return [ + builder.renderThunkDecl( + name: Self.thunkName(type: type, method: method), + parameters: method.parameters, + returnType: method.returnType + ) + ] + } + func renderConstructorDecl(constructor: ImportedConstructorSkeleton) throws -> [DeclSyntax] { let builder = CallJSEmission(moduleName: moduleName, abiName: constructor.abiName(context: type)) for param in constructor.parameters { @@ -462,6 +480,10 @@ public struct ImportTS { decls.append(contentsOf: try renderConstructorDecl(constructor: constructor)) } + for method in type.staticMethods { + decls.append(contentsOf: try renderStaticMethod(method: method)) + } + for getter in type.getters { decls.append(try renderGetterDecl(getter: getter)) } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index b780012f0..7928a5c1b 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1856,6 +1856,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private let inputFilePath: String private var jsClassNames: Set private let parent: SwiftToSkeleton + private var staticMethodsByType: [String: [ImportedFunctionSkeleton]] = [:] // MARK: - State Management @@ -1876,6 +1877,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { let from: JSImportFrom? var constructor: ImportedConstructorSkeleton? var methods: [ImportedFunctionSkeleton] + var staticMethods: [ImportedFunctionSkeleton] var getters: [ImportedGetterSkeleton] var setters: [ImportedSetterSkeleton] } @@ -2094,6 +2096,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { from: nil, constructor: nil, methods: [], + staticMethods: [], getters: [], setters: [] ) @@ -2107,6 +2110,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { from: from, constructor: nil, methods: [], + staticMethods: [], getters: [], setters: [] ) @@ -2114,6 +2118,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func exitJSClass() { if case .jsClassBody(let typeName) = state, let type = currentType, type.name == typeName { + let externalStaticMethods = staticMethodsByType[type.name] ?? [] importedTypes.append( ImportedTypeSkeleton( name: type.name, @@ -2121,11 +2126,13 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { from: type.from, constructor: type.constructor, methods: type.methods, + staticMethods: type.staticMethods + externalStaticMethods, getters: type.getters, setters: type.setters, documentation: nil ) ) + staticMethodsByType[type.name] = nil currentType = nil } stateStack.removeLast() @@ -2217,8 +2224,14 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { ) -> Bool { if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) { if isStaticMember { - parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: true).map { - importedFunctions.append($0) + parseFunction( + jsFunction, + node, + enclosingTypeName: typeName, + isStaticMember: true, + includeTypeNameForStatic: false + ).map { + type.staticMethods.append($0) } } else { parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map { @@ -2319,9 +2332,34 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { for member in members { if let function = member.decl.as(FunctionDeclSyntax.self) { if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes), - let parsed = parseFunction(jsFunction, function, enclosingTypeName: typeName, isStaticMember: true) + let parsed = parseFunction( + jsFunction, + function, + enclosingTypeName: typeName, + isStaticMember: true, + includeTypeNameForStatic: !jsClassNames.contains(typeName) + ) { - importedFunctions.append(parsed) + if jsClassNames.contains(typeName) { + if let index = importedTypes.firstIndex(where: { $0.name == typeName }) { + let existing = importedTypes[index] + importedTypes[index] = ImportedTypeSkeleton( + name: existing.name, + jsName: existing.jsName, + from: existing.from, + constructor: existing.constructor, + methods: existing.methods, + staticMethods: existing.staticMethods + [parsed], + getters: existing.getters, + setters: existing.setters, + documentation: existing.documentation + ) + } else { + staticMethodsByType[typeName, default: []].append(parsed) + } + } else { + importedFunctions.append(parsed) + } } else if AttributeChecker.hasJSSetterAttribute(function.attributes) { errors.append( DiagnosticError( @@ -2366,7 +2404,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { _ jsFunction: AttributeSyntax, _ node: FunctionDeclSyntax, enclosingTypeName: String?, - isStaticMember: Bool + isStaticMember: Bool, + includeTypeNameForStatic: Bool = true ) -> ImportedFunctionSkeleton? { guard validateEffects(node.signature.effectSpecifiers, node: node, attributeName: "JSFunction") != nil else { @@ -2377,7 +2416,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { let jsName = AttributeChecker.extractJSName(from: jsFunction) let from = AttributeChecker.extractJSImportFrom(from: jsFunction) let name: String - if isStaticMember, let enclosingTypeName { + if isStaticMember, includeTypeNameForStatic, let enclosingTypeName { name = "\(enclosingTypeName)_\(baseName)" } else { name = baseName diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 60b55cd47..c1b4d2e77 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -2246,6 +2246,14 @@ extension BridgeJSLink { ) } + func callStaticMethod(on objectExpr: String, name: String, returnType: BridgeType) throws -> String? { + let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) + return try call( + calleeExpr: calleeExpr, + returnType: returnType + ) + } + func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" let accessExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) @@ -2318,7 +2326,7 @@ extension BridgeJSLink { return loweredValues.first } - private static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { + static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil { return "\(objectExpr).\(propertyName)" @@ -3130,6 +3138,12 @@ extension BridgeJSLink { importObjectBuilder.assignToImportObject(name: setterAbiName, function: js) importObjectBuilder.appendDts(dts) } + for method in type.staticMethods { + let abiName = method.abiName(context: type, operation: "static") + let (js, dts) = try renderImportedStaticMethod(context: type, method: method) + importObjectBuilder.assignToImportObject(name: abiName, function: js) + importObjectBuilder.appendDts(dts) + } for method in type.methods { let (js, dts) = try renderImportedMethod(context: type, method: method) importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js) @@ -3207,6 +3221,32 @@ extension BridgeJSLink { return (funcLines, []) } + func renderImportedStaticMethod( + context: ImportedTypeSkeleton, + method: ImportedFunctionSkeleton + ) throws -> (js: [String], dts: [String]) { + let thunkBuilder = ImportedThunkBuilder() + for param in method.parameters { + try thunkBuilder.liftParameter(param: param) + } + let importRootExpr = context.from == .global ? "globalThis" : "imports" + let constructorExpr = ImportedThunkBuilder.propertyAccessExpr( + objectExpr: importRootExpr, + propertyName: context.jsName ?? context.name + ) + let returnExpr = try thunkBuilder.callStaticMethod( + on: constructorExpr, + name: method.jsName ?? method.name, + returnType: method.returnType + ) + let funcLines = thunkBuilder.renderFunction( + name: method.abiName(context: context, operation: "static"), + returnExpr: returnExpr, + returnType: method.returnType + ) + return (funcLines, []) + } + func renderImportedMethod( context: ImportedTypeSkeleton, method: ImportedFunctionSkeleton diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 5013dda66..66dda6cb0 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -640,9 +640,14 @@ public struct ImportedFunctionSkeleton: Codable { } public func abiName(context: ImportedTypeSkeleton?) -> String { + return abiName(context: context, operation: nil) + } + + public func abiName(context: ImportedTypeSkeleton?, operation: String?) -> String { return ABINameGenerator.generateImportedABIName( baseName: name, - context: context + context: context, + operation: operation ) } } @@ -752,6 +757,8 @@ public struct ImportedTypeSkeleton: Codable { public let from: JSImportFrom? public let constructor: ImportedConstructorSkeleton? public let methods: [ImportedFunctionSkeleton] + /// Static methods available on the JavaScript constructor. + public var staticMethods: [ImportedFunctionSkeleton] = [] public let getters: [ImportedGetterSkeleton] public let setters: [ImportedSetterSkeleton] public let documentation: String? @@ -762,6 +769,7 @@ public struct ImportedTypeSkeleton: Codable { from: JSImportFrom? = nil, constructor: ImportedConstructorSkeleton? = nil, methods: [ImportedFunctionSkeleton], + staticMethods: [ImportedFunctionSkeleton] = [], getters: [ImportedGetterSkeleton] = [], setters: [ImportedSetterSkeleton] = [], documentation: String? = nil @@ -771,6 +779,7 @@ public struct ImportedTypeSkeleton: Codable { self.from = from self.constructor = constructor self.methods = methods + self.staticMethods = staticMethods self.getters = getters self.setters = setters self.documentation = documentation diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js index 258334ee8..0bd17422b 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js @@ -821,8 +821,12 @@ export class TypeProcessor { const returnType = this.visitType(signature.getReturnType(), node); const effects = this.renderEffects({ isAsync: false }); const swiftMethodName = this.renderIdentifier(swiftName); + const isStatic = node.modifiers?.some( + (modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword + ) ?? false; + const staticKeyword = isStatic ? "static " : ""; - this.swiftLines.push(` ${annotation} func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`); + this.swiftLines.push(` ${annotation} ${staticKeyword}func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`); } /** diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift new file mode 100644 index 000000000..fdfe02003 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift @@ -0,0 +1,13 @@ +extension StaticBox { + @JSFunction static func makeDefault() throws(JSException) -> StaticBox +} + +@JSClass struct StaticBox { + @JSFunction static func create(_ value: Double) throws(JSException) -> StaticBox + @JSFunction func value() throws(JSException) -> Double + @JSFunction static func value() throws(JSException) -> Double +} + +extension StaticBox { + @JSFunction(jsName: "with-dashes") static func dashed() throws(JSException) -> StaticBox +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.json index c9be60d8a..031870e57 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.json @@ -61,6 +61,9 @@ "name" : "JSConsole", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.json index 5cec23259..c75d9e011 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.json @@ -81,6 +81,9 @@ "name" : "JSConsole", "setters" : [ + ], + "staticMethods" : [ + ] }, { @@ -116,6 +119,9 @@ "name" : "WebSocket", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json index c40f0dc82..659232d56 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json @@ -186,6 +186,9 @@ "name" : "Foo", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.json index 40b97c12b..b983f27a4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.json @@ -219,6 +219,9 @@ } } } + ], + "staticMethods" : [ + ] }, { @@ -248,6 +251,9 @@ "name" : "_Weird", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.json index 07906a404..be1d0f4cd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.json @@ -107,6 +107,9 @@ } } } + ], + "staticMethods" : [ + ] }, { @@ -162,6 +165,9 @@ "name" : "Animatable", "setters" : [ + ], + "staticMethods" : [ + ] } ] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json new file mode 100644 index 000000000..55fdb844c --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json @@ -0,0 +1,108 @@ +{ + "exported" : { + "classes" : [ + + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "protocols" : [ + + ], + "structs" : [ + + ] + }, + "imported" : { + "children" : [ + { + "functions" : [ + + ], + "types" : [ + { + "getters" : [ + + ], + "methods" : [ + { + "name" : "value", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } + } + ], + "name" : "StaticBox", + "setters" : [ + + ], + "staticMethods" : [ + { + "name" : "create", + "parameters" : [ + { + "name" : "value", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "name" : "value", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } + }, + { + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "jsName" : "with-dashes", + "name" : "dashed", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + } + ] + } + ] + } + ] + }, + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift new file mode 100644 index 000000000..ed54edf9d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift @@ -0,0 +1,86 @@ +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_create_static") +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 +#else +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value_static") +fileprivate func bjs_StaticBox_value_static() -> Float64 +#else +fileprivate func bjs_StaticBox_value_static() -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_makeDefault_static") +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 +#else +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_dashed_static") +fileprivate func bjs_StaticBox_dashed_static() -> Int32 +#else +fileprivate func bjs_StaticBox_dashed_static() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value") +fileprivate func bjs_StaticBox_value(_ self: Int32) -> Float64 +#else +fileprivate func bjs_StaticBox_value(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$StaticBox_create(_ value: Double) throws(JSException) -> StaticBox { + let valueValue = value.bridgeJSLowerParameter() + let ret = bjs_StaticBox_create_static(valueValue) + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_value() throws(JSException) -> Double { + let ret = bjs_StaticBox_value_static() + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_makeDefault_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_dashed() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_dashed_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_StaticBox_value(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift index e066ad272..1e8737280 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift @@ -48,6 +48,15 @@ extension FeatureFlag: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum {} @JSFunction(jsName: "method-with-dashes") func method_with_dashes() throws(JSException) -> String } +@JSClass struct StaticBox { + @JSFunction init(_ value: Double) throws(JSException) + @JSFunction func value() throws(JSException) -> Double + @JSFunction static func create(_ value: Double) throws(JSException) -> StaticBox + @JSFunction static func value() throws(JSException) -> Double + @JSFunction static func makeDefault() throws(JSException) -> StaticBox + @JSFunction(jsName: "with-dashes") static func with_dashes() throws(JSException) -> StaticBox +} + @JSFunction(from: .global) func parseInt(_ string: String) throws(JSException) -> Double @JSClass(from: .global) struct Animal { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 94359a709..556409c07 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -8140,6 +8140,111 @@ func _$_WeirdClass_method_with_dashes(_ self: JSObject) throws(JSException) -> S return String.bridgeJSLiftReturn(ret) } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_init") +fileprivate func bjs_StaticBox_init(_ value: Float64) -> Int32 +#else +fileprivate func bjs_StaticBox_init(_ value: Float64) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_create_static") +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 +#else +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_value_static") +fileprivate func bjs_StaticBox_value_static() -> Float64 +#else +fileprivate func bjs_StaticBox_value_static() -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_makeDefault_static") +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 +#else +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_with_dashes_static") +fileprivate func bjs_StaticBox_with_dashes_static() -> Int32 +#else +fileprivate func bjs_StaticBox_with_dashes_static() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StaticBox_value") +fileprivate func bjs_StaticBox_value(_ self: Int32) -> Float64 +#else +fileprivate func bjs_StaticBox_value(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$StaticBox_init(_ value: Double) throws(JSException) -> JSObject { + let valueValue = value.bridgeJSLowerParameter() + let ret = bjs_StaticBox_init(valueValue) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_create(_ value: Double) throws(JSException) -> StaticBox { + let valueValue = value.bridgeJSLowerParameter() + let ret = bjs_StaticBox_create_static(valueValue) + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_value() throws(JSException) -> Double { + let ret = bjs_StaticBox_value_static() + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_makeDefault_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_with_dashes() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_with_dashes_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { + let selfValue = self.bridgeJSLowerParameter() + let ret = bjs_StaticBox_value(selfValue) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Animal_init") fileprivate func bjs_Animal_init(_ name: Int32, _ age: Float64, _ isCat: Int32) -> Int32 diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 4b8ac7c7b..a2e4310d9 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -11668,6 +11668,9 @@ "name" : "Foo", "setters" : [ + ], + "staticMethods" : [ + ] } ] @@ -11967,6 +11970,9 @@ } } } + ], + "staticMethods" : [ + ] }, { @@ -11996,6 +12002,97 @@ "name" : "_WeirdClass", "setters" : [ + ], + "staticMethods" : [ + + ] + }, + { + "constructor" : { + "parameters" : [ + { + "name" : "value", + "type" : { + "double" : { + + } + } + } + ] + }, + "getters" : [ + + ], + "methods" : [ + { + "name" : "value", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } + } + ], + "name" : "StaticBox", + "setters" : [ + + ], + "staticMethods" : [ + { + "name" : "create", + "parameters" : [ + { + "name" : "value", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "name" : "value", + "parameters" : [ + + ], + "returnType" : { + "double" : { + + } + } + }, + { + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "jsName" : "with-dashes", + "name" : "with_dashes", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + } ] }, { @@ -12107,6 +12204,9 @@ } } } + ], + "staticMethods" : [ + ] } ] diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index ea9f8c68f..b43a711e2 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -127,4 +127,17 @@ class ImportAPITests: XCTestCase { let obj = try _WeirdClass() XCTAssertEqual(try obj.method_with_dashes(), "ok") } + + func testJSClassStaticFunctions() throws { + let created = try StaticBox.create(10) + XCTAssertEqual(try created.value(), 10) + + let defaultBox = try StaticBox.makeDefault() + XCTAssertEqual(try defaultBox.value(), 0) + + XCTAssertEqual(try StaticBox.value(), 99) + + let dashed = try StaticBox.with_dashes() + XCTAssertEqual(try dashed.value(), 7) + } } diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index 983d6052d..e38445805 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -31,3 +31,12 @@ export class $WeirdClass { constructor(); "method-with-dashes"(): string; } + +export class StaticBox { + constructor(value: number); + value(): number; + static create(value: number): StaticBox; + static value(): number; + static makeDefault(): StaticBox; + static "with-dashes"(): StaticBox; +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 850b31b6e..58a85acc2 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -9,6 +9,27 @@ export async function setupOptions(options, context) { Error.stackTraceLimit = 100; setupTestGlobals(globalThis); + class StaticBox { + constructor(value) { + this._value = value; + } + value() { + return this._value; + } + static create(value) { + return new StaticBox(value); + } + static value() { + return 99; + } + static makeDefault() { + return new StaticBox(0); + } + static ["with-dashes"]() { + return new StaticBox(7); + } + } + return { ...options, getImports: (importsContext) => { @@ -78,6 +99,7 @@ export async function setupOptions(options, context) { return "ok"; } }, + StaticBox, Foo: ImportedFoo, runAsyncWorks: async () => { const exports = importsContext.getExports(); From a2aa6e1d07e60f8397bf02fd7b8acc51c9d058c7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 11:19:11 +0900 Subject: [PATCH 2/6] BridgeJS: Cover static methods in ts2swift tests --- .../JavaScript/test/__snapshots__/ts2swift.test.js.snap | 1 + .../TS2Swift/JavaScript/test/fixtures/TypeScriptClass.d.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap index 6add64de4..c7f3b6652 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap @@ -295,6 +295,7 @@ exports[`ts2swift > snapshots Swift output for TypeScriptClass.d.ts > TypeScript @JSFunction init(_ name: String) throws(JSException) @JSFunction func greet() throws(JSException) -> String @JSFunction func changeName(_ name: String) throws(JSException) -> Void + @JSFunction static func staticMethod(_ p1: Double, _ p2: String) throws(JSException) -> String } " `; diff --git a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/TypeScriptClass.d.ts b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/TypeScriptClass.d.ts index 074772f24..0745de1f9 100644 --- a/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/TypeScriptClass.d.ts +++ b/Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/fixtures/TypeScriptClass.d.ts @@ -4,4 +4,6 @@ export class Greeter { constructor(name: string); greet(): string; changeName(name: string): void; + + static staticMethod(p1: number, p2: string): string; } From 4e3f02b623375316981153bcaf9280bda115bb99 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 11:33:10 +0900 Subject: [PATCH 3/6] BridgeJS: Fix d.ts rendering of static methods --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 32 +- .../MacroSwift/JSClassStaticFunctions.swift | 5 + .../JSClassStaticFunctions.json | 44 +++ .../JSClassStaticFunctions.swift | 36 +++ .../JSClassStaticFunctions.d.ts | 32 ++ .../JSClassStaticFunctions.js | 286 ++++++++++++++++++ 6 files changed, 422 insertions(+), 13 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index c1b4d2e77..d39b524b5 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -3144,6 +3144,25 @@ extension BridgeJSLink { importObjectBuilder.assignToImportObject(name: abiName, function: js) importObjectBuilder.appendDts(dts) } + if type.from == nil, type.constructor != nil || !type.staticMethods.isEmpty { + let dtsPrinter = CodeFragmentPrinter() + dtsPrinter.write("\(type.name): {") + dtsPrinter.indent { + if let constructor = type.constructor { + let returnType = BridgeType.jsObject(type.name) + dtsPrinter.write( + "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" + ) + } + for method in type.staticMethods { + let methodName = method.jsName ?? method.name + let signature = "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" + dtsPrinter.write(signature) + } + } + dtsPrinter.write("}") + importObjectBuilder.appendDts(dtsPrinter.lines) + } for method in type.methods { let (js, dts) = try renderImportedMethod(context: type, method: method) importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js) @@ -3174,19 +3193,6 @@ extension BridgeJSLink { returnType: returnType ) importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) - - if type.from == nil { - let dtsPrinter = CodeFragmentPrinter() - dtsPrinter.write("\(type.name): {") - dtsPrinter.indent { - dtsPrinter.write( - "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" - ) - } - dtsPrinter.write("}") - - importObjectBuilder.appendDts(dtsPrinter.lines) - } } func renderImportedGetter( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift index fdfe02003..ecf1b2473 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift @@ -11,3 +11,8 @@ extension StaticBox { extension StaticBox { @JSFunction(jsName: "with-dashes") static func dashed() throws(JSException) -> StaticBox } + +@JSClass struct WithCtor { + @JSFunction init(_ value: Double) throws(JSException) + @JSFunction static func create(_ value: Double) throws(JSException) -> WithCtor +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json index 55fdb844c..df3d3829e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json @@ -99,6 +99,50 @@ } } ] + }, + { + "constructor" : { + "parameters" : [ + { + "name" : "value", + "type" : { + "double" : { + + } + } + } + ] + }, + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "WithCtor", + "setters" : [ + + ], + "staticMethods" : [ + { + "name" : "create", + "parameters" : [ + { + "name" : "value", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + "_0" : "WithCtor" + } + } + } + ] } ] } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift index ed54edf9d..5e297f929 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift @@ -83,4 +83,40 @@ func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { throw error } return Double.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_WithCtor_init") +fileprivate func bjs_WithCtor_init(_ value: Float64) -> Int32 +#else +fileprivate func bjs_WithCtor_init(_ value: Float64) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_WithCtor_create_static") +fileprivate func bjs_WithCtor_create_static(_ value: Float64) -> Int32 +#else +fileprivate func bjs_WithCtor_create_static(_ value: Float64) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +func _$WithCtor_init(_ value: Double) throws(JSException) -> JSObject { + let valueValue = value.bridgeJSLowerParameter() + let ret = bjs_WithCtor_init(valueValue) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$WithCtor_create(_ value: Double) throws(JSException) -> WithCtor { + let valueValue = value.bridgeJSLowerParameter() + let ret = bjs_WithCtor_create_static(valueValue) + if let error = _swift_js_take_exception() { + throw error + } + return WithCtor.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts new file mode 100644 index 000000000..3b2b5de99 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts @@ -0,0 +1,32 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export interface StaticBox { + value(): number; +} +export interface WithCtor { +} +export type Exports = { +} +export type Imports = { + StaticBox: { + create(value: number): StaticBox; + value(): number; + makeDefault(): StaticBox; + "with-dashes"(): StaticBox; + } + WithCtor: { + new(value: number): WithCtor; + create(value: number): WithCtor; + } +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js new file mode 100644 index 000000000..f8f0ba86f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js @@ -0,0 +1,286 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let tmpRetTag; + let tmpRetStrings = []; + let tmpRetInts = []; + let tmpRetF32s = []; + let tmpRetF64s = []; + let tmpParamInts = []; + let tmpParamF32s = []; + let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; + let tmpStructCleanups = []; + const enumHelpers = {}; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_tag"] = function(tag) { + tmpRetTag = tag; + } + bjs["swift_js_push_i32"] = function(v) { + tmpRetInts.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + tmpRetF32s.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + tmpRetF64s.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + const value = textDecoder.decode(bytes); + tmpRetStrings.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return tmpParamInts.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return tmpParamF32s.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return tmpParamF64s.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return tmpParamPointers.pop(); + } + bjs["swift_js_struct_cleanup"] = function(cleanupId) { + if (cleanupId === 0) { return; } + const index = (cleanupId | 0) - 1; + const cleanup = tmpStructCleanups[index]; + tmpStructCleanups[index] = null; + if (cleanup) { cleanup(); } + while (tmpStructCleanups.length > 0 && tmpStructCleanups[tmpStructCleanups.length - 1] == null) { + tmpStructCleanups.pop(); + } + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_StaticBox_create_static"] = function bjs_StaticBox_create_static(value) { + try { + let ret = imports.StaticBox.create(value); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_StaticBox_value_static"] = function bjs_StaticBox_value_static() { + try { + let ret = imports.StaticBox.value(); + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_StaticBox_makeDefault_static"] = function bjs_StaticBox_makeDefault_static() { + try { + let ret = imports.StaticBox.makeDefault(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_StaticBox_dashed_static"] = function bjs_StaticBox_dashed_static() { + try { + let ret = imports.StaticBox["with-dashes"](); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_StaticBox_value"] = function bjs_StaticBox_value(self) { + try { + let ret = swift.memory.getObject(self).value(); + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WithCtor_init"] = function bjs_WithCtor_init(value) { + try { + return swift.memory.retain(new imports.WithCtor(value)); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WithCtor_create_static"] = function bjs_WithCtor_create_static(value) { + try { + let ret = imports.WithCtor.create(value); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const exports = { + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file From 0c3cb258e028fd10bc1c2bad2255074cd80ed04e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 11:36:11 +0900 Subject: [PATCH 4/6] ./Utilities/format.swift --- Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 3 ++- .../Inputs/MacroSwift/JSClassStaticFunctions.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index d39b524b5..fd5faee24 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -3156,7 +3156,8 @@ extension BridgeJSLink { } for method in type.staticMethods { let methodName = method.jsName ?? method.name - let signature = "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" + let signature = + "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" dtsPrinter.write(signature) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift index ecf1b2473..891eab118 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift @@ -15,4 +15,4 @@ extension StaticBox { @JSClass struct WithCtor { @JSFunction init(_ value: Double) throws(JSException) @JSFunction static func create(_ value: Double) throws(JSException) -> WithCtor -} \ No newline at end of file +} From cf6c745538acdea29e2c30f114f894ec3b2704fa Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 12:22:20 +0900 Subject: [PATCH 5/6] BridgeJS: Simplify static method naming --- .../BridgeJSCore/SwiftToSkeleton.swift | 54 ++++------------- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 4 +- .../JSClassStaticFunctions.json | 60 ++++++++++++------- .../JSClassStaticFunctions.swift | 56 ++++++++--------- .../JSClassStaticFunctions.d.ts | 8 ++- .../JSClassStaticFunctions.js | 20 +++---- 6 files changed, 93 insertions(+), 109 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 7928a5c1b..d84d30506 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -2198,7 +2198,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func handleTopLevelFunction(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes), - let function = parseFunction(jsFunction, node, enclosingTypeName: nil, isStaticMember: true) + let function = parseFunction(jsFunction, node) { importedFunctions.append(function) return .skipChildren @@ -2223,19 +2223,11 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { type: inout CurrentType ) -> Bool { if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) { - if isStaticMember { - parseFunction( - jsFunction, - node, - enclosingTypeName: typeName, - isStaticMember: true, - includeTypeNameForStatic: false - ).map { - type.staticMethods.append($0) - } - } else { - parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map { - type.methods.append($0) + if let method = parseFunction(jsFunction, node) { + if isStaticMember { + type.staticMethods.append(method) + } else { + type.methods.append(method) } } return true @@ -2332,30 +2324,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { for member in members { if let function = member.decl.as(FunctionDeclSyntax.self) { if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes), - let parsed = parseFunction( - jsFunction, - function, - enclosingTypeName: typeName, - isStaticMember: true, - includeTypeNameForStatic: !jsClassNames.contains(typeName) - ) - { + let parsed = parseFunction(jsFunction, function) { if jsClassNames.contains(typeName) { if let index = importedTypes.firstIndex(where: { $0.name == typeName }) { - let existing = importedTypes[index] - importedTypes[index] = ImportedTypeSkeleton( - name: existing.name, - jsName: existing.jsName, - from: existing.from, - constructor: existing.constructor, - methods: existing.methods, - staticMethods: existing.staticMethods + [parsed], - getters: existing.getters, - setters: existing.setters, - documentation: existing.documentation - ) + importedTypes[index].staticMethods.append(parsed) } else { - staticMethodsByType[typeName, default: []].append(parsed) + importedTypes.append(ImportedTypeSkeleton(name: typeName, staticMethods: [parsed])) } } else { importedFunctions.append(parsed) @@ -2403,9 +2377,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func parseFunction( _ jsFunction: AttributeSyntax, _ node: FunctionDeclSyntax, - enclosingTypeName: String?, - isStaticMember: Bool, - includeTypeNameForStatic: Bool = true ) -> ImportedFunctionSkeleton? { guard validateEffects(node.signature.effectSpecifiers, node: node, attributeName: "JSFunction") != nil else { @@ -2415,12 +2386,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text) let jsName = AttributeChecker.extractJSName(from: jsFunction) let from = AttributeChecker.extractJSImportFrom(from: jsFunction) - let name: String - if isStaticMember, includeTypeNameForStatic, let enclosingTypeName { - name = "\(enclosingTypeName)_\(baseName)" - } else { - name = baseName - } + let name = baseName let parameters = parseParameters(from: node.signature.parameterClause) let returnType: BridgeType diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 66dda6cb0..6fe88a6a0 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -758,7 +758,7 @@ public struct ImportedTypeSkeleton: Codable { public let constructor: ImportedConstructorSkeleton? public let methods: [ImportedFunctionSkeleton] /// Static methods available on the JavaScript constructor. - public var staticMethods: [ImportedFunctionSkeleton] = [] + public var staticMethods: [ImportedFunctionSkeleton] public let getters: [ImportedGetterSkeleton] public let setters: [ImportedSetterSkeleton] public let documentation: String? @@ -768,7 +768,7 @@ public struct ImportedTypeSkeleton: Codable { jsName: String? = nil, from: JSImportFrom? = nil, constructor: ImportedConstructorSkeleton? = nil, - methods: [ImportedFunctionSkeleton], + methods: [ImportedFunctionSkeleton] = [], staticMethods: [ImportedFunctionSkeleton] = [], getters: [ImportedGetterSkeleton] = [], setters: [ImportedSetterSkeleton] = [], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json index df3d3829e..7bf5db0fd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json @@ -24,6 +24,43 @@ ], "types" : [ + { + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "StaticBox", + "setters" : [ + + ], + "staticMethods" : [ + { + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "jsName" : "with-dashes", + "name" : "dashed", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + } + ] + }, { "getters" : [ @@ -74,29 +111,6 @@ } } - }, - { - "name" : "makeDefault", - "parameters" : [ - - ], - "returnType" : { - "jsObject" : { - "_0" : "StaticBox" - } - } - }, - { - "jsName" : "with-dashes", - "name" : "dashed", - "parameters" : [ - - ], - "returnType" : { - "jsObject" : { - "_0" : "StaticBox" - } - } } ] }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift index 5e297f929..efb83f406 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift @@ -1,35 +1,51 @@ #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_create_static") -fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_makeDefault_static") +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 #else -fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { fatalError("Only available on WebAssembly") } #endif #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value_static") -fileprivate func bjs_StaticBox_value_static() -> Float64 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_dashed_static") +fileprivate func bjs_StaticBox_dashed_static() -> Int32 #else -fileprivate func bjs_StaticBox_value_static() -> Float64 { +fileprivate func bjs_StaticBox_dashed_static() -> Int32 { fatalError("Only available on WebAssembly") } #endif +func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_makeDefault_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_dashed() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_dashed_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_makeDefault_static") -fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_create_static") +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 #else -fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { fatalError("Only available on WebAssembly") } #endif #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_dashed_static") -fileprivate func bjs_StaticBox_dashed_static() -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value_static") +fileprivate func bjs_StaticBox_value_static() -> Float64 #else -fileprivate func bjs_StaticBox_dashed_static() -> Int32 { +fileprivate func bjs_StaticBox_value_static() -> Float64 { fatalError("Only available on WebAssembly") } #endif @@ -60,22 +76,6 @@ func _$StaticBox_value() throws(JSException) -> Double { return Double.bridgeJSLiftReturn(ret) } -func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { - let ret = bjs_StaticBox_makeDefault_static() - if let error = _swift_js_take_exception() { - throw error - } - return StaticBox.bridgeJSLiftReturn(ret) -} - -func _$StaticBox_dashed() throws(JSException) -> StaticBox { - let ret = bjs_StaticBox_dashed_static() - if let error = _swift_js_take_exception() { - throw error - } - return StaticBox.bridgeJSLiftReturn(ret) -} - func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { let selfValue = self.bridgeJSLowerParameter() let ret = bjs_StaticBox_value(selfValue) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts index 3b2b5de99..e4c59459a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts @@ -4,6 +4,8 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface StaticBox { +} export interface StaticBox { value(): number; } @@ -13,11 +15,13 @@ export type Exports = { } export type Imports = { StaticBox: { - create(value: number): StaticBox; - value(): number; makeDefault(): StaticBox; "with-dashes"(): StaticBox; } + StaticBox: { + create(value: number): StaticBox; + value(): number; + } WithCtor: { new(value: number): WithCtor; create(value: number): WithCtor; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js index f8f0ba86f..4acf43685 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js @@ -203,37 +203,37 @@ export async function createInstantiator(options, swift) { return pointer || 0; } const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_StaticBox_create_static"] = function bjs_StaticBox_create_static(value) { + TestModule["bjs_StaticBox_makeDefault_static"] = function bjs_StaticBox_makeDefault_static() { try { - let ret = imports.StaticBox.create(value); + let ret = imports.StaticBox.makeDefault(); return swift.memory.retain(ret); } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_value_static"] = function bjs_StaticBox_value_static() { + TestModule["bjs_StaticBox_dashed_static"] = function bjs_StaticBox_dashed_static() { try { - let ret = imports.StaticBox.value(); - return ret; + let ret = imports.StaticBox["with-dashes"](); + return swift.memory.retain(ret); } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_makeDefault_static"] = function bjs_StaticBox_makeDefault_static() { + TestModule["bjs_StaticBox_create_static"] = function bjs_StaticBox_create_static(value) { try { - let ret = imports.StaticBox.makeDefault(); + let ret = imports.StaticBox.create(value); return swift.memory.retain(ret); } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_dashed_static"] = function bjs_StaticBox_dashed_static() { + TestModule["bjs_StaticBox_value_static"] = function bjs_StaticBox_value_static() { try { - let ret = imports.StaticBox["with-dashes"](); - return swift.memory.retain(ret); + let ret = imports.StaticBox.value(); + return ret; } catch (error) { setException(error); return 0 From 5161a41903aa660c84ad30f332070c07e2f4fcc5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 4 Feb 2026 12:54:10 +0900 Subject: [PATCH 6/6] BridgeJS: Remove support for static members in extensions --- .../BridgeJSCore/SwiftToSkeleton.swift | 51 +--------------- .../MacroSwift/JSClassStaticFunctions.swift | 8 +-- .../JSClassStaticFunctions.json | 60 +++++++------------ .../JSClassStaticFunctions.swift | 56 ++++++++--------- .../JSClassStaticFunctions.d.ts | 8 +-- .../JSClassStaticFunctions.js | 20 +++---- 6 files changed, 65 insertions(+), 138 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index d84d30506..9fb8b8ab0 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1856,8 +1856,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private let inputFilePath: String private var jsClassNames: Set private let parent: SwiftToSkeleton - private var staticMethodsByType: [String: [ImportedFunctionSkeleton]] = [:] - // MARK: - State Management enum State { @@ -2118,7 +2116,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { private func exitJSClass() { if case .jsClassBody(let typeName) = state, let type = currentType, type.name == typeName { - let externalStaticMethods = staticMethodsByType[type.name] ?? [] importedTypes.append( ImportedTypeSkeleton( name: type.name, @@ -2126,13 +2123,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { from: type.from, constructor: type.constructor, methods: type.methods, - staticMethods: type.staticMethods + externalStaticMethods, + staticMethods: type.staticMethods, getters: type.getters, setters: type.setters, documentation: nil ) ) - staticMethodsByType[type.name] = nil currentType = nil } stateStack.removeLast() @@ -2172,12 +2168,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { // MARK: - Visitor Methods - override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - let typeName = node.extendedType.trimmedDescription - collectStaticMembers(in: node.memberBlock.members, typeName: typeName) - return .skipChildren - } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { switch state { case .topLevel: @@ -2318,45 +2308,6 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { } } - // MARK: - Member Collection - - private func collectStaticMembers(in members: MemberBlockItemListSyntax, typeName: String) { - for member in members { - if let function = member.decl.as(FunctionDeclSyntax.self) { - if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes), - let parsed = parseFunction(jsFunction, function) { - if jsClassNames.contains(typeName) { - if let index = importedTypes.firstIndex(where: { $0.name == typeName }) { - importedTypes[index].staticMethods.append(parsed) - } else { - importedTypes.append(ImportedTypeSkeleton(name: typeName, staticMethods: [parsed])) - } - } else { - importedFunctions.append(parsed) - } - } else if AttributeChecker.hasJSSetterAttribute(function.attributes) { - errors.append( - DiagnosticError( - node: function, - message: - "@JSSetter is not supported for static members. Use it only for instance members in @JSClass types." - ) - ) - } - } else if let variable = member.decl.as(VariableDeclSyntax.self), - AttributeChecker.hasJSGetterAttribute(variable.attributes) - { - errors.append( - DiagnosticError( - node: variable, - message: - "@JSGetter is not supported for static members. Use it only for instance members in @JSClass types." - ) - ) - } - } - } - // MARK: - Parsing Methods private func parseConstructor( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift index 891eab118..c64952565 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/JSClassStaticFunctions.swift @@ -1,14 +1,8 @@ -extension StaticBox { - @JSFunction static func makeDefault() throws(JSException) -> StaticBox -} - @JSClass struct StaticBox { @JSFunction static func create(_ value: Double) throws(JSException) -> StaticBox @JSFunction func value() throws(JSException) -> Double @JSFunction static func value() throws(JSException) -> Double -} - -extension StaticBox { + @JSFunction static func makeDefault() throws(JSException) -> StaticBox @JSFunction(jsName: "with-dashes") static func dashed() throws(JSException) -> StaticBox } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json index 7bf5db0fd..df3d3829e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.json @@ -24,43 +24,6 @@ ], "types" : [ - { - "getters" : [ - - ], - "methods" : [ - - ], - "name" : "StaticBox", - "setters" : [ - - ], - "staticMethods" : [ - { - "name" : "makeDefault", - "parameters" : [ - - ], - "returnType" : { - "jsObject" : { - "_0" : "StaticBox" - } - } - }, - { - "jsName" : "with-dashes", - "name" : "dashed", - "parameters" : [ - - ], - "returnType" : { - "jsObject" : { - "_0" : "StaticBox" - } - } - } - ] - }, { "getters" : [ @@ -111,6 +74,29 @@ } } + }, + { + "name" : "makeDefault", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } + }, + { + "jsName" : "with-dashes", + "name" : "dashed", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "StaticBox" + } + } } ] }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift index efb83f406..5e297f929 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClassStaticFunctions.swift @@ -1,51 +1,35 @@ #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_makeDefault_static") -fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_create_static") +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 #else -fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { +fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { fatalError("Only available on WebAssembly") } #endif #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_dashed_static") -fileprivate func bjs_StaticBox_dashed_static() -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value_static") +fileprivate func bjs_StaticBox_value_static() -> Float64 #else -fileprivate func bjs_StaticBox_dashed_static() -> Int32 { +fileprivate func bjs_StaticBox_value_static() -> Float64 { fatalError("Only available on WebAssembly") } #endif -func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { - let ret = bjs_StaticBox_makeDefault_static() - if let error = _swift_js_take_exception() { - throw error - } - return StaticBox.bridgeJSLiftReturn(ret) -} - -func _$StaticBox_dashed() throws(JSException) -> StaticBox { - let ret = bjs_StaticBox_dashed_static() - if let error = _swift_js_take_exception() { - throw error - } - return StaticBox.bridgeJSLiftReturn(ret) -} - #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_create_static") -fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_makeDefault_static") +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 #else -fileprivate func bjs_StaticBox_create_static(_ value: Float64) -> Int32 { +fileprivate func bjs_StaticBox_makeDefault_static() -> Int32 { fatalError("Only available on WebAssembly") } #endif #if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_value_static") -fileprivate func bjs_StaticBox_value_static() -> Float64 +@_extern(wasm, module: "TestModule", name: "bjs_StaticBox_dashed_static") +fileprivate func bjs_StaticBox_dashed_static() -> Int32 #else -fileprivate func bjs_StaticBox_value_static() -> Float64 { +fileprivate func bjs_StaticBox_dashed_static() -> Int32 { fatalError("Only available on WebAssembly") } #endif @@ -76,6 +60,22 @@ func _$StaticBox_value() throws(JSException) -> Double { return Double.bridgeJSLiftReturn(ret) } +func _$StaticBox_makeDefault() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_makeDefault_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + +func _$StaticBox_dashed() throws(JSException) -> StaticBox { + let ret = bjs_StaticBox_dashed_static() + if let error = _swift_js_take_exception() { + throw error + } + return StaticBox.bridgeJSLiftReturn(ret) +} + func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { let selfValue = self.bridgeJSLowerParameter() let ret = bjs_StaticBox_value(selfValue) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts index e4c59459a..3b2b5de99 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts @@ -4,8 +4,6 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. -export interface StaticBox { -} export interface StaticBox { value(): number; } @@ -14,13 +12,11 @@ export interface WithCtor { export type Exports = { } export type Imports = { - StaticBox: { - makeDefault(): StaticBox; - "with-dashes"(): StaticBox; - } StaticBox: { create(value: number): StaticBox; value(): number; + makeDefault(): StaticBox; + "with-dashes"(): StaticBox; } WithCtor: { new(value: number): WithCtor; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js index 4acf43685..f8f0ba86f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js @@ -203,37 +203,37 @@ export async function createInstantiator(options, swift) { return pointer || 0; } const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_StaticBox_makeDefault_static"] = function bjs_StaticBox_makeDefault_static() { + TestModule["bjs_StaticBox_create_static"] = function bjs_StaticBox_create_static(value) { try { - let ret = imports.StaticBox.makeDefault(); + let ret = imports.StaticBox.create(value); return swift.memory.retain(ret); } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_dashed_static"] = function bjs_StaticBox_dashed_static() { + TestModule["bjs_StaticBox_value_static"] = function bjs_StaticBox_value_static() { try { - let ret = imports.StaticBox["with-dashes"](); - return swift.memory.retain(ret); + let ret = imports.StaticBox.value(); + return ret; } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_create_static"] = function bjs_StaticBox_create_static(value) { + TestModule["bjs_StaticBox_makeDefault_static"] = function bjs_StaticBox_makeDefault_static() { try { - let ret = imports.StaticBox.create(value); + let ret = imports.StaticBox.makeDefault(); return swift.memory.retain(ret); } catch (error) { setException(error); return 0 } } - TestModule["bjs_StaticBox_value_static"] = function bjs_StaticBox_value_static() { + TestModule["bjs_StaticBox_dashed_static"] = function bjs_StaticBox_dashed_static() { try { - let ret = imports.StaticBox.value(); - return ret; + let ret = imports.StaticBox["with-dashes"](); + return swift.memory.retain(ret); } catch (error) { setException(error); return 0