Skip to content

Commit 210689e

Browse files
authored
Merge pull request #771 from PassiveLogic/kr/export-swift-doc-comments
BridgeJS: Include Swift doc comments in generated d.ts
2 parents ebbde04 + 45088be commit 210689e

14 files changed

Lines changed: 1808 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: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,10 +1266,56 @@ 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+
if line.last == "\r" { line = line.dropLast() }
1285+
run.append(String(line))
1286+
case .docBlockComment(let text):
1287+
run.append(contentsOf: stripBlockComment(text))
1288+
case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count):
1289+
if count >= 2 { run.removeAll() }
1290+
case .lineComment, .blockComment:
1291+
run.removeAll()
1292+
default:
1293+
continue
1294+
}
1295+
}
1296+
// Trim boundary blank lines so line (`///`) and block (`/** */`) comments
1297+
// produce a consistent skeleton value.
1298+
while run.first?.trimmingCharacters(in: .whitespaces).isEmpty == true { run.removeFirst() }
1299+
while run.last?.trimmingCharacters(in: .whitespaces).isEmpty == true { run.removeLast() }
1300+
return run.isEmpty ? nil : run.joined(separator: "\n")
1301+
}
1302+
1303+
private func stripBlockComment(_ text: String) -> [String] {
1304+
var body = Substring(text)
1305+
if body.hasPrefix("/**") { body = body.dropFirst(3) }
1306+
if body.hasSuffix("*/") { body = body.dropLast(2) }
1307+
return body.split(separator: "\n", omittingEmptySubsequences: false).map { raw -> String in
1308+
var line = raw[...]
1309+
if line.last == "\r" { line = line.dropLast() }
1310+
while let first = line.first, first == " " || first == "\t" { line = line.dropFirst() }
1311+
if line.first == "*" {
1312+
line = line.dropFirst()
1313+
if line.first == " " { line = line.dropFirst() }
1314+
}
1315+
return String(line)
1316+
}
1317+
}
1318+
12731319
private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false) -> Effects? {
12741320
let isAsync = signature.effectSpecifiers?.asyncSpecifier != nil
12751321
var isThrows = false
@@ -1360,7 +1406,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
13601406
let constructor = ExportedConstructor(
13611407
abiName: "bjs_\(classAbiName)_init",
13621408
parameters: parameters,
1363-
effects: effects
1409+
effects: effects,
1410+
documentation: extractDocumentation(from: node)
13641411
)
13651412
exportedClassByName[classKey]?.constructor = constructor
13661413

@@ -1383,7 +1430,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
13831430
let constructor = ExportedConstructor(
13841431
abiName: "bjs_\(structAbiName)_init",
13851432
parameters: parameters,
1386-
effects: effects
1433+
effects: effects,
1434+
documentation: extractDocumentation(from: node)
13871435
)
13881436
exportedStructByName[structKey]?.constructor = constructor
13891437

@@ -1490,7 +1538,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
14901538
isReadonly: isReadonly,
14911539
isStatic: isStatic,
14921540
namespace: finalNamespace,
1493-
staticContext: staticContext
1541+
staticContext: staticContext,
1542+
documentation: extractDocumentation(from: node)
14941543
)
14951544

14961545
if case .enumBody(_, let key) = state {
@@ -1537,7 +1586,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
15371586
methods: [],
15381587
properties: [],
15391588
namespace: effectiveNamespace,
1540-
identityMode: classIdentityMode
1589+
identityMode: classIdentityMode,
1590+
documentation: extractDocumentation(from: node)
15411591
)
15421592
let uniqueKey = makeKey(name: name, namespace: effectiveNamespace)
15431593

@@ -1657,7 +1707,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
16571707
namespace: effectiveNamespace,
16581708
emitStyle: emitStyle,
16591709
staticMethods: [],
1660-
staticProperties: []
1710+
staticProperties: [],
1711+
documentation: extractDocumentation(from: node)
16611712
)
16621713

16631714
let enumUniqueKey = makeKey(name: name, namespace: effectiveNamespace)
@@ -1774,7 +1825,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17741825
name: name,
17751826
methods: [],
17761827
properties: [],
1777-
namespace: effectiveNamespace
1828+
namespace: effectiveNamespace,
1829+
documentation: extractDocumentation(from: node)
17781830
)
17791831

17801832
stateStack.push(state: .protocolBody(name: name, key: protocolUniqueKey))
@@ -1798,7 +1850,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
17981850
name: name,
17991851
methods: methods,
18001852
properties: exportedProtocolByName[protocolUniqueKey]?.properties ?? [],
1801-
namespace: effectiveNamespace
1853+
namespace: effectiveNamespace,
1854+
documentation: extractDocumentation(from: node)
18021855
)
18031856

18041857
exportedProtocolByName[protocolUniqueKey] = exportedProtocol
@@ -1874,7 +1927,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18741927
isReadonly: true,
18751928
isStatic: false,
18761929
namespace: effectiveNamespace,
1877-
staticContext: nil
1930+
staticContext: nil,
1931+
documentation: extractDocumentation(from: varDecl)
18781932
)
18791933
properties.append(property)
18801934
}
@@ -1888,7 +1942,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
18881942
explicitAccessControl: explicitAccessControl,
18891943
properties: properties,
18901944
methods: [],
1891-
namespace: effectiveNamespace
1945+
namespace: effectiveNamespace,
1946+
documentation: extractDocumentation(from: node)
18921947
)
18931948

18941949
exportedStructByName[structUniqueKey] = exportedStruct
@@ -1981,7 +2036,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
19812036
returnType: returnType,
19822037
effects: effects,
19832038
namespace: namespace,
1984-
staticContext: nil
2039+
staticContext: nil,
2040+
documentation: extractDocumentation(from: node)
19852041
)
19862042
}
19872043

@@ -2022,7 +2078,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20222078
let exportedProperty = ExportedProtocolProperty(
20232079
name: propertyName,
20242080
type: propertyType,
2025-
isReadonly: isReadonly
2081+
isReadonly: isReadonly,
2082+
documentation: extractDocumentation(from: node)
20262083
)
20272084

20282085
if var currentProtocol = exportedProtocolByName[protocolKey] {
@@ -2033,7 +2090,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
20332090
name: currentProtocol.name,
20342091
methods: currentProtocol.methods,
20352092
properties: properties,
2036-
namespace: currentProtocol.namespace
2093+
namespace: currentProtocol.namespace,
2094+
documentation: currentProtocol.documentation
20372095
)
20382096
exportedProtocolByName[protocolKey] = currentProtocol
20392097
}

0 commit comments

Comments
 (0)