GeezerVison is showing github.com — BridgeJS: Support `@JSFunction static func` · rarestype/JavaScriptKit@6990143
File treeExpand file treeCollapse file tree Open diff view settings Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript __Snapshots__/BridgeJSCodegenTests
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 191 | 191 | "name" : "TS2Swift", | 192 | 192 | "setters" : [ | 193 | 193 |
| | 194 | + ], | | 195 | + "staticMethods" : [ | | 196 | + | 194 | 197 | ] | 195 | 198 | } | 196 | 199 | ] | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +22Lines changed: 22 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -398,6 +398,24 @@ public struct ImportTS { | 398 | 398 | ] | 399 | 399 | } | 400 | 400 |
| | 401 | + func renderStaticMethod(method: ImportedFunctionSkeleton) throws -> [DeclSyntax] { | | 402 | + let abiName = method.abiName(context: type, operation: "static") | | 403 | + let builder = CallJSEmission(moduleName: moduleName, abiName: abiName) | | 404 | + for param in method.parameters { | | 405 | + try builder.lowerParameter(param: param) | | 406 | + } | | 407 | + try builder.call(returnType: method.returnType) | | 408 | + try builder.liftReturnValue(returnType: method.returnType) | | 409 | + topLevelDecls.append(builder.renderImportDecl()) | | 410 | + return [ | | 411 | + builder.renderThunkDecl( | | 412 | + name: Self.thunkName(type: type, method: method), | | 413 | + parameters: method.parameters, | | 414 | + returnType: method.returnType | | 415 | + ) | | 416 | + ] | | 417 | + } | | 418 | + | 401 | 419 | func renderConstructorDecl(constructor: ImportedConstructorSkeleton) throws -> [DeclSyntax] { | 402 | 420 | let builder = CallJSEmission(moduleName: moduleName, abiName: constructor.abiName(context: type)) | 403 | 421 | for param in constructor.parameters { | @@ -462,6 +480,10 @@ public struct ImportTS { | 462 | 480 | decls.append(contentsOf: try renderConstructorDecl(constructor: constructor)) | 463 | 481 | } | 464 | 482 |
| | 483 | + for method in type.staticMethods { | | 484 | + decls.append(contentsOf: try renderStaticMethod(method: method)) | | 485 | + } | | 486 | + | 465 | 487 | for getter in type.getters { | 466 | 488 | decls.append(try renderGetterDecl(getter: getter)) | 467 | 489 | } | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +45-6Lines changed: 45 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1856,6 +1856,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 1856 | 1856 | private let inputFilePath: String | 1857 | 1857 | private var jsClassNames: Set<String> | 1858 | 1858 | private let parent: SwiftToSkeleton | | 1859 | + private var staticMethodsByType: [String: [ImportedFunctionSkeleton]] = [:] | 1859 | 1860 |
| 1860 | 1861 | // MARK: - State Management | 1861 | 1862 |
| @@ -1876,6 +1877,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 1876 | 1877 | let from: JSImportFrom? | 1877 | 1878 | var constructor: ImportedConstructorSkeleton? | 1878 | 1879 | var methods: [ImportedFunctionSkeleton] | | 1880 | + var staticMethods: [ImportedFunctionSkeleton] | 1879 | 1881 | var getters: [ImportedGetterSkeleton] | 1880 | 1882 | var setters: [ImportedSetterSkeleton] | 1881 | 1883 | } | @@ -2094,6 +2096,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2094 | 2096 | from: nil, | 2095 | 2097 | constructor: nil, | 2096 | 2098 | methods: [], | | 2099 | + staticMethods: [], | 2097 | 2100 | getters: [], | 2098 | 2101 | setters: [] | 2099 | 2102 | ) | @@ -2107,25 +2110,29 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2107 | 2110 | from: from, | 2108 | 2111 | constructor: nil, | 2109 | 2112 | methods: [], | | 2113 | + staticMethods: [], | 2110 | 2114 | getters: [], | 2111 | 2115 | setters: [] | 2112 | 2116 | ) | 2113 | 2117 | } | 2114 | 2118 |
| 2115 | 2119 | private func exitJSClass() { | 2116 | 2120 | if case .jsClassBody(let typeName) = state, let type = currentType, type.name == typeName { | | 2121 | + let externalStaticMethods = staticMethodsByType[type.name] ?? [] | 2117 | 2122 | importedTypes.append( | 2118 | 2123 | ImportedTypeSkeleton( | 2119 | 2124 | name: type.name, | 2120 | 2125 | jsName: type.jsName, | 2121 | 2126 | from: type.from, | 2122 | 2127 | constructor: type.constructor, | 2123 | 2128 | methods: type.methods, | | 2129 | + staticMethods: type.staticMethods + externalStaticMethods, | 2124 | 2130 | getters: type.getters, | 2125 | 2131 | setters: type.setters, | 2126 | 2132 | documentation: nil | 2127 | 2133 | ) | 2128 | 2134 | ) | | 2135 | + staticMethodsByType[type.name] = nil | 2129 | 2136 | currentType = nil | 2130 | 2137 | } | 2131 | 2138 | stateStack.removeLast() | @@ -2217,8 +2224,14 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2217 | 2224 | ) -> Bool { | 2218 | 2225 | if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) { | 2219 | 2226 | if isStaticMember { | 2220 | | - parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: true).map { | 2221 | | - importedFunctions.append($0) | | 2227 | + parseFunction( | | 2228 | + jsFunction, | | 2229 | + node, | | 2230 | + enclosingTypeName: typeName, | | 2231 | + isStaticMember: true, | | 2232 | + includeTypeNameForStatic: false | | 2233 | + ).map { | | 2234 | + type.staticMethods.append($0) | 2222 | 2235 | } | 2223 | 2236 | } else { | 2224 | 2237 | parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map { | @@ -2319,9 +2332,34 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2319 | 2332 | for member in members { | 2320 | 2333 | if let function = member.decl.as(FunctionDeclSyntax.self) { | 2321 | 2334 | if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes), | 2322 | | - let parsed = parseFunction(jsFunction, function, enclosingTypeName: typeName, isStaticMember: true) | | 2335 | + let parsed = parseFunction( | | 2336 | + jsFunction, | | 2337 | + function, | | 2338 | + enclosingTypeName: typeName, | | 2339 | + isStaticMember: true, | | 2340 | + includeTypeNameForStatic: !jsClassNames.contains(typeName) | | 2341 | + ) | 2323 | 2342 | { | 2324 | | - importedFunctions.append(parsed) | | 2343 | + if jsClassNames.contains(typeName) { | | 2344 | + if let index = importedTypes.firstIndex(where: { $0.name == typeName }) { | | 2345 | + let existing = importedTypes[index] | | 2346 | + importedTypes[index] = ImportedTypeSkeleton( | | 2347 | + name: existing.name, | | 2348 | + jsName: existing.jsName, | | 2349 | + from: existing.from, | | 2350 | + constructor: existing.constructor, | | 2351 | + methods: existing.methods, | | 2352 | + staticMethods: existing.staticMethods + [parsed], | | 2353 | + getters: existing.getters, | | 2354 | + setters: existing.setters, | | 2355 | + documentation: existing.documentation | | 2356 | + ) | | 2357 | + } else { | | 2358 | + staticMethodsByType[typeName, default: []].append(parsed) | | 2359 | + } | | 2360 | + } else { | | 2361 | + importedFunctions.append(parsed) | | 2362 | + } | 2325 | 2363 | } else if AttributeChecker.hasJSSetterAttribute(function.attributes) { | 2326 | 2364 | errors.append( | 2327 | 2365 | DiagnosticError( | @@ -2366,7 +2404,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2366 | 2404 | _ jsFunction: AttributeSyntax, | 2367 | 2405 | _ node: FunctionDeclSyntax, | 2368 | 2406 | enclosingTypeName: String?, | 2369 | | - isStaticMember: Bool | | 2407 | + isStaticMember: Bool, | | 2408 | + includeTypeNameForStatic: Bool = true | 2370 | 2409 | ) -> ImportedFunctionSkeleton? { | 2371 | 2410 | guard validateEffects(node.signature.effectSpecifiers, node: node, attributeName: "JSFunction") != nil | 2372 | 2411 | else { | @@ -2377,7 +2416,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { | 2377 | 2416 | let jsName = AttributeChecker.extractJSName(from: jsFunction) | 2378 | 2417 | let from = AttributeChecker.extractJSImportFrom(from: jsFunction) | 2379 | 2418 | let name: String | 2380 | | - if isStaticMember, let enclosingTypeName { | | 2419 | + if isStaticMember, includeTypeNameForStatic, let enclosingTypeName { | 2381 | 2420 | name = "\(enclosingTypeName)_\(baseName)" | 2382 | 2421 | } else { | 2383 | 2422 | name = baseName | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +41-1Lines changed: 41 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -2246,6 +2246,14 @@ extension BridgeJSLink { | 2246 | 2246 | ) | 2247 | 2247 | } | 2248 | 2248 |
| | 2249 | + func callStaticMethod(on objectExpr: String, name: String, returnType: BridgeType) throws -> String? { | | 2250 | + let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) | | 2251 | + return try call( | | 2252 | + calleeExpr: calleeExpr, | | 2253 | + returnType: returnType | | 2254 | + ) | | 2255 | + } | | 2256 | + | 2249 | 2257 | func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { | 2250 | 2258 | let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)" | 2251 | 2259 | let accessExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name) | @@ -2318,7 +2326,7 @@ extension BridgeJSLink { | 2318 | 2326 | return loweredValues.first | 2319 | 2327 | } | 2320 | 2328 |
| 2321 | | - private static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { | | 2329 | + static func propertyAccessExpr(objectExpr: String, propertyName: String) -> String { | 2322 | 2330 | if propertyName.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil | 2323 | 2331 | { | 2324 | 2332 | return "\(objectExpr).\(propertyName)" | @@ -3130,6 +3138,12 @@ extension BridgeJSLink { | 3130 | 3138 | importObjectBuilder.assignToImportObject(name: setterAbiName, function: js) | 3131 | 3139 | importObjectBuilder.appendDts(dts) | 3132 | 3140 | } | | 3141 | + for method in type.staticMethods { | | 3142 | + let abiName = method.abiName(context: type, operation: "static") | | 3143 | + let (js, dts) = try renderImportedStaticMethod(context: type, method: method) | | 3144 | + importObjectBuilder.assignToImportObject(name: abiName, function: js) | | 3145 | + importObjectBuilder.appendDts(dts) | | 3146 | + } | 3133 | 3147 | for method in type.methods { | 3134 | 3148 | let (js, dts) = try renderImportedMethod(context: type, method: method) | 3135 | 3149 | importObjectBuilder.assignToImportObject(name: method.abiName(context: type), function: js) | @@ -3207,6 +3221,32 @@ extension BridgeJSLink { | 3207 | 3221 | return (funcLines, []) | 3208 | 3222 | } | 3209 | 3223 |
| | 3224 | + func renderImportedStaticMethod( | | 3225 | + context: ImportedTypeSkeleton, | | 3226 | + method: ImportedFunctionSkeleton | | 3227 | + ) throws -> (js: [String], dts: [String]) { | | 3228 | + let thunkBuilder = ImportedThunkBuilder() | | 3229 | + for param in method.parameters { | | 3230 | + try thunkBuilder.liftParameter(param: param) | | 3231 | + } | | 3232 | + let importRootExpr = context.from == .global ? "globalThis" : "imports" | | 3233 | + let constructorExpr = ImportedThunkBuilder.propertyAccessExpr( | | 3234 | + objectExpr: importRootExpr, | | 3235 | + propertyName: context.jsName ?? context.name | | 3236 | + ) | | 3237 | + let returnExpr = try thunkBuilder.callStaticMethod( | | 3238 | + on: constructorExpr, | | 3239 | + name: method.jsName ?? method.name, | | 3240 | + returnType: method.returnType | | 3241 | + ) | | 3242 | + let funcLines = thunkBuilder.renderFunction( | | 3243 | + name: method.abiName(context: context, operation: "static"), | | 3244 | + returnExpr: returnExpr, | | 3245 | + returnType: method.returnType | | 3246 | + ) | | 3247 | + return (funcLines, []) | | 3248 | + } | | 3249 | + | 3210 | 3250 | func renderImportedMethod( | 3211 | 3251 | context: ImportedTypeSkeleton, | 3212 | 3252 | method: ImportedFunctionSkeleton | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +10-1Lines changed: 10 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -640,9 +640,14 @@ public struct ImportedFunctionSkeleton: Codable { | 640 | 640 | } | 641 | 641 |
| 642 | 642 | public func abiName(context: ImportedTypeSkeleton?) -> String { | | 643 | + return abiName(context: context, operation: nil) | | 644 | + } | | 645 | + | | 646 | + public func abiName(context: ImportedTypeSkeleton?, operation: String?) -> String { | 643 | 647 | return ABINameGenerator.generateImportedABIName( | 644 | 648 | baseName: name, | 645 | | - context: context | | 649 | + context: context, | | 650 | + operation: operation | 646 | 651 | ) | 647 | 652 | } | 648 | 653 | } | @@ -752,6 +757,8 @@ public struct ImportedTypeSkeleton: Codable { | 752 | 757 | public let from: JSImportFrom? | 753 | 758 | public let constructor: ImportedConstructorSkeleton? | 754 | 759 | public let methods: [ImportedFunctionSkeleton] | | 760 | + /// Static methods available on the JavaScript constructor. | | 761 | + public var staticMethods: [ImportedFunctionSkeleton] = [] | 755 | 762 | public let getters: [ImportedGetterSkeleton] | 756 | 763 | public let setters: [ImportedSetterSkeleton] | 757 | 764 | public let documentation: String? | @@ -762,6 +769,7 @@ public struct ImportedTypeSkeleton: Codable { | 762 | 769 | from: JSImportFrom? = nil, | 763 | 770 | constructor: ImportedConstructorSkeleton? = nil, | 764 | 771 | methods: [ImportedFunctionSkeleton], | | 772 | + staticMethods: [ImportedFunctionSkeleton] = [], | 765 | 773 | getters: [ImportedGetterSkeleton] = [], | 766 | 774 | setters: [ImportedSetterSkeleton] = [], | 767 | 775 | documentation: String? = nil | @@ -771,6 +779,7 @@ public struct ImportedTypeSkeleton: Codable { | 771 | 779 | self.from = from | 772 | 780 | self.constructor = constructor | 773 | 781 | self.methods = methods | | 782 | + self.staticMethods = staticMethods | 774 | 783 | self.getters = getters | 775 | 784 | self.setters = setters | 776 | 785 | self.documentation = documentation | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js +5-1Lines changed: 5 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -821,8 +821,12 @@ export class TypeProcessor { | 821 | 821 | const returnType = this.visitType(signature.getReturnType(), node); | 822 | 822 | const effects = this.renderEffects({ isAsync: false }); | 823 | 823 | const swiftMethodName = this.renderIdentifier(swiftName); | | 824 | + const isStatic = node.modifiers?.some( | | 825 | + (modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword | | 826 | + ) ?? false; | | 827 | + const staticKeyword = isStatic ? "static " : ""; | 824 | 828 |
| 825 | | - this.swiftLines.push(` ${annotation} func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`); | | 829 | + this.swiftLines.push(` ${annotation} ${staticKeyword}func ${swiftMethodName}(${params}) ${effects} -> ${returnType}`); | 826 | 830 | } | 827 | 831 |
| 828 | 832 | /** | |
Collapse file Copy file name to clipboard +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +extension StaticBox { | | 2 | + @JSFunction static func makeDefault() throws(JSException) -> StaticBox | | 3 | +} | | 4 | + | | 5 | +@JSClass struct StaticBox { | | 6 | + @JSFunction static func create(_ value: Double) throws(JSException) -> StaticBox | | 7 | + @JSFunction func value() throws(JSException) -> Double | | 8 | + @JSFunction static func value() throws(JSException) -> Double | | 9 | +} | | 10 | + | | 11 | +extension StaticBox { | | 12 | + @JSFunction(jsName: "with-dashes") static func dashed() throws(JSException) -> StaticBox | | 13 | +} |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.json +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 61 | 61 | "name" : "JSConsole", | 62 | 62 | "setters" : [ | 63 | 63 |
| | 64 | + ], | | 65 | + "staticMethods" : [ | | 66 | + | 64 | 67 | ] | 65 | 68 | } | 66 | 69 | ] | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.json +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 81 | 81 | "name" : "JSConsole", | 82 | 82 | "setters" : [ | 83 | 83 |
| | 84 | + ], | | 85 | + "staticMethods" : [ | | 86 | + | 84 | 87 | ] | 85 | 88 | }, | 86 | 89 | { | | 116 | 119 | "name" : "WebSocket", | 117 | 120 | "setters" : [ | 118 | 121 |
| | 122 | + ], | | 123 | + "staticMethods" : [ | | 124 | + | 119 | 125 | ] | 120 | 126 | } | 121 | 127 | ] | |
Collapse file Copy file name to clipboardExpand all lines: Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 186 | 186 | "name" : "Foo", | 187 | 187 | "setters" : [ | 188 | 188 |
| | 189 | + ], | | 190 | + "staticMethods" : [ | | 191 | + | 189 | 192 | ] | 190 | 193 | } | 191 | 194 | ] | |
|