Skip to content

Commit 94347db

Browse files
committed
BridgeJS: Include Swift doc comments in generated d.ts
Propagate Swift `///` and `/** */` documentation on exported declarations into the generated TypeScript declarations as JSDoc, so editors surface hover docs for the bridged API. The exporter now captures the leading doc comment for functions, classes, methods, properties, constructors, structs, and enums into the skeleton, and the linker renders it as a single JSDoc block. The Swift DocC field list is mapped as the inverse of the TS2Swift importer: the leading description becomes the JSDoc body, `- Parameters:`/`- Parameter x:` become `@param`, `- Returns:` becomes `@returns`, and `- Throws:` becomes `@throws`. Existing default-value annotations are merged into the same block so a parameter never emits two comment blocks; declarations without doc comments produce byte-identical output to before.
1 parent 2fc75d4 commit 94347db

14 files changed

Lines changed: 1766 additions & 68 deletions

File tree

Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"methods" : [
1717
{
1818
"abiName" : "bjs_PlayBridgeJS_updateDetailed",
19+
"documentation" : "Structured entry point used by the playground so JS doesn't need to parse diagnostics.",
1920
"effects" : {
2021
"isAsync" : false,
2122
"isStatic" : false,

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,10 +1266,54 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
12661266
returnType: returnType,
12671267
effects: effects,
12681268
namespace: finalNamespace,
1269-
staticContext: staticContext
1269+
staticContext: staticContext,
1270+
documentation: extractDocumentation(from: node)
12701271
)
12711272
}
12721273

1274+
/// Returns the doc comment (`///` or `/** */`) attached to a declaration, with
1275+
/// markers stripped and DocC field lists (`- Parameters:`, `- Returns:`) preserved.
1276+
private func extractDocumentation(from node: some SyntaxProtocol) -> String? {
1277+
var run: [String] = []
1278+
for piece in node.leadingTrivia {
1279+
switch piece {
1280+
case .docLineComment(let text):
1281+
var line = Substring(text)
1282+
if line.hasPrefix("///") { line = line.dropFirst(3) }
1283+
if line.first == " " { line = line.dropFirst() }
1284+
run.append(String(line))
1285+
case .docBlockComment(let text):
1286+
run.append(contentsOf: stripBlockComment(text))
1287+
case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count):
1288+
if count >= 2 { run.removeAll() }
1289+
case .lineComment, .blockComment:
1290+
run.removeAll()
1291+
default:
1292+
continue
1293+
}
1294+
}
1295+
let joined = run.joined(separator: "\n")
1296+
return joined.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : joined
1297+
}
1298+
1299+
private func stripBlockComment(_ text: String) -> [String] {
1300+
var body = Substring(text)
1301+
if body.hasPrefix("/**") { body = body.dropFirst(3) }
1302+
if body.hasSuffix("*/") { body = body.dropLast(2) }
1303+
var lines = body.split(separator: "\n", omittingEmptySubsequences: false).map { raw -> String in
1304+
var line = raw[...]
1305+
while let first = line.first, first == " " || first == "\t" { line = line.dropFirst() }
1306+
if line.first == "*" {
1307+
line = line.dropFirst()
1308+
if line.first == " " { line = line.dropFirst() }
1309+
}
1310+
return String(line)
1311+
}
1312+
while lines.first?.trimmingCharacters(in: .whitespaces).isEmpty == true { lines.removeFirst() }
1313+
while lines.last?.trimmingCharacters(in: .whitespaces).isEmpty == true { lines.removeLast() }
1314+
return lines
1315+
}
1316+
12731317
private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false) -> Effects? {
12741318
let isAsync = signature.effectSpecifiers?.asyncSpecifier != nil
12751319
var isThrows = false
@@ -1360,7 +1404,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
13601404
let constructor = ExportedConstructor(
13611405
abiName: "bjs_\(classAbiName)_init",
13621406
parameters: parameters,
1363-
effects: effects
1407+
effects: effects,
1408+
documentation: extractDocumentation(from: node)
13641409
)
13651410
exportedClassByName[classKey]?.constructor = constructor
13661411

@@ -1383,7 +1428,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
13831428
let constructor = ExportedConstructor(
13841429
abiName: "bjs_\(structAbiName)_init",
13851430
parameters: parameters,
1386-
effects: effects
1431+
effects: effects,
1432+
documentation: extractDocumentation(from: node)
13871433
)
13881434
exportedStructByName[structKey]?.constructor = constructor
13891435

@@ -1490,7 +1536,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
14901536
isReadonly: isReadonly,
14911537
isStatic: isStatic,
14921538
namespace: finalNamespace,
1493-
staticContext: staticContext
1539+
staticContext: staticContext,
1540+
documentation: extractDocumentation(from: node)
14941541
)
14951542

