Skip to content

Commit 1496944

Browse files
BridgeJS: Add internal debug tool for inspecting intermediate stages (#519)
1 parent bcbeea0 commit 1496944

File tree

4 files changed

+172
-4
lines changed

4 files changed

+172
-4
lines changed

Plugins/BridgeJS/Package.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ let package = Package(
77
name: "BridgeJS",
88
platforms: [.macOS(.v13)],
99
dependencies: [
10-
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1")
10+
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"),
11+
// Development dependencies
12+
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.7.0"),
1113
],
1214
targets: [
1315
.target(name: "BridgeJSBuildPlugin"),
@@ -71,5 +73,14 @@ let package = Package(
7173
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
7274
]
7375
),
76+
77+
.executableTarget(
78+
name: "BridgeJSToolInternal",
79+
dependencies: [
80+
"BridgeJSCore",
81+
"BridgeJSLink",
82+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
83+
]
84+
),
7485
]
7586
)

Plugins/BridgeJS/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,26 @@ Return values use direct Wasm returns for primitives, and imported intrinsic fun
163163

164164
For detailed semantics, see the [How It Works sections](https://swiftpackageindex.com/swiftwasm/JavaScriptKit/documentation/javascriptkit/exporting-swift-class#How-It-Works) in the user documentation.
165165

166+
## Debug utilities
167+
168+
`BridgeJSToolInternal` exposes pipeline stages for debugging:
169+
170+
- `emit-skeleton` - Parse Swift files (or `-` for stdin) and print the BridgeJS skeleton as JSON.
171+
- `emit-swift-thunks` — Read skeleton JSON (from a file or `-` for stdin) and print the generated Swift glue (export and import thunks).
172+
- `emit-js` / `emit-dts` - Read skeleton JSON files (or `-` for stdin) and print the .js/.d.ts
173+
174+
Use these to inspect parser output and generated code without running the full generate/link pipeline.
175+
176+
```console
177+
$ cat <<EOS | ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal emit-skeleton - | ./Plugins/BridgeJS/.build/debug/BridgeJSToolInternal emit-dts -
178+
@JSFunction func foo() throws(JSException) -> Int
179+
@JS class Bar {
180+
@JS init() {}
181+
@JS func baz() {}
182+
}
183+
EOS
184+
```
185+
166186
## Future Work
167187

168188
- [ ] Cast between TS interface

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import BridgeJSSkeleton
77
import BridgeJSUtilities
88
#endif
99

10-
struct BridgeJSLink {
10+
public struct BridgeJSLink {
1111
var skeletons: [BridgeJSSkeleton] = []
1212
let sharedMemory: Bool
1313
private let namespaceBuilder = NamespaceBuilder()
1414

15-
init(
15+
public init(
1616
skeletons: [BridgeJSSkeleton] = [],
1717
sharedMemory: Bool
1818
) {
@@ -1035,7 +1035,7 @@ struct BridgeJSLink {
10351035
return printer.lines.joined(separator: "\n")
10361036
}
10371037

1038-
func link() throws -> (outputJs: String, outputDts: String) {
1038+
public func link() throws -> (outputJs: String, outputDts: String) {
10391039
let data = try collectLinkData()
10401040
let outputJs = try generateJavaScript(data: data)
10411041
let outputDts = generateTypeScript(data: data)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@preconcurrency import struct Foundation.URL
2+
@preconcurrency import struct Foundation.Data
3+
@preconcurrency import class Foundation.JSONEncoder
4+
@preconcurrency import class Foundation.JSONDecoder
5+
@preconcurrency import class Foundation.FileHandle
6+
import SwiftParser
7+
import SwiftSyntax
8+
9+
import BridgeJSCore
10+
import BridgeJSSkeleton
11+
import BridgeJSLink
12+
import BridgeJSUtilities
13+
14+
import ArgumentParser
15+
16+
@main struct BridgeJSToolInternal: ParsableCommand {
17+
18+
static let configuration = CommandConfiguration(
19+
commandName: "bridge-js-tool-internal",
20+
abstract: "BridgeJS Tool Internal",
21+
version: "0.1.0",
22+
subcommands: [
23+
EmitSkeleton.self,
24+
EmitSwiftThunks.self,
25+
EmitJS.self,
26+
EmitDTS.self,
27+
]
28+
)
29+
30+
static func readData(from file: String) throws -> Data {
31+
if file == "-" {
32+
return try FileHandle.standardInput.readToEnd() ?? Data()
33+
} else {
34+
return try Data(contentsOf: URL(fileURLWithPath: file))
35+
}
36+
}
37+
38+
struct EmitSkeleton: ParsableCommand {
39+
static let configuration = CommandConfiguration(
40+
commandName: "emit-skeleton",
41+
abstract: "Emit the BridgeJS skeleton",
42+
)
43+
44+
@Argument(help: "The input files to emit the BridgeJS skeleton from")
45+
var inputFiles: [String]
46+
47+
func run() throws {
48+
let swiftToSkeleton = SwiftToSkeleton(
49+
progress: ProgressReporting(verbose: false),
50+
moduleName: "InternalModule",
51+
exposeToGlobal: false
52+
)
53+
for inputFile in inputFiles.sorted() {
54+
let content = try String(decoding: readData(from: inputFile), as: UTF8.self)
55+
if BridgeJSGeneratedFile.hasSkipComment(content) {
56+
continue
57+
}
58+
let sourceFile = Parser.parse(source: content)
59+
swiftToSkeleton.addSourceFile(sourceFile, inputFilePath: inputFile)
60+
}
61+
let skeleton = try swiftToSkeleton.finalize()
62+
let encoder = JSONEncoder()
63+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
64+
let skeletonData = try encoder.encode(skeleton)
65+
print(String(data: skeletonData, encoding: .utf8)!)
66+
}
67+
}
68+
69+
struct EmitSwiftThunks: ParsableCommand {
70+
static let configuration = CommandConfiguration(
71+
commandName: "emit-swift-thunks",
72+
abstract: "Emit the Swift thunks",
73+
)
74+
@Argument(help: "The skeleton file to emit the Swift thunks from")
75+
var skeletonFile: String
76+
77+
func run() throws {
78+
let skeletonData = try readData(from: skeletonFile)
79+
let skeleton = try JSONDecoder().decode(BridgeJSSkeleton.self, from: skeletonData)
80+
let moduleName = "InternalModule"
81+
let exported = try skeleton.exported.flatMap {
82+
try ExportSwift(
83+
progress: ProgressReporting(verbose: false),
84+
moduleName: moduleName,
85+
skeleton: $0
86+
).finalize()
87+
}
88+
let imported = try skeleton.imported.flatMap {
89+
try ImportTS(
90+
progress: ProgressReporting(verbose: false),
91+
moduleName: moduleName,
92+
skeleton: $0
93+
).finalize()
94+
}
95+
let combinedSwift = [exported, imported].compactMap { $0 }
96+
print(combinedSwift.joined(separator: "\n\n"))
97+
}
98+
}
99+
100+
static func linkSkeletons(skeletonFiles: [String]) throws -> (outputJs: String, outputDts: String) {
101+
var skeletons: [BridgeJSSkeleton] = []
102+
for skeletonFile in skeletonFiles.sorted() {
103+
let skeletonData = try readData(from: skeletonFile)
104+
skeletons.append(try JSONDecoder().decode(BridgeJSSkeleton.self, from: skeletonData))
105+
}
106+
let link = BridgeJSLink(skeletons: skeletons, sharedMemory: false)
107+
return try link.link()
108+
}
109+
110+
struct EmitJS: ParsableCommand {
111+
static let configuration = CommandConfiguration(
112+
commandName: "emit-js",
113+
abstract: "Emit the JavaScript glue code",
114+
)
115+
@Argument(help: "The skeleton files to emit the JavaScript glue code from")
116+
var skeletonFiles: [String]
117+
118+
func run() throws {
119+
let (outputJs, _) = try linkSkeletons(skeletonFiles: skeletonFiles)
120+
print(outputJs)
121+
}
122+
}
123+
124+
struct EmitDTS: ParsableCommand {
125+
static let configuration = CommandConfiguration(
126+
commandName: "emit-dts",
127+
abstract: "Emit the TypeScript type definitions",
128+
)
129+
@Argument(help: "The skeleton files to emit the TypeScript type definitions from")
130+
var skeletonFiles: [String]
131+
132+
func run() throws {
133+
let (_, outputDts) = try linkSkeletons(skeletonFiles: skeletonFiles)
134+
print(outputDts)
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)