Skip to content

Commit f6f443f

Browse files
committed
Fix WASI LSP shutdown
Return io.EOF when WASI stdin closes instead of triggering repeated empty reads. Cover the command lifecycle in the unified module test.
1 parent 33668e7 commit f6f443f

3 files changed

Lines changed: 39 additions & 2 deletions

File tree

‎packages/typescript/test/browser/suite.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const packageDir = path.resolve(browserDir, "../..");
2626
const modes = ["async", "sync"] as const;
2727
const expectedTestCounts = {
2828
async: 334,
29-
sync: 337,
29+
sync: 338,
3030
} as const;
3131
const fileExclusions = [
3232
{
@@ -50,7 +50,7 @@ const fileExclusions = [
5050
{
5151
mode: "sync",
5252
file: "sync/wasm.test.ts",
53-
tests: 2,
53+
tests: 3,
5454
reason: "These Node.js integration tests use node:fs and node:wasi; wasm.test.ts covers browser API loading.",
5555
},
5656
] as const;

‎packages/typescript/test/sync/wasm.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,39 @@ describe("API over WebAssembly", () => {
5656
}
5757
});
5858

59+
test("exits successfully when LSP stdin closes", async () => {
60+
const WebAssembly = (globalThis as any).WebAssembly;
61+
const directory = await mkdtemp(path.join(tmpdir(), "typescript-wasip1-"));
62+
try {
63+
const stdin = await open(path.join(directory, "stdin"), "w+");
64+
const stdout = await open(path.join(directory, "stdout"), "w+");
65+
const stderr = await open(path.join(directory, "stderr"), "w+");
66+
try {
67+
const wasi = new WASI({
68+
version: "preview1",
69+
args: ["tsc.wasm", "--lsp", "--stdio"],
70+
env: { PWD: "/" },
71+
preopens: { "/": process.cwd() },
72+
stdin: stdin.fd,
73+
stdout: stdout.fd,
74+
stderr: stderr.fd,
75+
returnOnExit: true,
76+
});
77+
const module = await WebAssembly.compile(await readFile(wasmURL));
78+
const instance = await WebAssembly.instantiate(module, {
79+
wasi_snapshot_preview1: wasi.wasiImport,
80+
});
81+
assert.strictEqual(wasi.start(instance), 0);
82+
}
83+
finally {
84+
await Promise.all([stdin.close(), stdout.close(), stderr.close()]);
85+
}
86+
}
87+
finally {
88+
await rm(directory, { recursive: true });
89+
}
90+
});
91+
5992
test("runs the compiler and checker through the reactor", async () => {
6093
const WebAssembly = (globalThis as any).WebAssembly;
6194
const module = await WebAssembly.compile(

‎tsc/cmd/tsc/lsp_stdin_wasip1.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package main
44

55
import (
6+
"io"
67
"runtime"
78
"syscall"
89
)
@@ -12,6 +13,9 @@ type wasiStdin struct{}
1213
func (wasiStdin) Read(buffer []byte) (int, error) {
1314
for {
1415
n, err := syscall.Read(syscall.Stdin, buffer)
16+
if n == 0 && err == nil {
17+
return 0, io.EOF
18+
}
1519
if err != syscall.EAGAIN {
1620
return n, err
1721
}

0 commit comments

Comments
 (0)