Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ let package = Package(
exclude: [
"bridge-js.config.json",
"bridge-js.d.ts",
"bridge-js.global.d.ts",
"Generated/JavaScript",
],
swiftSettings: [
Expand Down
47 changes: 41 additions & 6 deletions Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
private struct CurrentType {
let name: String
let jsName: String?
let from: JSImportFrom?
var constructor: ImportedConstructorSkeleton?
var methods: [ImportedFunctionSkeleton]
var getters: [ImportedGetterSkeleton]
Expand Down Expand Up @@ -1872,6 +1873,22 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
}
return nil
}

/// Extracts the `from` argument value from an attribute, if present.
static func extractJSImportFrom(from attribute: AttributeSyntax) -> JSImportFrom? {
guard let arguments = attribute.arguments?.as(LabeledExprListSyntax.self) else {
return nil
}
for argument in arguments {
guard argument.label?.text == "from" else { continue }

// Accept `.global`, `JSImportFrom.global`, etc.
let description = argument.expression.trimmedDescription
let caseName = description.split(separator: ".").last.map(String.init) ?? description
return JSImportFrom(rawValue: caseName)
}
return nil
}
}

// MARK: - Validation Helpers
Expand Down Expand Up @@ -1996,14 +2013,23 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {

private func enterJSClass(_ typeName: String) {
stateStack.append(.jsClassBody(name: typeName))
currentType = CurrentType(name: typeName, jsName: nil, constructor: nil, methods: [], getters: [], setters: [])
currentType = CurrentType(
name: typeName,
jsName: nil,
from: nil,
constructor: nil,
methods: [],
getters: [],
setters: []
)
}

private func enterJSClass(_ typeName: String, jsName: String?) {
private func enterJSClass(_ typeName: String, jsName: String?, from: JSImportFrom?) {
stateStack.append(.jsClassBody(name: typeName))
currentType = CurrentType(
name: typeName,
jsName: jsName,
from: from,
constructor: nil,
methods: [],
getters: [],
Expand All @@ -2017,6 +2043,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
ImportedTypeSkeleton(
name: type.name,
jsName: type.jsName,
from: type.from,
constructor: type.constructor,
methods: type.methods,
getters: type.getters,
Expand All @@ -2031,8 +2058,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {

override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
if AttributeChecker.hasJSClassAttribute(node.attributes) {
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
enterJSClass(node.name.text, jsName: jsName)
let attribute = AttributeChecker.firstJSClassAttribute(node.attributes)
let jsName = attribute.flatMap(AttributeChecker.extractJSName)
let from = attribute.flatMap(AttributeChecker.extractJSImportFrom)
enterJSClass(node.name.text, jsName: jsName, from: from)
}
return .visitChildren
}
Expand All @@ -2045,8 +2074,10 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {

override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
if AttributeChecker.hasJSClassAttribute(node.attributes) {
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
enterJSClass(node.name.text, jsName: jsName)
let attribute = AttributeChecker.firstJSClassAttribute(node.attributes)
let jsName = attribute.flatMap(AttributeChecker.extractJSName)
let from = attribute.flatMap(AttributeChecker.extractJSImportFrom)
enterJSClass(node.name.text, jsName: jsName, from: from)
}
return .visitChildren
}
Expand Down Expand Up @@ -2269,6 +2300,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, let enclosingTypeName {
name = "\(enclosingTypeName)_\(baseName)"
Expand All @@ -2289,6 +2321,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
return ImportedFunctionSkeleton(
name: name,
jsName: jsName,
from: from,
parameters: parameters,
returnType: returnType,
documentation: nil
Expand Down Expand Up @@ -2322,9 +2355,11 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
}
let propertyName = SwiftToSkeleton.normalizeIdentifier(identifier.identifier.text)
let jsName = AttributeChecker.extractJSName(from: jsGetter)
let from = AttributeChecker.extractJSImportFrom(from: jsGetter)
return ImportedGetterSkeleton(
name: propertyName,
jsName: jsName,
from: from,
type: propertyType,
documentation: nil,
functionName: nil
Expand Down
98 changes: 70 additions & 28 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public struct BridgeJSLink {
var topLevelDtsTypeLines: [String] = []
var importObjectBuilders: [ImportObjectBuilder] = []
var enumStaticAssignments: [String] = []
var needsImportsObject: Bool = false
}

private func collectLinkData() throws -> LinkData {
Expand Down Expand Up @@ -173,12 +174,21 @@ public struct BridgeJSLink {
let importObjectBuilder = ImportObjectBuilder(moduleName: unified.moduleName)
for fileSkeleton in imported.children {
for getter in fileSkeleton.globalGetters {
if getter.from == nil {
data.needsImportsObject = true
}
try renderImportedGlobalGetter(importObjectBuilder: importObjectBuilder, getter: getter)
}
for function in fileSkeleton.functions {
if function.from == nil {
data.needsImportsObject = true
}
try renderImportedFunction(importObjectBuilder: importObjectBuilder, function: function)
}
for type in fileSkeleton.types {
if type.constructor != nil, type.from == nil {
data.needsImportsObject = true
}
try renderImportedType(importObjectBuilder: importObjectBuilder, type: type)
}
}
Expand Down Expand Up @@ -294,7 +304,7 @@ public struct BridgeJSLink {
}
}

private func generateAddImports() -> CodeFragmentPrinter {
private func generateAddImports(needsImportsObject: Bool) -> CodeFragmentPrinter {
let printer = CodeFragmentPrinter()
let allStructs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 }
printer.write("return {")
Expand All @@ -311,7 +321,7 @@ public struct BridgeJSLink {
"bjs = {};",
"importObject[\"bjs\"] = bjs;",
])
if skeletons.contains(where: { $0.imported != nil }) {
if needsImportsObject {
printer.write(lines: [
"const imports = options.getImports(importsContext);"
])
Expand Down Expand Up @@ -1043,7 +1053,7 @@ public struct BridgeJSLink {
printer.write(lines: structPrinter.lines)
}
printer.nextLine()
printer.write(contentsOf: generateAddImports())
printer.write(contentsOf: generateAddImports(needsImportsObject: data.needsImportsObject))
}
printer.indent()

Expand Down Expand Up @@ -2176,11 +2186,15 @@ extension BridgeJSLink {
return printer.lines
}

func call(name: String, returnType: BridgeType) throws -> String? {
let calleeExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: name)
func call(name: String, fromObjectExpr: String, returnType: BridgeType) throws -> String? {
let calleeExpr = Self.propertyAccessExpr(objectExpr: fromObjectExpr, propertyName: name)
return try self.call(calleeExpr: calleeExpr, returnType: returnType)
}

func call(name: String, returnType: BridgeType) throws -> String? {
return try call(name: name, fromObjectExpr: "imports", returnType: returnType)
}

private func call(calleeExpr: String, returnType: BridgeType) throws -> String? {
let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))"
return try self.call(callExpr: callExpr, returnType: returnType)
Expand All @@ -2204,14 +2218,18 @@ extension BridgeJSLink {
)
}

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

func callConstructor(jsName: String, swiftTypeName: String) throws -> String? {
return try callConstructor(jsName: jsName, swiftTypeName: swiftTypeName, fromObjectExpr: "imports")
}

func callMethod(name: String, returnType: BridgeType) throws -> String? {
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
Expand Down Expand Up @@ -2253,14 +2271,13 @@ extension BridgeJSLink {
body.write("\(call);")
}

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

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

let returnExpr: String?
if loweringFragment.parameters.count == 0 {
Expand All @@ -2279,6 +2296,10 @@ extension BridgeJSLink {
)
}

func getImportProperty(name: String, returnType: BridgeType) throws -> String? {
return try getImportProperty(name: name, fromObjectExpr: "imports", returnType: returnType)
}

private func lowerReturnValue(
returnType: BridgeType,
returnExpr: String?,
Expand Down Expand Up @@ -3013,18 +3034,25 @@ extension BridgeJSLink {
try thunkBuilder.liftParameter(param: param)
}
let jsName = function.jsName ?? function.name
let returnExpr = try thunkBuilder.call(name: jsName, returnType: function.returnType)
let importRootExpr = function.from == .global ? "globalThis" : "imports"
let returnExpr = try thunkBuilder.call(
name: jsName,
fromObjectExpr: importRootExpr,
returnType: function.returnType
)
let funcLines = thunkBuilder.renderFunction(
name: function.abiName(context: nil),
returnExpr: returnExpr,
returnType: function.returnType
)
let effects = Effects(isAsync: false, isThrows: false)
importObjectBuilder.appendDts(
[
"\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
]
)
if function.from == nil {
importObjectBuilder.appendDts(
[
"\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
]
)
}
importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines)
}

Expand All @@ -3034,14 +3062,21 @@ extension BridgeJSLink {
) throws {
let thunkBuilder = ImportedThunkBuilder()
let jsName = getter.jsName ?? getter.name
let returnExpr = try thunkBuilder.getImportProperty(name: jsName, returnType: getter.type)
let importRootExpr = getter.from == .global ? "globalThis" : "imports"
let returnExpr = try thunkBuilder.getImportProperty(
name: jsName,
fromObjectExpr: importRootExpr,
returnType: getter.type
)
let abiName = getter.abiName(context: nil)
let funcLines = thunkBuilder.renderFunction(
name: abiName,
returnExpr: returnExpr,
returnType: getter.type
)
importObjectBuilder.appendDts(["readonly \(renderTSPropertyName(jsName)): \(getter.type.tsType);"])
if getter.from == nil {
importObjectBuilder.appendDts(["readonly \(renderTSPropertyName(jsName)): \(getter.type.tsType);"])
}
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)
}

Expand Down Expand Up @@ -3105,7 +3140,12 @@ extension BridgeJSLink {
try thunkBuilder.liftParameter(param: param)
}
let returnType = BridgeType.jsObject(type.name)
let returnExpr = try thunkBuilder.callConstructor(jsName: type.jsName ?? type.name, swiftTypeName: type.name)
let importRootExpr = type.from == .global ? "globalThis" : "imports"
let returnExpr = try thunkBuilder.callConstructor(
jsName: type.jsName ?? type.name,
swiftTypeName: type.name,
fromObjectExpr: importRootExpr
)
let abiName = constructor.abiName(context: type)
let funcLines = thunkBuilder.renderFunction(
name: abiName,
Expand All @@ -3114,16 +3154,18 @@ extension BridgeJSLink {
)
importObjectBuilder.assignToImportObject(name: abiName, function: funcLines)

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("}")
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)
importObjectBuilder.appendDts(dtsPrinter.lines)
}
}

func renderImportedGetter(
Expand Down
Loading