1+ const { readFile } = require ( 'fs/promises' ) ;
2+ const path = require ( 'path' ) ;
3+ let WASI ;
4+
5+ // Lazy-load WASI because the built-in module only exists on Node >= 13.
6+ // This allows the rest of the package (and the test suite) to run on older
7+ // versions where Swift analysis is not needed. We fall back to a noop stub
8+ // that immediately throws when used.
9+ try {
10+ // Use indirect `require` to avoid webpack / bundlers statically analysing it.
11+ WASI = eval ( "require" ) ( 'wasi' ) . WASI ; // eslint-disable-line no-eval
12+ } catch ( _ ) {
13+ WASI = class {
14+ constructor ( ) { throw new Error ( 'WASI unsupported in this Node version' ) ; }
15+ } ;
16+ }
17+
18+ /**
19+ * Executes the pre-compiled SwiftSyntax WebAssembly binary and returns its stdout.
20+ *
21+ * The WASM module must live next to this runner as `SwiftSyntaxWasm.wasm`.
22+ * It is expected to behave like a CLI: first arg is a path to the Swift file
23+ * whose AST / JSON it should print to STDOUT.
24+ *
25+ * @param {string } filePath Absolute or relative path to the .swift source file.
26+ * @param {Object } [options]
27+ * @param {string } [options.wasmPath] Override the default path to the WASM binary.
28+ * @returns {Promise<string> } Captured stdout produced by the WASM program.
29+ */
30+ async function runSwiftSyntax ( filePath , options = { } ) {
31+ const wasmPath = options . wasmPath || path . join ( __dirname , 'SwiftSyntaxWasm.wasm' ) ;
32+
33+ // Make sure we can locate the WASM file early for better error reporting.
34+ try {
35+ await readFile ( wasmPath ) ;
36+ } catch ( err ) {
37+ throw new Error ( `SwiftSyntax WASM binary not found at ${ wasmPath } . ` +
38+ 'Ensure you have built it via the Swift WASI toolchain or downloaded the prebuilt artefact.' ) ;
39+ }
40+
41+ // WASI instance configured to expose the current working directory so that
42+ // the module can open the target Swift file.
43+ const wasi = new WASI ( {
44+ args : [ 'SwiftSyntaxWasm.wasm' , filePath ] ,
45+ env : process . env ,
46+ preopens : { '/' : process . cwd ( ) }
47+ } ) ;
48+
49+ const wasmBytes = await readFile ( wasmPath ) ;
50+ const module = await WebAssembly . compile ( wasmBytes ) ;
51+ const instance = await WebAssembly . instantiate ( module , wasi . getImportObject ( ) ) ;
52+
53+ // Capture stdout by monkey-patching process.stdout.write for the duration of
54+ // the WASI execution. This avoids spawning a separate process or temp files.
55+ let stdout = '' ;
56+ const originalWrite = process . stdout . write ;
57+ process . stdout . write = ( chunk , encoding , callback ) => {
58+ stdout += chunk instanceof Buffer ? chunk . toString ( 'utf8' ) : chunk ;
59+ if ( typeof encoding === 'function' ) {
60+ encoding ( ) ; // encoding is actually the callback here
61+ } else if ( typeof callback === 'function' ) {
62+ callback ( ) ;
63+ }
64+ return true ;
65+ } ;
66+
67+ try {
68+ wasi . start ( instance ) ;
69+ } finally {
70+ process . stdout . write = originalWrite ; // always restore
71+ }
72+
73+ return stdout ;
74+ }
75+
76+ module . exports = { runSwiftSyntax } ;
0 commit comments