14961543
if case .enumBody(_, let key) = state {
@@ -1537,7 +1584,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
15371584
methods: [],
15381585
properties: [],
15391586
namespace: effectiveNamespace,
1540-
identityMode: classIdentityMode
1587+
identityMode: classIdentityMode,
1588+
documentation: extractDocumentation(from: node)
15411589
)
15421590
let uniqueKey = makeKey(name: name, namespace: effectiveNamespace)
15431591

@@ -1657,7 +1705,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
16571705
namespace: effectiveNamespace,
16581706
emitStyle: emitStyle,
16591707
staticMethods: [],
1660-
staticProperties: []
1708+
staticProperties: [],
1709+
documentation: extractDocumentation(from: node)
16611710
)
16621711

16631712
let enumUniqueKey = makeKey(name: name, namespace: effectiveNamespace)
@@ -1774,7 +1823,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17741823
name: name,
17751824
methods: [],
17761825
properties: [],
1777-
namespace: effectiveNamespace
1826+
namespace: effectiveNamespace,
1827+
documentation: extractDocumentation(from: node)
17781828
)
17791829

17801830
stateStack.push(state: .protocolBody(name: name, key: protocolUniqueKey))
@@ -1798,7 +1848,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17981848
name: name,
17991849
methods: methods,
18001850
properties: exportedProtocolByName[protocolUniqueKey]?.properties ?? [],
1801-
namespace: effectiveNamespace
1851+
namespace: effectiveNamespace,
1852+
documentation: extractDocumentation(from: node)
18021853
)
18031854

18041855
exportedProtocolByName[protocolUniqueKey] = exportedProtocol
@@ -1874,7 +1925,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18741925
isReadonly: true,
18751926
isStatic: false,
18761927
namespace: effectiveNamespace,
1877-
staticContext: nil
1928+
staticContext: nil,
1929+
documentation: extractDocumentation(from: varDecl)
18781930
)
18791931
properties.append(property)
18801932
}
@@ -1888,7 +1940,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18881940
explicitAccessControl: explicitAccessControl,
18891941
properties: properties,
18901942
methods: [],
1891-
namespace: effectiveNamespace
1943+
namespace: effectiveNamespace,
1944+
documentation: extractDocumentation(from: node)
18921945
)
18931946

18941947
exportedStructByName[structUniqueKey] = exportedStruct
@@ -1981,7 +2034,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19812034
returnType: returnType,
19822035
effects: effects,
19832036
namespace: namespace,
1984-
staticContext: nil
2037+
staticContext: nil,
2038+
documentation: extractDocumentation(from: node)
19852039
)
19862040
}
19872041

@@ -2022,7 +2076,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20222076
let exportedProperty = ExportedProtocolProperty(
20232077
name: propertyName,
20242078
type: propertyType,
2025-
isReadonly: isReadonly
2079+
isReadonly: isReadonly,
2080+
documentation: extractDocumentation(from: node)
20262081
)
20272082

20282083
if var currentProtocol = exportedProtocolByName[protocolKey] {
@@ -2033,7 +2088,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20332088
name: currentProtocol.name,
20342089
methods: currentProtocol.methods,
20352090
properties: properties,
2036-
namespace: currentProtocol.namespace
2091+
namespace: currentProtocol.namespace,
2092+
documentation: currentProtocol.documentation
20372093
)
20382094
exportedProtocolByName[protocolKey] = currentProtocol
20392095
}

0 commit comments

Comments
 (0)