Skip to content

Commit 28d3db1

Browse files
authored
Merge pull request #530 from swiftwasm/katei/34cd-add-jsgetter-fro
BridgeJS: allow imports from globalThis
2 parents 7b1ee44 + 773254a commit 28d3db1

File tree

21 files changed

+1221
-71
lines changed

21 files changed

+1221
-71
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ let package = Package(
181181
exclude: [
182182
"bridge-js.config.json",
183183
"bridge-js.d.ts",
184+
"bridge-js.global.d.ts",
184185
"Generated/JavaScript",
185186
],
186187
swiftSettings: [

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
17981798
private struct CurrentType {
17991799
let name: String
18001800
let jsName: String?
1801+
let from: JSImportFrom?
18011802
var constructor: ImportedConstructorSkeleton?
18021803
var methods: [ImportedFunctionSkeleton]
18031804
var getters: [ImportedGetterSkeleton]
@@ -1872,6 +1873,22 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
18721873
}
18731874
return nil
18741875
}
1876+
1877+
/// Extracts the `from` argument value from an attribute, if present.
1878+
static func extractJSImportFrom(from attribute: AttributeSyntax) -> JSImportFrom? {
1879+
guard let arguments = attribute.arguments?.as(LabeledExprListSyntax.self) else {
1880+
return nil
1881+
}
1882+
for argument in arguments {
1883+
guard argument.label?.text == "from" else { continue }
1884+
1885+
// Accept `.global`, `JSImportFrom.global`, etc.
1886+
let description = argument.expression.trimmedDescription
1887+
let caseName = description.split(separator: ".").last.map(String.init) ?? description
1888+
return JSImportFrom(rawValue: caseName)
1889+
}
1890+
return nil
1891+
}
18751892
}
18761893

18771894
// MARK: - Validation Helpers
@@ -1996,14 +2013,23 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
19962013

19972014
private func enterJSClass(_ typeName: String) {
19982015
stateStack.append(.jsClassBody(name: typeName))
1999-
currentType = CurrentType(name: typeName, jsName: nil, constructor: nil, methods: [], getters: [], setters: [])
2016+
currentType = CurrentType(
2017+
name: typeName,
2018+
jsName: nil,
2019+
from: nil,
2020+
constructor: nil,
2021+
methods: [],
2022+
getters: [],
2023+
setters: []
2024+
)
20002025
}
20012026

