Skip to content

Commit 0497d26

Browse files
Added jsName support to @JSFunction and @JSClass, end-to-end.
- Updated macro APIs: `Sources/JavaScriptKit/Macros.swift` - Plumbed `jsName` through the imported skeleton + Swift parser: `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift`, `Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift` - Updated JS glue + generated `.d.ts` to use the JS names (dot access when possible, bracket access when needed): `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` - Updated TS→Swift generator to emit `@JSClass(jsName: ...)` / `@JSFunction(jsName: ...)` when it has to sanitize names: `Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js` - Added coverage for a renamed class + quoted method name: `Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts` (snapshots refreshed; `swift test --package-path ./Plugins/BridgeJS` passes)
1 parent 7906050 commit 0497d26

File tree

10 files changed

+260
-40
lines changed

10 files changed

+260
-40
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
17431743
// Current type being collected (when in jsClassBody state)
17441744
private struct CurrentType {
17451745
let name: String
1746+
let jsName: String?
17461747
var constructor: ImportedConstructorSkeleton?
17471748
var methods: [ImportedFunctionSkeleton]
17481749
var getters: [ImportedGetterSkeleton]
@@ -1758,6 +1759,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
17581759
hasAttribute(attributes, name: "JSFunction")
17591760
}
17601761

1762+
static func firstJSFunctionAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1763+
attributes?.first { attribute in
1764+
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSFunction"
1765+
}?.as(AttributeSyntax.self)
1766+
}
1767+
17611768
static func hasJSGetterAttribute(_ attributes: AttributeListSyntax?) -> Bool {
17621769
hasAttribute(attributes, name: "JSGetter")
17631770
}
@@ -1782,6 +1789,12 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
17821789
hasAttribute(attributes, name: "JSClass")
17831790
}
17841791

