Skip to content

Commit 8d3c223

Browse files
WIP
1 parent 4337c35 commit 8d3c223

File tree

17 files changed

+1376
-310
lines changed

17 files changed

+1376
-310
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
import JavaScriptKit
8+
9+
@JSFunction func benchmarkHelperNoop() throws(JSException) -> Void
10+
11+
@JSFunction func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void
12+
13+
@JSFunction func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void

Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fileprivate func bjs_benchmarkHelperNoop() -> Void {
1515
}
1616
#endif
1717

18-
func benchmarkHelperNoop() throws(JSException) -> Void {
18+
func _$benchmarkHelperNoop() throws(JSException) -> Void {
1919
bjs_benchmarkHelperNoop()
2020
if let error = _swift_js_take_exception() {
2121
throw error
@@ -31,7 +31,7 @@ fileprivate func bjs_benchmarkHelperNoopWithNumber(_ n: Float64) -> Void {
3131
}
3232
#endif
3333

34-
func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void {
34+
func _$benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void {
3535
let nValue = n.bridgeJSLowerParameter()
3636
bjs_benchmarkHelperNoopWithNumber(nValue)
3737
if let error = _swift_js_take_exception() {
@@ -48,7 +48,7 @@ fileprivate func bjs_benchmarkRunner(_ name: Int32, _ body: Int32) -> Void {
4848
}
4949
#endif
5050

51-
func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void {
51+
func _$benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void {
5252
let nameValue = name.bridgeJSLowerParameter()
5353
let bodyValue = body.bridgeJSLowerParameter()
5454
bjs_benchmarkRunner(nameValue, bodyValue)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
import JavaScriptKit
8+
9+
@JSFunction func createTS2Skeleton() throws(JSException) -> TS2Skeleton
10+
11+
@JSClass struct TS2Skeleton: _JSBridgedClass {
12+
@JSFunction func convert(_ ts: String) throws(JSException) -> String
13+
}

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fileprivate func bjs_createTS2Skeleton() -> Int32 {
1515
}
1616
#endif
1717

18-
func createTS2Skeleton() throws(JSException) -> TS2Skeleton {
18+
func _$createTS2Skeleton() throws(JSException) -> TS2Skeleton {
1919
let ret = bjs_createTS2Skeleton()
2020
if let error = _swift_js_take_exception() {
2121
throw error
@@ -32,21 +32,12 @@ fileprivate func bjs_TS2Skeleton_convert(_ self: Int32, _ ts: Int32) -> Int32 {
3232
}
3333
#endif
3434

35-
struct TS2Skeleton: _JSBridgedClass {
36-
let jsObject: JSObject
37-
38-
init(unsafelyWrapping jsObject: JSObject) {
39-
self.jsObject = jsObject
40-
}
41-
42-
func convert(_ ts: String) throws(JSException) -> String {
43-
let selfValue = self.bridgeJSLowerParameter()
44-
let tsValue = ts.bridgeJSLowerParameter()
45-
let ret = bjs_TS2Skeleton_convert(selfValue, tsValue)
46-
if let error = _swift_js_take_exception() {
47-
throw error
48-
}
49-
return String.bridgeJSLiftReturn(ret)
35+
func _$TS2Skeleton_convert(_ self: JSObject, _ ts: String) throws(JSException) -> String {
36+
let selfValue = self.bridgeJSLowerParameter()
37+
let tsValue = ts.bridgeJSLowerParameter()
38+
let ret = bjs_TS2Skeleton_convert(selfValue, tsValue)
39+
if let error = _swift_js_take_exception() {
40+
throw error
5041
}
51-
42+
return String.bridgeJSLiftReturn(ret)
5243
}

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ let package = Package(
4242
targets: [
4343
.target(
4444
name: "JavaScriptKit",
45-
dependencies: ["_CJavaScriptKit", "JavaScriptKitMacros"],
45+
dependencies: ["_CJavaScriptKit", "BridgeJSMacros"],
4646
exclude: useLegacyResourceBundling ? [] : ["Runtime"],
4747
resources: useLegacyResourceBundling ? [.copy("Runtime")] : [],
4848
cSettings: shouldBuildForEmbedded
@@ -60,7 +60,7 @@ let package = Package(
6060
),
6161
.target(name: "_CJavaScriptKit"),
6262
.macro(
63-
name: "JavaScriptKitMacros",
63+
name: "BridgeJSMacros",
6464
dependencies: [
6565
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
6666
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),

Plugins/BridgeJS/Package.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,19 @@ let package = Package(
5656
],
5757
exclude: ["__Snapshots__", "Inputs", "MultifileInputs"]
5858
),
59+
.macro(
60+
name: "BridgeJSMacros",
61+
dependencies: [
62+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
63+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
64+
]
65+
),
66+
.testTarget(
67+
name: "BridgeJSMacrosTests",
68+
dependencies: [
69+
"BridgeJSMacros",
70+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
71+
]
72+
),
5973
]
6074
)

Sources/JavaScriptKitMacros/JavaScriptKitMacrosPlugin.swift renamed to Plugins/BridgeJS/Sources/BridgeJSMacros/BridgeJSMacrosPlugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftCompilerPlugin
22
import SwiftSyntaxMacros
33

44
@main
5-
struct JavaScriptKitMacrosPlugin: CompilerPlugin {
5+
struct BridgeJSMacrosPlugin: CompilerPlugin {
66
var providingMacros: [Macro.Type] = [
77
JSFunctionMacro.self,
88
JSVariableMacro.self,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import SwiftSyntax
2+
import SwiftSyntaxBuilder
3+
import SwiftSyntaxMacros
4+
import SwiftDiagnostics
5+
6+
public enum JSClassMacro {}
7+
8+
extension JSClassMacro: MemberMacro {
9+
public static func expansion(
10+
of node: AttributeSyntax,
11+
providingMembersOf declaration: some DeclGroupSyntax,
12+
in context: some MacroExpansionContext
13+
) throws -> [DeclSyntax] {
14+
var members: [DeclSyntax] = []
15+
16+
let existingMembers = declaration.memberBlock.members
17+
let hasJSObjectProperty = existingMembers.contains { member in
18+
guard let variable = member.decl.as(VariableDeclSyntax.self) else { return false }
19+
return variable.bindings.contains { binding in
20+
binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == "jsObject"
21+
}
22+
}
23+
24+
if !hasJSObjectProperty {
25+
members.append(DeclSyntax("let jsObject: JSObject"))
26+
}
27+
28+
let hasUnsafelyWrappingInit = existingMembers.contains { member in
29+
guard let initializer = member.decl.as(InitializerDeclSyntax.self) else { return false }
30+
let parameters = initializer.signature.parameterClause.parameters
31+
guard let firstParam = parameters.first else { return false }
32+
let externalName = firstParam.firstName.text
33+
let internalName = firstParam.secondName?.text
34+
return externalName == "unsafelyWrapping" && internalName == "jsObject"
35+
}
36+
37+
if !hasUnsafelyWrappingInit {
38+
members.append(
39+
DeclSyntax(
40+
"""
41+
init(unsafelyWrapping jsObject: JSObject) {
42+
self.jsObject = jsObject
43+
}
44+
"""
45+
)
46+
)
47+
}
48+
49+
return members
50+
}
51+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import SwiftSyntax
2+
import SwiftSyntaxBuilder
3+
import SwiftSyntaxMacros
4+
import SwiftDiagnostics
5+
6+
public enum JSFunctionMacro {}
7+
8+
extension JSFunctionMacro: BodyMacro {
9+
public static func expansion(
10+
of node: AttributeSyntax,
11+
providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
12+
in context: some MacroExpansionContext
13+
) throws -> [CodeBlockItemSyntax] {
14+
if let functionDecl = declaration.as(FunctionDeclSyntax.self) {
15+
let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context)
16+
let isStatic = JSMacroHelper.isStatic(functionDecl.modifiers)
17+
let isInstanceMember = enclosingTypeName != nil && !isStatic
18+
19+
let name = functionDecl.name.text
20+
let glueName = JSMacroHelper.glueName(baseName: name, enclosingTypeName: enclosingTypeName)
21+
22+
var arguments: [String] = []
23+
if isInstanceMember {
24+
arguments.append("self.jsObject")
25+
}
26+
arguments.append(
27+
contentsOf: JSMacroHelper.parameterNames(functionDecl.signature.parameterClause.parameters)
28+
)
29+
30+
let argsJoined = arguments.joined(separator: ", ")
31+
let call = "\(glueName)(\(argsJoined))"
32+
33+
let effects = functionDecl.signature.effectSpecifiers
34+
let isAsync = effects?.asyncSpecifier != nil
35+
let isThrows = effects?.throwsClause != nil
36+
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: isThrows)
37+
38+
let isVoid = JSMacroHelper.isVoidReturn(functionDecl.signature.returnClause?.type)
39+
let line = isVoid ? "\(prefix)\(call)" : "return \(prefix)\(call)"
40+
return [CodeBlockItemSyntax(stringLiteral: line)]
41+
}
42+
43+
if let initializerDecl = declaration.as(InitializerDeclSyntax.self) {
44+
guard let enclosingTypeName = JSMacroHelper.enclosingTypeName(from: context) else {
45+
context.diagnose(
46+
Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration)
47+
)
48+
return []
49+
}
50+
51+
let glueName = JSMacroHelper.glueName(baseName: "init", enclosingTypeName: enclosingTypeName)
52+
let parameters = initializerDecl.signature.parameterClause.parameters
53+
let arguments = JSMacroHelper.parameterNames(parameters)
54+
let call = "\(glueName)(\(arguments.joined(separator: ", ")))"
55+
56+
let effects = initializerDecl.signature.effectSpecifiers
57+
let isAsync = effects?.asyncSpecifier != nil
58+
let isThrows = effects?.throwsClause != nil
59+
let prefix = JSMacroHelper.tryAwaitPrefix(isAsync: isAsync, isThrows: isThrows)
60+
61+
return [
62+
CodeBlockItemSyntax(stringLiteral: "let jsObject = \(prefix)\(call)"),
63+
CodeBlockItemSyntax(stringLiteral: "self.init(unsafelyWrapping: jsObject)"),
64+
]
65+
}
66+
67+
context.diagnose(Diagnostic(node: Syntax(declaration), message: JSMacroMessage.unsupportedDeclaration))
68+
return []
69+
}
70+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import SwiftSyntax
2+
import SwiftSyntaxBuilder
3+
import SwiftSyntaxMacros
4+
import SwiftDiagnostics
5+
6+
enum JSMacroMessage: String, DiagnosticMessage {
7+
case unsupportedDeclaration = "@JSImportFunction can only be applied to functions or initializers."
8+
case unsupportedVariable = "@JSImportVariable can only be applied to single-variable declarations."
9+
10+
var message: String { rawValue }
11+
var diagnosticID: MessageID { MessageID(domain: "JavaScriptKitMacros", id: rawValue) }
12+
var severity: DiagnosticSeverity { .error }
13+
}
14+
15+
enum JSMacroHelper {
16+
static func enclosingTypeName(from context: some MacroExpansionContext) -> String? {
17+
for syntax in context.lexicalContext {
18+
if let decl = syntax.as(ClassDeclSyntax.self) {
19+
return decl.name.text
20+
}
21+
if let decl = syntax.as(StructDeclSyntax.self) {
22+
return decl.name.text
23+
}
24+
if let decl = syntax.as(EnumDeclSyntax.self) {
25+
return decl.name.text
26+
}
27+
if let decl = syntax.as(ActorDeclSyntax.self) {
28+
return decl.name.text
29+
}
30+
}
31+
return nil
32+
}
33+
34+
static func isStatic(_ modifiers: DeclModifierListSyntax?) -> Bool {
35+
guard let modifiers else { return false }
36+
return modifiers.contains { modifier in
37+
modifier.name.tokenKind == .keyword(.static) || modifier.name.tokenKind == .keyword(.class)
38+
}
39+
}
40+
41+
static func isVoidReturn(_ returnType: TypeSyntax?) -> Bool {
42+
guard let returnType else { return true }
43+
if let identifier = returnType.as(IdentifierTypeSyntax.self), identifier.name.text == "Void" {
44+
return true
45+
}
46+
if let tuple = returnType.as(TupleTypeSyntax.self), tuple.elements.isEmpty {
47+
return true
48+
}
49+
return false
50+
}
51+
52+
static func glueName(baseName: String, enclosingTypeName: String?) -> String {
53+
if let enclosingTypeName {
54+
return "_$\(enclosingTypeName)_\(baseName)"
55+
}
56+
return "_$\(baseName)"
57+
}
58+
59+
static func glueName(baseName: String, enclosingTypeName: String?, operation: String) -> String {
60+
if let enclosingTypeName {
61+
return "_$\(enclosingTypeName)_\(baseName)_\(operation)"
62+
}
63+
return "_$\(baseName)_\(operation)"
64+
}
65+
66+
static func parameterNames(_ parameters: FunctionParameterListSyntax) -> [String] {
67+
parameters.compactMap { param in
68+
let nameToken = param.secondName ?? param.firstName
69+
let nameText = nameToken.text
70+
return nameText == "_" ? nil : nameText
71+
}
72+
}
73+
74+
static func tryAwaitPrefix(isAsync: Bool, isThrows: Bool) -> String {
75+
switch (isAsync, isThrows) {
76+
case (true, true):
77+
return "try await "
78+
case (true, false):
79+
return "await "
80+
case (false, true):
81+
return "try "
82+
case (false, false):
83+
return ""
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)