From 9b06534813667d83d461984ac92543075793042b Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Tue, 11 Nov 2025 02:10:42 +0000 Subject: [PATCH 1/2] Templating out the start of a docs update --- astro.config.ts | 34 ++- .../{core-concepts => about}/why-volar.mdx | 0 .../core-concepts/language-definition.mdx | 282 ++++++++++++++++++ .../docs/core-concepts/service-definition.mdx | 71 +++++ src/content/docs/guides/build-and-bundle.mdx | 262 ++++++++++++++++ src/content/docs/index.mdx | 2 +- src/content/docs/packages/cdn.mdx | 31 ++ src/content/docs/packages/coc-nvim.mdx | 53 ++++ src/content/docs/packages/code-gen.mdx | 45 +++ src/content/docs/packages/eslint.mdx | 46 +++ src/content/docs/packages/jsdelivr.mdx | 27 ++ src/content/docs/packages/kit.mdx | 42 +++ src/content/docs/packages/language-core.mdx | 65 ++++ src/content/docs/packages/language-hub.mdx | 45 +++ src/content/docs/packages/language-server.mdx | 64 ++++ .../docs/packages/language-service.mdx | 57 ++++ src/content/docs/packages/monaco.mdx | 45 +++ src/content/docs/packages/preview.mdx | 38 +++ src/content/docs/packages/shared.mdx | 35 +++ .../docs/packages/snapshot-document.mdx | 40 +++ src/content/docs/packages/source-map.mdx | 44 +++ src/content/docs/packages/test-utils.mdx | 38 +++ src/content/docs/packages/transforms.mdx | 35 +++ src/content/docs/packages/tsl-config.mdx | 35 +++ .../docs/packages/typescript-faster.mdx | 34 +++ .../packages/typescript-language-service.mdx | 40 +++ src/content/docs/packages/typescript.mdx | 43 +++ src/content/docs/packages/vscode.mdx | 45 +++ src/content/docs/reference/languages.mdx | 126 -------- src/content/docs/reference/services.mdx | 279 ----------------- src/content/docs/service-methods/index.mdx | 39 +++ .../provide-call-hierarchy-incoming-calls.mdx | 19 ++ .../provide-call-hierarchy-items.mdx | 20 ++ .../provide-call-hierarchy-outgoing-calls.mdx | 19 ++ .../service-methods/provide-code-actions.mdx | 41 +++ .../service-methods/provide-code-lens.mdx | 26 ++ .../provide-color-presentations.mdx | 22 ++ .../provide-completion-items.mdx | 44 +++ .../service-methods/provide-definition.mdx | 28 ++ .../provide-document-colors.mdx | 23 ++ .../provide-document-formatting-edits.mdx | 28 ++ .../provide-document-highlights.mdx | 26 ++ .../provide-document-links.mdx | 26 ++ ...rovide-document-range-formatting-edits.mdx | 25 ++ ...provide-document-semantic-tokens-edits.mdx | 16 + .../provide-document-semantic-tokens.mdx | 25 ++ .../provide-document-symbols.mdx | 26 ++ .../provide-folding-ranges.mdx | 19 ++ .../docs/service-methods/provide-hover.mdx | 34 +++ .../service-methods/provide-inlay-hints.mdx | 26 ++ .../provide-linked-editing-ranges.mdx | 20 ++ .../provide-on-type-formatting-edits.mdx | 24 ++ .../service-methods/provide-references.mdx | 28 ++ .../service-methods/provide-rename-edits.mdx | 30 ++ .../provide-selection-ranges.mdx | 19 ++ .../provide-signature-help.mdx | 32 ++ .../service-methods/resolve-code-action.mdx | 34 +++ .../service-methods/resolve-code-lens.mdx | 25 ++ .../resolve-completion-item.mdx | 35 +++ .../service-methods/resolve-document-link.mdx | 22 ++ .../service-methods/resolve-inlay-hint.mdx | 22 ++ src/content/docs/services/css.mdx | 49 +++ src/content/docs/services/emmet.mdx | 41 +++ src/content/docs/services/eslint.mdx | 41 +++ src/content/docs/services/html.mdx | 42 +++ src/content/docs/services/json.mdx | 41 +++ src/content/docs/services/markdown.mdx | 41 +++ src/content/docs/services/prettier.mdx | 56 ++++ src/content/docs/services/prettyhtml.mdx | 40 +++ src/content/docs/services/pug-beautify.mdx | 40 +++ src/content/docs/services/pug.mdx | 40 +++ src/content/docs/services/sass-formatter.mdx | 40 +++ src/content/docs/services/tslint.mdx | 41 +++ .../services/typescript-twoslash-queries.mdx | 41 +++ src/content/docs/services/typescript.mdx | 41 +++ src/content/docs/services/vetur.mdx | 40 +++ src/content/docs/services/yaml.mdx | 41 +++ .../{core-concepts => tools}/volar-labs-1.png | Bin .../{core-concepts => tools}/volar-labs.mdx | 0 79 files changed, 3081 insertions(+), 420 deletions(-) rename src/content/docs/{core-concepts => about}/why-volar.mdx (100%) create mode 100644 src/content/docs/core-concepts/language-definition.mdx create mode 100644 src/content/docs/core-concepts/service-definition.mdx create mode 100644 src/content/docs/guides/build-and-bundle.mdx create mode 100644 src/content/docs/packages/cdn.mdx create mode 100644 src/content/docs/packages/coc-nvim.mdx create mode 100644 src/content/docs/packages/code-gen.mdx create mode 100644 src/content/docs/packages/eslint.mdx create mode 100644 src/content/docs/packages/jsdelivr.mdx create mode 100644 src/content/docs/packages/kit.mdx create mode 100644 src/content/docs/packages/language-core.mdx create mode 100644 src/content/docs/packages/language-hub.mdx create mode 100644 src/content/docs/packages/language-server.mdx create mode 100644 src/content/docs/packages/language-service.mdx create mode 100644 src/content/docs/packages/monaco.mdx create mode 100644 src/content/docs/packages/preview.mdx create mode 100644 src/content/docs/packages/shared.mdx create mode 100644 src/content/docs/packages/snapshot-document.mdx create mode 100644 src/content/docs/packages/source-map.mdx create mode 100644 src/content/docs/packages/test-utils.mdx create mode 100644 src/content/docs/packages/transforms.mdx create mode 100644 src/content/docs/packages/tsl-config.mdx create mode 100644 src/content/docs/packages/typescript-faster.mdx create mode 100644 src/content/docs/packages/typescript-language-service.mdx create mode 100644 src/content/docs/packages/typescript.mdx create mode 100644 src/content/docs/packages/vscode.mdx delete mode 100644 src/content/docs/reference/languages.mdx delete mode 100644 src/content/docs/reference/services.mdx create mode 100644 src/content/docs/service-methods/index.mdx create mode 100644 src/content/docs/service-methods/provide-call-hierarchy-incoming-calls.mdx create mode 100644 src/content/docs/service-methods/provide-call-hierarchy-items.mdx create mode 100644 src/content/docs/service-methods/provide-call-hierarchy-outgoing-calls.mdx create mode 100644 src/content/docs/service-methods/provide-code-actions.mdx create mode 100644 src/content/docs/service-methods/provide-code-lens.mdx create mode 100644 src/content/docs/service-methods/provide-color-presentations.mdx create mode 100644 src/content/docs/service-methods/provide-completion-items.mdx create mode 100644 src/content/docs/service-methods/provide-definition.mdx create mode 100644 src/content/docs/service-methods/provide-document-colors.mdx create mode 100644 src/content/docs/service-methods/provide-document-formatting-edits.mdx create mode 100644 src/content/docs/service-methods/provide-document-highlights.mdx create mode 100644 src/content/docs/service-methods/provide-document-links.mdx create mode 100644 src/content/docs/service-methods/provide-document-range-formatting-edits.mdx create mode 100644 src/content/docs/service-methods/provide-document-semantic-tokens-edits.mdx create mode 100644 src/content/docs/service-methods/provide-document-semantic-tokens.mdx create mode 100644 src/content/docs/service-methods/provide-document-symbols.mdx create mode 100644 src/content/docs/service-methods/provide-folding-ranges.mdx create mode 100644 src/content/docs/service-methods/provide-hover.mdx create mode 100644 src/content/docs/service-methods/provide-inlay-hints.mdx create mode 100644 src/content/docs/service-methods/provide-linked-editing-ranges.mdx create mode 100644 src/content/docs/service-methods/provide-on-type-formatting-edits.mdx create mode 100644 src/content/docs/service-methods/provide-references.mdx create mode 100644 src/content/docs/service-methods/provide-rename-edits.mdx create mode 100644 src/content/docs/service-methods/provide-selection-ranges.mdx create mode 100644 src/content/docs/service-methods/provide-signature-help.mdx create mode 100644 src/content/docs/service-methods/resolve-code-action.mdx create mode 100644 src/content/docs/service-methods/resolve-code-lens.mdx create mode 100644 src/content/docs/service-methods/resolve-completion-item.mdx create mode 100644 src/content/docs/service-methods/resolve-document-link.mdx create mode 100644 src/content/docs/service-methods/resolve-inlay-hint.mdx create mode 100644 src/content/docs/services/css.mdx create mode 100644 src/content/docs/services/emmet.mdx create mode 100644 src/content/docs/services/eslint.mdx create mode 100644 src/content/docs/services/html.mdx create mode 100644 src/content/docs/services/json.mdx create mode 100644 src/content/docs/services/markdown.mdx create mode 100644 src/content/docs/services/prettier.mdx create mode 100644 src/content/docs/services/prettyhtml.mdx create mode 100644 src/content/docs/services/pug-beautify.mdx create mode 100644 src/content/docs/services/pug.mdx create mode 100644 src/content/docs/services/sass-formatter.mdx create mode 100644 src/content/docs/services/tslint.mdx create mode 100644 src/content/docs/services/typescript-twoslash-queries.mdx create mode 100644 src/content/docs/services/typescript.mdx create mode 100644 src/content/docs/services/vetur.mdx create mode 100644 src/content/docs/services/yaml.mdx rename src/content/docs/{core-concepts => tools}/volar-labs-1.png (100%) rename src/content/docs/{core-concepts => tools}/volar-labs.mdx (100%) diff --git a/astro.config.ts b/astro.config.ts index 8834a22..112bb11 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -32,26 +32,32 @@ export default defineConfig({ PageTitle: "./src/components/starlight/PageTitle.astro", }, sidebar: [ + { + label: "About", + autogenerate: { directory: "about" }, + }, { label: "Core Concepts", - items: [ - // Each item here is one entry in the navigation menu. - { label: "Why Volar?", link: "/core-concepts/why-volar" }, - { label: "Embedded Languages", link: "/core-concepts/embedded-languages" }, - { label: "Volar Labs", link: "/core-concepts/volar-labs" }, - ], - // TODO: Use `autogenerate` once it allows you to order the sidebar + autogenerate: { directory: "core-concepts" }, + }, + { + label: "Service Methods", + autogenerate: { directory: "service-methods" }, + collapsed: true, + }, + { + label: "Packages", + autogenerate: { directory: "packages" }, + collapsed: true, }, { - label: "Guides", - items: [ - { label: "Your First Volar Language Server", link: "/guides/first-server" }, - { label: "File Structure", link: "/guides/file-structure" }, - ], + label: "Services", + autogenerate: { directory: "services" }, + collapsed: true, }, { - label: "Reference", - autogenerate: { directory: "reference" }, + label: "Tools", + autogenerate: { directory: "tools" }, }, ], }), diff --git a/src/content/docs/core-concepts/why-volar.mdx b/src/content/docs/about/why-volar.mdx similarity index 100% rename from src/content/docs/core-concepts/why-volar.mdx rename to src/content/docs/about/why-volar.mdx diff --git a/src/content/docs/core-concepts/language-definition.mdx b/src/content/docs/core-concepts/language-definition.mdx new file mode 100644 index 0000000..1f1d045 --- /dev/null +++ b/src/content/docs/core-concepts/language-definition.mdx @@ -0,0 +1,282 @@ +--- +title: Language Definition +description: Define and implement Volar language plugins and VirtualCode, including all hooks, mappings, and best practices. +--- + +> This page is a work in progress. Interested in contributing some documentation to it, or want to improve it? [Edit this page on GitHub](https://github.com/volarjs/docs/blob/main/src/content/docs/core-concepts/language-definition.mdx) + +As can be expected from a framework for language servers, Volar allows you to define the languages you want to support in your project. + +## Shape of a language definition + +A language definition is a JavaScript object that contains a `createVirtualCode` and a `updateVirtualCode` function. + +```ts title="src/my-language.ts" +export const language = { + createVirtualCode(fileId, languageId, snapshot) { + // Create a virtual code object + }, + updateVirtualCode(_fileId, languageCode, snapshot) { + // Update the virtual code object + }, +}; +``` + +As the names suggests, those methods create and update a `VirtualCode`. A `VirtualCode` object is created for each file that your language server will handle. These can then be accessed in the hooks of the [services](/core-concepts/service-definition) that provides the features of your language server, as such they're also a place you can store additional information about the file that could be useful to know for the features of your services. + +Albeit not required, a common pattern is to define a JavaScript class that implements the `VirtualCode` interface, as this makes it easier to later add more properties and methods to the virtual code object and unlock the ability to use `instanceof` to check if a virtual code object is of a certain type. + +```ts title="src/my-language.ts" +import type { LanguagePlugin, VirtualCode } from '@volar/language-core'; + +export const language = { + createVirtualCode(fileId, languageId, snapshot) { + if (languageId !== 'my-language') + return; + + return new MyLanguageVirtualCode(snapshot); + }, + updateVirtualCode(_fileId, languageCode, snapshot) { + languageCode.update(snapshot); + return languageCode; + }, +} satisfies LanguagePlugin; + +export class MyLanguageVirtualCode implements VirtualCode { + id = 'root'; + languageId = 'my-language'; + mappings = [] + + constructor( + public snapshot: ts.IScriptSnapshot + ) { + this.onSnapshotUpdated(); + } + + public update(newSnapshot: ts.IScriptSnapshot) { + this.snapshot = newSnapshot; + this.onSnapshotUpdated(); + } + + onSnapshotUpdated() { + // Update the virtual code object + } +} +``` + +This is a simple example of a language definition, where `MyVirtualLanguageCode` only does the strict minimum possible. In a real language definition, you would most likely have a lot more properties and methods available on the `MyLanguageVirtualCode` class. + +### Embedded languages + +If your language supports [embedded languages](/core-concepts/embedded-languages/), your instance `VirtualCode` should include a `embeddedCodes` property that contains an array of `VirtualCode` instances for the embedded languages. + +```ts title="src/my-language.ts" ins={20, 34-52} collapse={1-14, 22-31} +import type { LanguagePlugin, VirtualCode } from '@volar/language-core'; + +export const language = { + createVirtualCode(fileId, languageId, snapshot) { + if (languageId !== 'my-language') + return; + + return new MyLanguageVirtualCode(snapshot); + }, + updateVirtualCode(_fileId, languageCode, snapshot) { + languageCode.update(snapshot); + return languageCode; + }, +} satisfies LanguagePlugin; + +export class MyLanguageVirtualCode implements VirtualCode { + id = 'root'; + languageId = 'my-language'; + mappings = [] + embeddedCodes: VirtualCode[] = [] + + constructor( + public snapshot: ts.IScriptSnapshot + ) { + this.onSnapshotUpdated(); + } + + public update(newSnapshot: ts.IScriptSnapshot) { + this.snapshot = newSnapshot; + this.onSnapshotUpdated(); + } + + onSnapshotUpdated() { + const snapshotContent = this.snapshot.getText(0, this.snapshot.getLength()); + + // Find embedded languages + const embeddedLanguages = findEmbeddedLanguages(snapshotContent); + + // Create virtual code objects for embedded languages + this.embeddedCodes = embeddedLanguages.map(embeddedLanguage => { + return { + id: embeddedLanguage.id, + languageId: embeddedLanguage.languageId, + mappings: [], + snapshot: { + getText: (start, end) => embeddedLanguage.content.substring(start, end), + getLength: () => embeddedLanguage.content.length, + getChangeRange: () => undefined, + } + } + }); + } +} +``` + +## Full LanguagePlugin hooks + +While only two methods are required, Volar's language plugin commonly implements three hooks: + +- `getLanguageId(fileUri)?: string | undefined` + - Return a language id (e.g. `"vue"`, `"svelte"`, `"html1"`) for `fileUri`, or `undefined` to skip. + - Use this to opt files in/out by extension, shebang, or contents. +- `createVirtualCode(fileUri, languageId, snapshot): VirtualCode | undefined` + - Create the root `VirtualCode` for a file. Build initial mappings and any embedded `VirtualCode`s. +- `updateVirtualCode(fileUri, languageCode, snapshot): VirtualCode` + - Incrementally update the existing `VirtualCode` to reflect a new snapshot, then return it. + +Notes: +- `snapshot` is immutable for a given version. Build all parsing and indexing on the snapshot text. +- Keep updates incremental; avoid full re-parses if you can track deltas. + +## The VirtualCode contract + +Your `VirtualCode` should implement the following essential properties: + +- `id: string` + - Unique within the file. The root is commonly `"root"`. +- `languageId: string` + - The language id of the root or embedded segment (`"my-language"`, `"html"`, `"css"`, etc.). +- `snapshot: ts.IScriptSnapshot` + - Provides stable reads via `getText(start, end)` and `getLength()`. +- `embeddedCodes?: VirtualCode[]` + - Child segments for embedded languages (e.g., HTML template + CSS block). +- `mappings?: Array<{ sourceRange: [number, number]; mappedRange: [number, number]; data?: unknown }>` + - Optional raw mappings that your plugin feeds into higher-level source-mapping utilities. + +Tip: You can store additional parsed state on the instance (AST, symbol tables, indexes) keyed by the current snapshot to speed up service methods. + +## Mappings and source maps + +Mappings connect source text offsets to generated text offsets for embedded/virtual files and vice versa. Accurate mappings are the foundation for “jump to definition,” “rename,” “highlight,” etc. + +- Use `@volar/source-map` to build and query mappings reliably. +- Prefer coarse-enough segments to avoid mapping explosions. +- Attach capability flags in mapping `data` to signal which features should traverse a mapping. + +See: [@volar/source-map](/packages/source-map), [@volar/code-gen](/packages/code-gen), [@volar/transforms](/packages/transforms). + +## Implementing getLanguageId + +Use `getLanguageId` to rout files to your plugin: + +```ts +getLanguageId(uri) { + if (uri.path.endsWith('.html1')) return 'html1'; +} +``` + +Avoid expensive I/O; for content sniffing, consider a cheap prefix/marker check from a lightweight cache. + +## Implementing createVirtualCode + +Parse the snapshot, build initial state and mappings, and create any embedded `VirtualCode`s: + +```ts +createVirtualCode(uri, languageId, snapshot) { + if (languageId !== 'my-language') return; + const code = new MyLanguageVirtualCode(snapshot); + // Build mappings for template / style regions, if any + code.embeddedCodes = [ + /* e.g., HTML virtual code, CSS virtual code */ + ]; + return code; +} +``` + +## Implementing updateVirtualCode + +Keep it incremental when possible: + +```ts +updateVirtualCode(uri, languageCode, snapshot) { + if (languageCode.snapshot === snapshot) return languageCode; // no-op + languageCode.update(snapshot); // recompute mappings/embeds that changed + return languageCode; +} +``` + +## Best practices + +- Performance + - Cache parse results and indexes per snapshot version. + - Recompute only affected regions on update. + - Keep mapping segments focused; avoid 1:1 character mappings unless necessary. +- Correctness + - Always operate on the provided snapshot; never read from the file system directly. + - Keep `embeddedCodes` synchronized with source regions on each update. + - Normalize URIs (use consistent schemes like `file://`). +- Debuggability + - Add a `name` to your service (in the service layer) and log mapping spans during development. + - Provide toggles to dump virtual files for inspection. + +## Example: language with HTML and CSS embeds + +```ts +export class HtmlLikeCode implements VirtualCode { + id = 'root'; + languageId = 'htmllike'; + embeddedCodes = []; + constructor(public snapshot: ts.IScriptSnapshot) { + this.onSnapshotUpdated(); + } + update(s: ts.IScriptSnapshot) { + this.snapshot = s; + this.onSnapshotUpdated(); + } + private onSnapshotUpdated() { + const text = this.snapshot.getText(0, this.snapshot.getLength()); + const { templateRange, styleRange } = parseRegions(text); + this.embeddedCodes = []; + if (templateRange) { + this.embeddedCodes.push(new HtmlEmbeddedCode(this.snapshot, templateRange)); + } + if (styleRange) { + this.embeddedCodes.push(new CssEmbeddedCode(this.snapshot, styleRange)); + } + } +} +``` + +Pair this with first-party services: +- HTML: `volar-service-html` +- CSS: `volar-service-css` + +## Interplay with Services + +Language plugins define structure (files, embeds, mappings). Services implement features (completion, hover, formatting, etc.). Services rely on your plugin’s mappings to translate positions correctly. + +- Learn service method semantics in [Service Methods](/service-methods/). +- Use purpose-built services (HTML/CSS/TS/JSON/YAML) where possible, and write custom services for language-specific logic. + +## Troubleshooting + +- Features act on the wrong text + - Check mapping direction and off-by-one offsets; ensure ranges are half-open `[start, end)`. +- Embedded regions not updating + - Ensure `updateVirtualCode` recomputes `embeddedCodes` when the snapshot changes. +- Poor performance with large files + - Cache parse results per snapshot; avoid full re-parse on every change. + - Reduce mapping granularity; prefer region-level segments. + +## Related reading + +- VS Code API (extension environment, client-side expectations): https://code.visualstudio.com/api +- LSP server/client libraries for VS Code: https://github.com/microsoft/vscode-languageserver-node +- Source mapping and transforms: + - [@volar/source-map](/packages/source-map) + - [@volar/code-gen](/packages/code-gen) + - [@volar/transforms](/packages/transforms) diff --git a/src/content/docs/core-concepts/service-definition.mdx b/src/content/docs/core-concepts/service-definition.mdx new file mode 100644 index 0000000..6290525 --- /dev/null +++ b/src/content/docs/core-concepts/service-definition.mdx @@ -0,0 +1,71 @@ +--- +title: Service Definition +description: A list of available services in VolarJS and how to use them. +--- + +Services are the building blocks of Volar language servers. They are responsible for providing all the features you'd expect in a language server, completions, diagnostics, hover information, you name it. + +While services are technically not tied to individual languages, they are often designed to work with a specific language or file type, including [embedded languages](/core-concepts/embedded-languages). + +For example, if you were to implement a Volar language server for HTML, you would likely end up with three different services, one for general HTML support, one for CSS support and one for JavaScript support, the latter two being used for `