2002-
private func enterJSClass(_ typeName: String, jsName: String?) {
2027+
private func enterJSClass(_ typeName: String, jsName: String?, from: JSImportFrom?) {
20032028
stateStack.append(.jsClassBody(name: typeName))
20042029
currentType = CurrentType(
20052030
name: typeName,
20062031
jsName: jsName,
2032+
from: from,
20072033
constructor: nil,
20082034
methods: [],
20092035
getters: [],
@@ -2017,6 +2043,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
20172043
ImportedTypeSkeleton(
20182044
name: type.name,
20192045
jsName: type.jsName,
2046+
from: type.from,
20202047
constructor: type.constructor,
20212048
methods: type.methods,
20222049
getters: type.getters,
@@ -2031,8 +2058,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
20312058

20322059
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
20332060
if AttributeChecker.hasJSClassAttribute(node.attributes) {
2034-
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
2035-
enterJSClass(node.name.text, jsName: jsName)
2061+
let attribute = AttributeChecker.firstJSClassAttribute(node.attributes)
2062+
let jsName = attribute.flatMap(AttributeChecker.extractJSName)
2063+
let from = attribute.flatMap(AttributeChecker.extractJSImportFrom)
2064+
enterJSClass(node.name.text, jsName: jsName, from: from)
20362065
}
20372066
return .visitChildren
20382067
}
@@ -2045,8 +2074,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
20452074

20462075
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
20472076
if AttributeChecker.hasJSClassAttribute(node.attributes) {
2048-
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
2049-
enterJSClass(node.name.text, jsName: jsName)
2077+
let attribute = AttributeChecker.firstJSClassAttribute(node.attributes)
2078+
let jsName = attribute.flatMap(AttributeChecker.extractJSName)
2079+
let from = attribute.flatMap(AttributeChecker.extractJSImportFrom)
2080+
enterJSClass(node.name.text, jsName: jsName, from: from)
20502081
}
20512082
return .visitChildren
20522083
}
@@ -2269,6 +2300,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
22692300

22702301
let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text)
22712302
let jsName = AttributeChecker.extractJSName(from: jsFunction)
2303+
let from = AttributeChecker.extractJSImportFrom(from: jsFunction)
22722304
let name: String
22732305
if isStaticMember, let enclosingTypeName {
22742306
name = "\(enclosingTypeName)_\(baseName)"
@@ -2289,6 +2321,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
22892321
return ImportedFunctionSkeleton(
22902322
name: name,
22912323
jsName: jsName,
2324+
from: from,
22922325
parameters: parameters,
22932326
returnType: returnType,
22942327
documentation: nil
@@ -2322,9 +2355,11 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
23222355
}
23232356
let propertyName = SwiftToSkeleton.normalizeIdentifier(identifier.identifier.text)
23242357
let jsName = AttributeChecker.extractJSName(from: jsGetter)
2358+
let from = AttributeChecker.extractJSImportFrom(from: jsGetter)
23252359
return ImportedGetterSkeleton(
23262360
name: propertyName,
23272361
jsName: jsName,
2362+
from: from,
23282363
type: propertyType,
23292364
documentation: nil,
23302365
functionName: nil

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public struct BridgeJSLink {
8080
var topLevelDtsTypeLines: [String] = []
8181
var importObjectBuilders: [ImportObjectBuilder] = []
8282
var enumStaticAssignments: [String] = []
83+
var needsImportsObject: Bool = false
8384
}
8485

8586
private func collectLinkData() throws -> LinkData {
@@ -173,12 +174,21 @@ public struct BridgeJSLink {
173174
let importObjectBuilder = ImportObjectBuilder(moduleName: unified.moduleName)
174175
for fileSkeleton in imported.children {
175176
for getter in fileSkeleton.globalGetters {
177+
if getter.from == nil {
178+
data.needsImportsObject = true
179+
}
176180
try renderImportedGlobalGetter(importObjectBuilder: importObjectBuilder, getter: getter)
177181
}
178182
for function in fileSkeleton.functions {
183+
if function.from == nil {
184+
data.needsImportsObject = true
185+
}
179186
try renderImportedFunction(importObjectBuilder: importObjectBuilder, function: function)
180187
}
181188
for type in fileSkeleton.types {
189+
if type.constructor != nil, type.from == nil {
190+
data.needsImportsObject = true
191+
}
182192
try renderImportedType(importObjectBuilder: importObjectBuilder, type: type)
183193
}
184194
}
@@ -294,7 +304,7 @@ public struct BridgeJSLink {
294304
}
295305
}
296306

297-
private func generateAddImports() -> CodeFragmentPrinter {
307+
private func generateAddImports(needsImportsObject: Bool) -> CodeFragmentPrinter {
298308
let printer = CodeFragmentPrinter()
299309
let allStructs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 }
300310
printer.write("return {")
@@ -311,7 +321,7 @@ public struct BridgeJSLink {
311321
"bjs = {};",
312322
"importObject[\"bjs\"] = bjs;",
313323
])
314-
if skeletons.contains(where: { $0.imported != nil }) {
324+
if needsImportsObject {
315325
printer.write(lines: [
316326
"const imports = options.getImports(importsContext);"
317327
])
@@ -1043,7 +1053,7 @@ public struct BridgeJSLink {
10431053
printer.write(lines: structPrinter.lines)
10441054
}
10451055
printer.nextLine()
1046-
printer.write(contentsOf: generateAddImports())
1056+
printer.write(contentsOf: generateAddImports(needsImportsObject: data.needsImportsObject))
10471057
}
10481058
printer.indent()
10491059

@@ -2176,11 +2186,15 @@ extension BridgeJSLink {
21762186
return printer.lines
21772187
}
21782188

2179-
func call(name: String, returnType: BridgeType) throws -> String? {
2180-
let calleeExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: name)
2189+
func call(name: String, fromObjectExpr: String, returnType: BridgeType) throws -> String? {
2190+
let calleeExpr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: name)
21812191
return try self.call(calleeExpr: calleeExpr, returnType: returnType)
21822192
}
21832193

2194+
func call(name: String, returnType: BridgeType) throws -> String? {
2195+
return try call(name: name, fromObjectExpr: "imports", returnType: returnType)
2196+
}
2197+
21842198
private func call(calleeExpr: String, returnType: BridgeType) throws -> String? {
21852199
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
21862200
return try self.call(callExpr: callExpr, returnType: returnType)
@@ -2204,14 +2218,18 @@ extension BridgeJSLink {
22042218
)
22052219
}
22062220

2207-
func callConstructor(jsName: String, swiftTypeName: String) throws -> String? {
2208-
let ctorExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: jsName)
2221+
func callConstructor(jsName: String, swiftTypeName: String, fromObjectExpr: String) throws -> String? {
2222+
let ctorExpr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: jsName)
22092223
let call = "new \(ctorExpr)(\(parameterForwardings.joined(separator: ", ")))"
22102224
let type: BridgeType = .jsObject(swiftTypeName)
22112225
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type, context: context)
22122226
return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment)
22132227
}
22142228

2229+
func callConstructor(jsName: String, swiftTypeName: String) throws -> String? {
2230+
return try callConstructor(jsName: jsName, swiftTypeName: swiftTypeName, fromObjectExpr: "imports")
2231+
}
2232+
22152233
func callMethod(name: String, returnType: BridgeType) throws -> String? {
22162234
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
22172235
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
@@ -2253,14 +2271,13 @@ extension BridgeJSLink {
22532271
body.write("\(call);")
22542272
}
22552273

2256-
func getImportProperty(name: String, returnType: BridgeType) throws -> String? {
2274+
func getImportProperty(name: String, fromObjectExpr: String, returnType: BridgeType) throws -> String? {
22572275
if returnType == .void {
22582276
throw BridgeJSLinkError(message: "Void is not supported for imported JS properties")
22592277
}
22602278

22612279
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType, context: context)
2262-
let escapedName = BridgeJSLink.escapeForJavaScriptStringLiteral(name)
2263-
let expr = "imports[\"\(escapedName)\"]"
2280+
let expr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: name)
22642281

22652282
let returnExpr: String?
22662283
if loweringFragment.parameters.count == 0 {
@@ -2279,6 +2296,10 @@ extension BridgeJSLink {
22792296
)
22802297
}
22812298

2299+
func getImportProperty(name: String, returnType: BridgeType) throws -> String? {
2300+
return try getImportProperty(name: name, fromObjectExpr: "imports", returnType: returnType)
2301+
}
2302+
22822303
private func lowerReturnValue(
22832304
returnType: BridgeType,
22842305
returnExpr: String?,
@@ -3013,18 +3034,25 @@ extension BridgeJSLink {
30133034
try thunkBuilder.liftParameter(param: param)
30143035
}
30153036
let jsName = function.jsName ?? function.name
3016-
let returnExpr = try thunkBuilder.call(name: jsName, returnType: function.returnType)
3037+
let importRootExpr = function.from == .global ? "globalThis" : "imports"
3038+
let returnExpr = try thunkBuilder.call(
3039+
name: jsName,
3040+
fromObjectExpr: importRootExpr,
3041+
returnType: function.returnType
3042+
)
30173043
let funcLines = thunkBuilder.renderFunction(
30183044
name: function.abiName(context: nil),
30193045
returnExpr: returnExpr,
30203046
returnType: function.returnType
30213047
)
30223048
let effects = Effects(isAsync: false, isThrows: false)
3023-
importObjectBuilder.appendDts(
3024-
[
3025-
"\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
3026-
]
3027-
)
3049+
if function.from == nil {
3050+
importObjectBuilder.appendDts(
3051+
[
3052+
"\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
3053+
]
3054+
)
3055+
}
30283056
importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines)
30293057
}
30303058

@@ -3034,14 +3062,21 @@ extension BridgeJSLink {
30343062
) throws {
30353063
let thunkBuilder = ImportedThunkBuilder()
30363064
let jsName = getter.jsName ?? getter.name
3037-
let returnExpr = try thunkBuilder.getImportProperty(name: jsName, returnType: getter.type)
3065+
let importRootExpr = getter.from == .global ? "globalThis" : "imports"
3066+
let returnExpr = try thunkBuilder.getImportProperty(
3067+
name: jsName,
3068+
fromObjectExpr: importRootExpr,
3069+
returnType: getter.type
3070+
)
30383071
let abiName = getter.abiName(context: nil)
30393072
let funcLines = thunkBuilder.renderFunction(
30403073
name: abiName,
30413074
returnExpr: returnExpr,
30423075
returnType: getter.type
30433076
)
3044-
importObjectBuilder.appendDts(["readonly \(renderTSPropertyName(jsName)): \(getter.type.tsType);"])
3077+
if getter.from == nil {
3078+
importObjectBuilder.appendDts(["readonly \(renderTSPropertyName(jsName)): \(getter.type.tsType);"])
3079+
}
30453080
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)
30463081
}
30473082

