Sign in

Andrew Branch

@andrewbran.ch
1.1K followers 108 following 52 posts

Working on the TypeScript API at Microsoft.

PostsRepliesMedia
Andrew Branch @andrewbran.ch · 15/08/2026
What’s next? Content mappers, I hope! github.com/microsoft/ty...
A Vue file with TS intellisense working, and the same errors showing in the CLI with `tsgo --runExternalCode`
160
Andrew Branch @andrewbran.ch · 15/08/2026
This simple example is running in the extension host, but you can imagine passing this connection to your own LSP server, serving additional completions or hovers in TS files by combining its own logic with type checker queries.
The demo extension working, logging "add, subtract" in a math.ts file
150
Andrew Branch @andrewbran.ch · 15/08/2026
Let's do something totally different. Here's a VS Code extension, which gets a sidecar API connection to the TypeScript LSP that was already running. All the same APIs are available, and now you can inspect the projects the user has open in their editor. Here we're logging the exports of .ts files.
A VS Code extension calling a cross-extension API to get a pipe to connect to the TS 7 LSP and construct an async API instance from it. A change handler gets the default TS project for the file and uses its program and checker to log the file's exports.
150
Andrew Branch @andrewbran.ch · 15/08/2026
So far, almost all of this code could be mistaken for the old TS 6 API. But under the hood, a lot of care has to go into pretty silly things like making sure when you get the same Type or Symbol twice, they have reference equality. That doesn't come for free anymore.
two results of getSymbolAtLocation are strict equal to each other
140
Andrew Branch @andrewbran.ch · 15/08/2026
Now for some transformation. The new AST is a hybrid of these "remote nodes" backed by binary data and normal JavaScript objects. The client can encode the result into the same binary format so the server can run it through the emitter, in this case. Here's a (dumb) codemod to fix our error.
A transforming visitor replacing a string literal "2" with a numeric literal 2 on the same file, then printing the transformed file to a string.
140
Andrew Branch @andrewbran.ch · 15/08/2026
To demonstrate the laziness of file decoding, we can see that after recursively visiting one of the two statements in the file, 10 of the 20 nodes were actually materialized. (The timings here look slower than they should because the VFS is not yet production quality.)
Metrics showing 20 nodes were fetched and 10 were materialized, with 5ms of IPC transport overhead
150
Andrew Branch @andrewbran.ch · 15/08/2026
Every node materializes into an instance of the same class, all reading the same underlying buffer. The TS types lie and give each node a specialized type. Here, we see `kind` reading its value from an offset computed by the node's index in the flattened AST and the kind field's offset.
Another debug view showing the getter for `kind`, reading a uint32 from the underlying buffer
260
Andrew Branch @andrewbran.ch · 15/08/2026
But how exactly does that work? The tsgo process encodes the AST in a compact binary format and sends it all over. The client's SourceFile type wraps the Uint8Array and exposes getters for every property you could want to access. Children and other properties are lazily decoded on first access.
A debug view inspecting the RemoteSourceFile type at runtime, showing a DataView with an underlying ArrayBuffer.
1110
Andrew Branch @andrewbran.ch · 15/08/2026
Slightly more interesting: let's navigate a source file and use the type checker on some stuff. This found the return type 'void' from `console.log` and the return type 'number' from `add`.
Recursively visiting each child of the second statement of main.ts, getting the signature of call expressions, and logging the return type of those signatures. Output shows void (belonging to console.log) and number (belonging to add)
1110
Andrew Branch @andrewbran.ch · 15/08/2026
Let’s start with something basic like getting diagnostics. Not too much to say about this. Each API call like this is communicating over STDIO with the tsgo process. We'll see some details in a minute.
console.logging program.getSemanticDiagnostics, showing an assignability error as a JS object
1181
Andrew Branch @andrewbran.ch · 15/08/2026
The API comes in sync and async flavors. You import from "unstable" for now, but it's not going to change architecturally before 7.1. Breaks will be mostly targeted renames/moves. You access programs through obtaining immutable snapshots. There will be more ways coming soon. More on this later.
A TS file creating a sync API instance from "typescript/unstable/sync" with a virtual file system, and obtaining a program, checker, and emitter.
1161
Andrew Branch @andrewbran.ch · 15/08/2026
If you’re not stalking TypeScript PRs, you might be surprised how much you can do with the API in nightlies (and even in 7.0 stable), so here’s a tiny demo that I think hits some of the highlights. 🧵
A package.json in VS Code after npm installing typescript@next
412620
Andrew Branch @andrewbran.ch · 06/07/2026
Hm uh oh
404 page screenshot
200