microsoft / microsoft/TypeScript

API usage patterns for complex editor extensions

Aperta
#63,800 56 commenti 110 reazioni 1 assegnatario Vedi su GitHub

@andrewbranch ci sta già lavorando.

Dal 18/2/2026.

Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

TypeScript 6.0 and earlier allowed loading third-party plugin code directly into TS Server, exposing language service and project system internals for proxying and patching. This was often used in coordination with editor extensions, sometimes in tandem with an additional custom language server, to provide language support for languages that embed or transform into TypeScript.

In the Go implementation, we can't dynamically link third-party code into the server process. Instead, we provide an IPC-based API and a TypeScript client library, currently under development, for communicating with the server. The API will expose most of the read-only features of today's `ts.Program` and `ts.TypeChecker` APIs; I want to use this issue to discuss and explore what other features the API needs in order to replace TS Server plugins.

## Case study: Vue

I'm going to present my very basic understanding of how Vue's language support integrates with TypeScript today. There are three components:

1. `@vue/typescript-plugin`, the TS Server plugin
2. `@vue/language-server`, an additional LSP server responsible mostly for serving requests in the template and CSS parts of `.vue` files
3. The VS Code extension that registers the TS plugin, spawns the Vue language server, and brokers communication between the two.

When the TS Server plugin initializes, it tells TypeScript to include the `.vue` extension in its projects and overrides TS Server's view of `.vue` file contents with valid TypeScript syntax. It then intercepts requests and responses for those files in order to map positions between locations in the original `.vue` file content and locations in the virtual TypeScript-only content. It also overrides module resolution to allow `import {} from "./MyComponent.vue"` to resolve, and `readDirectory` to include `.vue` files in path completions. Finally, it installs some custom request handlers that provide information from the TypeScript language service to `@vue/typescript-plugin`, proxied through the VS Code extension.

## Proposal

I'd like to get feedback on a system where, instead of making the TypeScript LSP server serve requests for non-TypeScript files directly, we let an auxiliary language server, through API usage, compute or proxy whatever information it needs from the TypeScript language service. Roughly how I imagine that looking:

- The Vue language server would tell the TypeScript API to open / keep open any projects for which it wants to include `.vue` files, and would "open" the virtual files, then continue to manage their state in a similar way in `textDocument/didChange` and `workspace/didChangeWatchedFiles` handlers.

```ts
const snapshot = await api.updateSnapshot({
openProject: projectTsconfigUri,
openFiles: projectVueFiles.map(file => ({
uri: `virtual://@vue/language-server/${file.basePath}`,
content: getServiceScriptContent(file),
scriptKind: ScriptKind.TS,
defaultProject: projectTsconfigUri,
version: file.version,
}))
})
```

- Language service handlers in the Vue language server could access these virtual files through `Program`/`TypeChecker` APIs for semantic analysis as well as initiate standard LSP requests for them.

```ts
connection.onCompletion(async (params) => {
const { uri, position } = params.textDocument;
using snapshot = await api.updateSnapshot();
const { project, checker } = await snapshot.getDefaultProjectForFile(uri);
const sourceFile = program.getSourceFile(uri);

const virtualFilePosition = mapToVirtualFilePosition(uri, position);
const node = getTokenAtPosition(sourceFile, virtualFilePosition);
const tsCompletions = await snapshot.lspRequest("textDocument/completion", {
textDocument: { uri },
position: virtualFilePosition,
});

if (shouldProvideSpecialVueCompletions(node)) {
const vueCompletions = await getVueCompletionsUsingSemanticAPIs(node, checker, params);
return [...tsCompletions, ...vueCompletions];
}
});
```

- For module resolution, we'd likely want to provide some kind of static mapping API that can be updated as needed—having callbacks into client code for module resolution is something we want to avoid. Whatever the format, it should cover module resolution, auto imports, and path completions—it seems silly that today's TS Server plugins can specify extra file extensions and external files but still have to patch `readDirectory` to get them to show up in path completions.

This approach shifts the responsibility and control for servicing LSP requests for non-TypeScript files to the language servers that actually own those files. With the exception of module resolution, most of what other language servers need from TypeScript falls pretty cleanly out of existing LSP functionality (opening and updating a virtual file is the same thing as opening an untitled file in an editor; we just need to add a project association) or existing/planned API functionality.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.