@@ -3105,7 +3140,12 @@ extension BridgeJSLink {
31053140
try thunkBuilder.liftParameter(param: param)
31063141
}
31073142
let returnType = BridgeType.jsObject(type.name)
3108-
let returnExpr = try thunkBuilder.callConstructor(jsName: type.jsName ?? type.name, swiftTypeName: type.name)
3143+
let importRootExpr = type.from == .global ? "globalThis" : "imports"
3144+
let returnExpr = try thunkBuilder.callConstructor(
3145+
jsName: type.jsName ?? type.name,
3146+
swiftTypeName: type.name,
3147+
fromObjectExpr: importRootExpr
3148+
)
31093149
let abiName = constructor.abiName(context: type)
31103150
let funcLines = thunkBuilder.renderFunction(
31113151
name: abiName,
@@ -3114,16 +3154,18 @@ extension BridgeJSLink {
31143154
)
31153155
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)
31163156

3117-
let dtsPrinter = CodeFragmentPrinter()
3118-
dtsPrinter.write("\(type.name): {")
3119-
dtsPrinter.indent {
3120-
dtsPrinter.write(
3121-
"new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));"
3122-
)
3123-
}
3124-
dtsPrinter.write("}")
3157+
if type.from == nil {
3158+
let dtsPrinter = CodeFragmentPrinter()
3159+
dtsPrinter.write("\(type.name): {")
3160+
dtsPrinter.indent {
3161+
dtsPrinter.write(
3162+
"new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));"
3163+
)
3164+
}
3165+
dtsPrinter.write("}")
31253166

3126-
importObjectBuilder.appendDts(dtsPrinter.lines)
3167+
importObjectBuilder.appendDts(dtsPrinter.lines)
3168+
}
31273169
}
31283170

31293171
func renderImportedGetter(

0 commit comments

Comments
 (0)