Skip to content

Commit 889fb2b

Browse files
committed
feat: Introduce extended macro and namespace information extraction and storing
1 parent d62db09 commit 889fb2b

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,12 @@ class ExportSwift {
123123
}
124124

125125
private func visitFunction(node: FunctionDeclSyntax) -> ExportedFunction? {
126-
guard node.attributes.hasJSAttribute() else {
126+
guard let jsAttribute = node.attributes.firstJSAttribute else {
127127
return nil
128128
}
129-
let name = node.name.text
129+
130+
let (baseName, namespace) = extractNameAndNamespace(from: node, jsAttribute: jsAttribute)
131+
130132
var parameters: [Parameter] = []
131133
for param in node.signature.parameterClause.parameters {
132134
guard let type = self.parent.lookupType(for: param.type) else {
@@ -151,21 +153,22 @@ class ExportSwift {
151153
let abiName: String
152154
switch state {
153155
case .topLevel:
154-
abiName = "bjs_\(name)"
156+
abiName = "bjs_\(baseName)"
155157
case .classBody(let className):
156-
abiName = "bjs_\(className)_\(name)"
158+
abiName = "bjs_\(className)_\(baseName)"
157159
}
158160

159161
guard let effects = collectEffects(signature: node.signature) else {
160162
return nil
161163
}
162164

163165
return ExportedFunction(
164-
name: name,
166+
name: baseName,
165167
abiName: abiName,
166168
parameters: parameters,
167169
returnType: returnType,
168-
effects: effects
170+
effects: effects,
171+
namespace: namespace
169172
)
170173
}
171174

@@ -192,6 +195,19 @@ class ExportSwift {
192195
}
193196
return Effects(isAsync: isAsync, isThrows: isThrows)
194197
}
198+
199+
private func extractNameAndNamespace(
200+
from node: FunctionDeclSyntax,
201+
jsAttribute: AttributeSyntax
202+
) -> (name: String, namespace: [String]?) {
203+
guard let arguments = jsAttribute.arguments?.as(LabeledExprListSyntax.self),
204+
let firstArg = arguments.first?.expression.as(StringLiteralExprSyntax.self),
205+
let namespaceString = firstArg.segments.first?.as(StringSegmentSyntax.self)?.content.text else {
206+
return (node.name.text, nil)
207+
}
208+
let namespaces = namespaceString.split(separator: ".").map(String.init)
209+
return (node.name.text, namespaces)
210+
}
195211

196212
override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind {
197213
guard node.attributes.hasJSAttribute() else { return .skipChildren }
@@ -227,11 +243,13 @@ class ExportSwift {
227243
let name = node.name.text
228244
stateStack.push(state: .classBody(name: name))
229245

230-
guard node.attributes.hasJSAttribute() else { return .skipChildren }
246+
guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren }
247+
231248
exportedClassByName[name] = ExportedClass(
232249
name: name,
233250
constructor: nil,
234-
methods: []
251+
methods: [],
252+
namespace: nil
235253
)
236254
exportedClassNames.append(name)
237255
return .visitChildren
@@ -635,9 +653,13 @@ class ExportSwift {
635653

636654
extension AttributeListSyntax {
637655
fileprivate func hasJSAttribute() -> Bool {
638-
return first(where: {
656+
firstJSAttribute != nil
657+
}
658+
659+
fileprivate var firstJSAttribute: AttributeSyntax? {
660+
first(where: {
639661
$0.as(AttributeSyntax.self)?.attributeName.trimmedDescription == "JS"
640-
}) != nil
662+
})?.as(AttributeSyntax.self)
641663
}
642664
}
643665

Sources/JavaScriptKit/Macros.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
/// return "Hello, \(name)!"
99
/// }
1010
///
11+
/// // Export a function to JavaScript with namespace
12+
/// @JS("__Swift.Foundation.UUID.create") public func createUUID() -> String {
13+
/// return UUID().uuidString
14+
/// }
15+
///
1116
/// // Export a class and its members
1217
/// @JS class Counter {
1318
/// private var count = 0
@@ -33,3 +38,29 @@
3338
/// - Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases.
3439
@attached(peer)
3540
public macro JS() = Builtin.ExternalMacro
41+
42+
/// A macro that exposes Swift functions, classes, and methods to JavaScript.
43+
/// Additionally defines namespaces defined by `namespace` parameter
44+
///
45+
/// Apply this macro to Swift declarations that you want to make callable from JavaScript:
46+
///
47+
/// ```swift
48+
/// // Export a function to JavaScript with namespace
49+
/// @JS("__Swift.Foundation.UUID.create") public func createUUID() -> String {
50+
/// return UUID().uuidString
51+
/// }
52+
/// ```
53+
///
54+
/// Resulting TypeScript declaration equivalent will be formatted as follow:
55+
/// ```typescript
56+
///
57+
/// ```
58+
/// When you build your project with the BridgeJS plugin, these declarations will be
59+
/// accessible from JavaScript, and TypeScript declaration files (`.d.ts`) will be
60+
/// automatically generated to provide type safety.
61+
///
62+
/// For detailed usage information, see the article <doc:Exporting-Swift-to-JavaScript>.
63+
///
64+
/// - Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases.
65+
@attached(peer)
66+
public macro JS(_ namespace: String) = Builtin.ExternalMacro

0 commit comments

Comments
 (0)