microsoft / microsoft/TypeScript

API usage patterns for complex editor extensions

Offen
#63,800 56 Kommentare 110 Reaktionen 1 zugewiesene Person Auf GitHub ansehen

@andrewbranch arbeitet bereits daran.

Seit 18.2.2026.

Vorherrschende Sprache
Go
Sterne
111k
Forks
14.3k
Ø Merge
2 T. 4 Std.
Gemergte PRs (30 T.)
132

Beschreibung

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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.