1792+
static func firstJSClassAttribute(_ attributes: AttributeListSyntax?) -> AttributeSyntax? {
1793+
attributes?.first { attribute in
1794+
attribute.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JSClass"
1795+
}?.as(AttributeSyntax.self)
1796+
}
1797+
17851798
static func hasAttribute(_ attributes: AttributeListSyntax?, name: String) -> Bool {
17861799
guard let attributes else { return false }
17871800
return attributes.contains { attribute in
@@ -1929,14 +1942,20 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
19291942

19301943
private func enterJSClass(_ typeName: String) {
19311944
stateStack.append(.jsClassBody(name: typeName))
1932-
currentType = CurrentType(name: typeName, constructor: nil, methods: [], getters: [], setters: [])
1945+
currentType = CurrentType(name: typeName, jsName: nil, constructor: nil, methods: [], getters: [], setters: [])
1946+
}
1947+
1948+
private func enterJSClass(_ typeName: String, jsName: String?) {
1949+
stateStack.append(.jsClassBody(name: typeName))
1950+
currentType = CurrentType(name: typeName, jsName: jsName, constructor: nil, methods: [], getters: [], setters: [])
19331951
}
19341952

19351953
private func exitJSClass() {
19361954
if case .jsClassBody(let typeName) = state, let type = currentType, type.name == typeName {
19371955
importedTypes.append(
19381956
ImportedTypeSkeleton(
19391957
name: type.name,
1958+
jsName: type.jsName,
19401959
constructor: type.constructor,
19411960
methods: type.methods,
19421961
getters: type.getters,
@@ -1951,7 +1970,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
19511970

19521971
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
19531972
if AttributeChecker.hasJSClassAttribute(node.attributes) {
1954-
enterJSClass(node.name.text)
1973+
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
1974+
enterJSClass(node.name.text, jsName: jsName)
19551975
}
19561976
return .visitChildren
19571977
}
@@ -1964,7 +1984,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
19641984

19651985
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
19661986
if AttributeChecker.hasJSClassAttribute(node.attributes) {
1967-
enterJSClass(node.name.text)
1987+
let jsName = AttributeChecker.firstJSClassAttribute(node.attributes).flatMap(AttributeChecker.extractJSName)
1988+
enterJSClass(node.name.text, jsName: jsName)
19681989
}
19691990
return .visitChildren
19701991
}
@@ -2002,8 +2023,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
20022023
}
20032024

20042025
private func handleTopLevelFunction(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
2005-
if AttributeChecker.hasJSFunctionAttribute(node.attributes),
2006-
let function = parseFunction(node, enclosingTypeName: nil, isStaticMember: true)
2026+
if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes),
2027+
let function = parseFunction(jsFunction, node, enclosingTypeName: nil, isStaticMember: true)
20072028
{
20082029
importedFunctions.append(function)
20092030
return .skipChildren
@@ -2027,13 +2048,13 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
20272048
isStaticMember: Bool,
20282049
type: inout CurrentType
20292050
) -> Bool {
2030-
if AttributeChecker.hasJSFunctionAttribute(node.attributes) {
2051+
if let jsFunction = AttributeChecker.firstJSFunctionAttribute(node.attributes) {
20312052
if isStaticMember {
2032-
parseFunction(node, enclosingTypeName: typeName, isStaticMember: true).map {
2053+
parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: true).map {
20332054
importedFunctions.append($0)
20342055
}
20352056
} else {
2036-
parseFunction(node, enclosingTypeName: typeName, isStaticMember: false).map {
2057+
parseFunction(jsFunction, node, enclosingTypeName: typeName, isStaticMember: false).map {
20372058
type.methods.append($0)
20382059
}
20392060
}
@@ -2130,8 +2151,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
21302151
private func collectStaticMembers(in members: MemberBlockItemListSyntax, typeName: String) {
21312152
for member in members {
21322153
if let function = member.decl.as(FunctionDeclSyntax.self) {
2133-
if AttributeChecker.hasJSFunctionAttribute(function.attributes),
2134-
let parsed = parseFunction(function, enclosingTypeName: typeName, isStaticMember: true)
2154+
if let jsFunction = AttributeChecker.firstJSFunctionAttribute(function.attributes),
2155+
let parsed = parseFunction(jsFunction, function, enclosingTypeName: typeName, isStaticMember: true)
21352156
{
21362157
importedFunctions.append(parsed)
21372158
} else if AttributeChecker.hasJSSetterAttribute(function.attributes) {
@@ -2175,6 +2196,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
21752196
}
21762197

21772198
private func parseFunction(
2199+
_ jsFunction: AttributeSyntax,
21782200
_ node: FunctionDeclSyntax,
21792201
enclosingTypeName: String?,
21802202
isStaticMember: Bool
@@ -2185,6 +2207,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
21852207
}
21862208

21872209
let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text)
2210+
let jsName = AttributeChecker.extractJSName(from: jsFunction)
21882211
let name: String
21892212
if isStaticMember, let enclosingTypeName {
21902213
name = "\(enclosingTypeName)_\(baseName)"
@@ -2204,6 +2227,7 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor {
22042227
}
22052228
return ImportedFunctionSkeleton(
22062229
name: name,
2230+
jsName: jsName,
22072231
parameters: parameters,
22082232
returnType: returnType,
22092233
documentation: nil

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,8 +1194,9 @@ public struct BridgeJSLink {
11941194

11951195
// Add methods
11961196
for method in type.methods {
1197+
let methodName = method.jsName ?? method.name
11971198
let methodSignature =
1198-
"\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));"
1199+
"\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));"
11991200
printer.write(methodSignature)
12001201
}
12011202

@@ -2127,7 +2128,8 @@ extension BridgeJSLink {
21272128
}
21282129

21292130
func call(name: String, returnType: BridgeType) throws -> String? {
2130-
return try self.call(calleeExpr: "imports.\(name)", returnType: returnType)
2131+
let calleeExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: name)
2132+
return try self.call(calleeExpr: calleeExpr, returnType: returnType)
21312133
}
21322134

21332135
private func call(calleeExpr: String, returnType: BridgeType) throws -> String? {
@@ -2153,16 +2155,19 @@ extension BridgeJSLink {
21532155
)
21542156
}
21552157

2156-
func callConstructor(name: String) throws -> String? {
2157-
let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))"
2158-
let type: BridgeType = .jsObject(name)
2158+
func callConstructor(jsName: String, swiftTypeName: String) throws -> String? {
2159+
let ctorExpr = Self.propertyAccessExpr(objectExpr: "imports", propertyName: jsName)
2160+
let call = "new \(ctorExpr)(\(parameterForwardings.joined(separator: ", ")))"
2161+
let type: BridgeType = .jsObject(swiftTypeName)
21592162
let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type, context: context)
21602163
return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment)
21612164
}
21622165

21632166
func callMethod(name: String, returnType: BridgeType) throws -> String? {
2167+
let objectExpr = "\(JSGlueVariableScope.reservedSwift).memory.getObject(self)"
2168+
let calleeExpr = Self.propertyAccessExpr(objectExpr: objectExpr, propertyName: name)
21642169
return try call(
2165-
calleeExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)",
2170+
calleeExpr: calleeExpr,
21662171
returnType: returnType
21672172
)
21682173
}
@@ -2957,7 +2962,8 @@ extension BridgeJSLink {
29572962
for param in function.parameters {
29582963
try thunkBuilder.liftParameter(param: param)
29592964
}
2960-
let returnExpr = try thunkBuilder.call(name: function.name, returnType: function.returnType)
2965+
let jsName = function.jsName ?? function.name
2966+
let returnExpr = try thunkBuilder.call(name: jsName, returnType: function.returnType)
29612967
let funcLines = thunkBuilder.renderFunction(
29622968
name: function.abiName(context: nil),
29632969
returnExpr: returnExpr,
@@ -2966,7 +2972,7 @@ extension BridgeJSLink {
29662972
let effects = Effects(isAsync: false, isThrows: false)
29672973
importObjectBuilder.appendDts(
29682974
[
2969-
"\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
2975+
"\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));"
29702976
]
29712977
)
29722978
importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines)
@@ -3049,7 +3055,7 @@ extension BridgeJSLink {
30493055
try thunkBuilder.liftParameter(param: param)
30503056
}
30513057
let returnType = BridgeType.jsObject(type.name)
3052-
let returnExpr = try thunkBuilder.callConstructor(name: type.name)
3058+
let returnExpr = try thunkBuilder.callConstructor(jsName: type.jsName ?? type.name, swiftTypeName: type.name)
30533059
let abiName = constructor.abiName(context: type)
30543060
let funcLines = thunkBuilder.renderFunction(
30553061
name: abiName,
@@ -3111,7 +3117,7 @@ extension BridgeJSLink {
31113117
for param in method.parameters {
31123118
try thunkBuilder.liftParameter(param: param)
31133119
}
3114-
let returnExpr = try thunkBuilder.callMethod(name: method.name, returnType: method.returnType)
3120+
let returnExpr = try thunkBuilder.callMethod(name: method.jsName ?? method.name, returnType: method.returnType)
31153121
let funcLines = thunkBuilder.renderFunction(
31163122
name: method.abiName(context: context),
31173123
returnExpr: returnExpr,

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,12 +590,21 @@ public struct ExportedSkeleton: Codable {
590590

591591
public struct ImportedFunctionSkeleton: Codable {
592592
public let name: String
593+
/// The JavaScript function/method name to call, if different from `name`.
594+
public let jsName: String?
593595
public let parameters: [Parameter]
594596
public let returnType: BridgeType
595597
public let documentation: String?
596598

597-
public init(name: String, parameters: [Parameter], returnType: BridgeType, documentation: String? = nil) {
599+
public init(
600+
name: String,
601+
jsName: String? = nil,
602+
parameters: [Parameter],
603+
returnType: BridgeType,
604+
documentation: String? = nil
605+
) {
598606
self.name = name
607+
self.jsName = jsName
599608
self.parameters = parameters
600609
self.returnType = returnType
601610
self.documentation = documentation
@@ -704,6 +713,8 @@ public struct ImportedSetterSkeleton: Codable {
704713

705714
public struct ImportedTypeSkeleton: Codable {
706715
public let name: String
716+
/// The JavaScript constructor name to use for `init(...)`, if different from `name`.
717+
public let jsName: String?
707718
public let constructor: ImportedConstructorSkeleton?
708719
public let methods: [ImportedFunctionSkeleton]
709720
public let getters: [ImportedGetterSkeleton]
@@ -712,13 +723,15 @@ public struct ImportedTypeSkeleton: Codable {
712723

713724
public init(
714725
name: String,
726+
jsName: String? = nil,
715727
constructor: ImportedConstructorSkeleton? = nil,
716728
methods: [ImportedFunctionSkeleton],
717729
getters: [ImportedGetterSkeleton] = [],
718730
setters: [ImportedSetterSkeleton] = [],
719731
documentation: String? = nil
720732
) {
721733
self.name = name
734+
self.jsName = jsName
722735
self.constructor = constructor
723736
self.methods = methods
724737
self.getters = getters

0 commit comments

Comments
 (0)