@valbuild/language-server 0.98.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +7 -0
- package/bin.js +3 -0
- package/dist/declarations/src/EditorFsHost.d.ts +30 -0
- package/dist/declarations/src/ValProject.d.ts +76 -0
- package/dist/declarations/src/codeActions.d.ts +26 -0
- package/dist/declarations/src/completionContext.d.ts +77 -0
- package/dist/declarations/src/completions.d.ts +50 -0
- package/dist/declarations/src/diagnostics.d.ts +105 -0
- package/dist/declarations/src/index.d.ts +13 -0
- package/dist/declarations/src/modulePathMap.d.ts +60 -0
- package/dist/declarations/src/protocol.d.ts +200 -0
- package/dist/declarations/src/publicValFiles.d.ts +29 -0
- package/dist/declarations/src/server.d.ts +47 -0
- package/dist/declarations/src/uri.d.ts +28 -0
- package/dist/declarations/src/valModulesRegistry.d.ts +32 -0
- package/dist/declarations/src/version.d.ts +10 -0
- package/dist/valbuild-language-server.cjs.d.ts +2 -0
- package/dist/valbuild-language-server.cjs.dev.js +2497 -0
- package/dist/valbuild-language-server.cjs.js +7 -0
- package/dist/valbuild-language-server.cjs.prod.js +2497 -0
- package/dist/valbuild-language-server.esm.js +2451 -0
- package/package.json +53 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2025 Fredrik Ekholdt and Blank AS
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/bin.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IValFSHost } from "@valbuild/server";
|
|
2
|
+
/**
|
|
3
|
+
* Access to the editor's in-memory view of a file.
|
|
4
|
+
*
|
|
5
|
+
* An editor holds unsaved ("dirty") buffers that differ from disk. Validation
|
|
6
|
+
* must see what the user is looking at, not what was last saved, so every read
|
|
7
|
+
* goes through this first.
|
|
8
|
+
*/
|
|
9
|
+
export type OpenDocuments = {
|
|
10
|
+
/**
|
|
11
|
+
* Latest editor content for an absolute filesystem path, or `undefined` if the
|
|
12
|
+
* file is not open in the editor.
|
|
13
|
+
*/
|
|
14
|
+
read(fsPath: string): string | undefined;
|
|
15
|
+
};
|
|
16
|
+
/** An {@link OpenDocuments} backed by a plain map. Useful for tests. */
|
|
17
|
+
export declare function mapOpenDocuments(entries?: Map<string, string>): OpenDocuments & {
|
|
18
|
+
set(fsPath: string, content: string): void;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* An `IValFSHost` that overlays the editor's unsaved buffers on top of the real
|
|
22
|
+
* filesystem.
|
|
23
|
+
*
|
|
24
|
+
* `IValFSHost` is the filesystem seam that `createService`, `loadValModules`
|
|
25
|
+
* and `ValSourceFileHandler` all read through, so overriding it here is what
|
|
26
|
+
* makes Val evaluate the user's *current* editor state. Everything else
|
|
27
|
+
* delegates to `ts.sys`, exactly like the default host in
|
|
28
|
+
* `@valbuild/server`'s `createService`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createEditorFsHost(open: OpenDocuments): IValFSHost;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ModuleFilePath } from "@valbuild/core";
|
|
2
|
+
import type { SchemaSourceSnapshot } from "@valbuild/shared/internal";
|
|
3
|
+
import { type Service } from "@valbuild/server";
|
|
4
|
+
import { type OpenDocuments } from "./EditorFsHost.js";
|
|
5
|
+
/**
|
|
6
|
+
* A single Val root, and everything needed to evaluate its modules.
|
|
7
|
+
*
|
|
8
|
+
* One of these per Val root — never one shared across roots. Different roots in
|
|
9
|
+
* a monorepo can pin different versions of Val, and each gets its own
|
|
10
|
+
* `Service` (and therefore its own evaluated `val.modules` and module cache).
|
|
11
|
+
*/
|
|
12
|
+
/** What `Service.get` returns; re-declared to avoid depending on an internal type. */
|
|
13
|
+
export type ValModuleContent = Awaited<ReturnType<Service["get"]>>;
|
|
14
|
+
export type ValProjectInitError = {
|
|
15
|
+
/**
|
|
16
|
+
* - `no-config` — no tsconfig.json/jsconfig.json at the Val root.
|
|
17
|
+
* - `missing-core` — `@valbuild/core` is not resolvable from the Val root.
|
|
18
|
+
* - `service-failed` — anything else that stopped the service starting.
|
|
19
|
+
*/
|
|
20
|
+
code: "no-config" | "missing-core" | "service-failed";
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
export type ValProject = {
|
|
24
|
+
readonly valRoot: string;
|
|
25
|
+
/**
|
|
26
|
+
* Evaluate a module and return its schema, source and validation errors.
|
|
27
|
+
* Resolves to an init error instead of throwing if the project could not be
|
|
28
|
+
* set up at all.
|
|
29
|
+
*/
|
|
30
|
+
getModule(moduleFilePath: ModuleFilePath, options?: {
|
|
31
|
+
validate: boolean;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
status: "ok";
|
|
34
|
+
content: ValModuleContent;
|
|
35
|
+
cached: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
status: "error";
|
|
38
|
+
error: ValProjectInitError;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Schemas and sources for every Val module in the project.
|
|
42
|
+
*
|
|
43
|
+
* Needed for anything that has to look across modules: resolving `keyOf` and
|
|
44
|
+
* `route` validation, and offering route/key completions. Built once and then
|
|
45
|
+
* updated per module, so an edit costs one re-evaluation rather than N.
|
|
46
|
+
*/
|
|
47
|
+
getSnapshot(): Promise<{
|
|
48
|
+
status: "ok";
|
|
49
|
+
snapshot: SchemaSourceSnapshot;
|
|
50
|
+
} | {
|
|
51
|
+
status: "error";
|
|
52
|
+
error: ValProjectInitError;
|
|
53
|
+
}>;
|
|
54
|
+
/** Val module file paths found under the Val root. */
|
|
55
|
+
listModuleFilePaths(): ModuleFilePath[];
|
|
56
|
+
/** Drop cached results. Pass a path to invalidate one module. */
|
|
57
|
+
invalidate(moduleFilePath?: ModuleFilePath): void;
|
|
58
|
+
/** Number of cached module results — for tests and diagnostics. */
|
|
59
|
+
cacheSize(): number;
|
|
60
|
+
dispose(): Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Whether `@valbuild/core` is resolvable from a Val root.
|
|
64
|
+
*
|
|
65
|
+
* Injectable because jest's module registry intercepts `createRequire` and
|
|
66
|
+
* resolves against the repo regardless of the base path given, so the real
|
|
67
|
+
* implementation always reports success under test.
|
|
68
|
+
*/
|
|
69
|
+
export type CoreResolver = (valRoot: string) => boolean;
|
|
70
|
+
export declare const defaultCoreResolver: CoreResolver;
|
|
71
|
+
export declare function createValProject({ valRoot, open, isCoreResolvable, }: {
|
|
72
|
+
valRoot: string;
|
|
73
|
+
open: OpenDocuments;
|
|
74
|
+
/** Override for tests; see {@link CoreResolver}. */
|
|
75
|
+
isCoreResolvable?: CoreResolver;
|
|
76
|
+
}): ValProject;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ValidationFix } from "@valbuild/core";
|
|
2
|
+
import { CodeAction, type Diagnostic, type TextEdit } from "vscode-languageserver";
|
|
3
|
+
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
4
|
+
import type { ValModuleContent } from "./ValProject.js";
|
|
5
|
+
export declare function isLocalFix(fix: string): fix is ValidationFix;
|
|
6
|
+
/**
|
|
7
|
+
* Build quick fixes for the diagnostics the client sent back.
|
|
8
|
+
*
|
|
9
|
+
* The client returns our `Diagnostic.data` verbatim, which is where the source
|
|
10
|
+
* path and available fixes come from — no re-deriving them from a code string.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createValCodeActions({ document, diagnostics, content, valRoot, remoteHost, }: {
|
|
13
|
+
document: TextDocument;
|
|
14
|
+
diagnostics: Diagnostic[];
|
|
15
|
+
content: ValModuleContent;
|
|
16
|
+
valRoot: string;
|
|
17
|
+
remoteHost?: string;
|
|
18
|
+
}): Promise<CodeAction[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Narrow an edit down to the region that actually changed.
|
|
21
|
+
*
|
|
22
|
+
* A whole-document replacement would work, but it moves the cursor and shows up
|
|
23
|
+
* as a full-file change in review. Trimming the common prefix and suffix keeps
|
|
24
|
+
* the edit tight without needing a real diff algorithm.
|
|
25
|
+
*/
|
|
26
|
+
export declare function minimalTextEdit(before: string, after: string, document: TextDocument): TextEdit | undefined;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Works out what the cursor is sitting in, so completions can be offered for it.
|
|
4
|
+
*
|
|
5
|
+
* AST-based rather than text/regex-based: `c.image(` can be nested, wrapped or
|
|
6
|
+
* multi-line, and matching on text gets that wrong in exactly the cases where a
|
|
7
|
+
* user most wants help.
|
|
8
|
+
*/
|
|
9
|
+
export type ValCompletionContext = ValFileRefContext | ValStringValueContext;
|
|
10
|
+
/** The cursor is inside a plain string in the module's content. */
|
|
11
|
+
export type ValStringValueContext = {
|
|
12
|
+
kind: "string-value";
|
|
13
|
+
currentText: string;
|
|
14
|
+
contentStart: number;
|
|
15
|
+
contentEnd: number;
|
|
16
|
+
/**
|
|
17
|
+
* True when the string is an object key rather than a value.
|
|
18
|
+
*
|
|
19
|
+
* The distinction decides which schema to consult: a value is described by the
|
|
20
|
+
* schema at its own path, whereas a key is described by its *container* — a
|
|
21
|
+
* gallery record, for instance, is keyed by file reference.
|
|
22
|
+
*/
|
|
23
|
+
isPropertyName: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Name of the property this string is the value of, when it is one.
|
|
26
|
+
*
|
|
27
|
+
* Used for structures Val treats as opaque: a richtext link node is a plain
|
|
28
|
+
* object with `href`, so there is no schema at that path to consult.
|
|
29
|
+
*/
|
|
30
|
+
valueOfProperty?: string;
|
|
31
|
+
};
|
|
32
|
+
export type ValFileRefContext = {
|
|
33
|
+
kind: "file-ref";
|
|
34
|
+
/** Which constructor: `c.image(...)` or `c.file(...)`. */
|
|
35
|
+
subType: "image" | "file";
|
|
36
|
+
/** The string literal being edited, without quotes. */
|
|
37
|
+
currentText: string;
|
|
38
|
+
/** Offsets of the string literal's contents, excluding the quotes. */
|
|
39
|
+
contentStart: number;
|
|
40
|
+
contentEnd: number;
|
|
41
|
+
/**
|
|
42
|
+
* Start offset of the reference argument, including its opening quote.
|
|
43
|
+
*
|
|
44
|
+
* Stable while the user types to filter the completion list, because every
|
|
45
|
+
* such keystroke lands *inside* the literal. That makes it the anchor
|
|
46
|
+
* {@link findFileRefArgument} re-locates the call by at resolve time.
|
|
47
|
+
*/
|
|
48
|
+
refArgStart: number;
|
|
49
|
+
/** End offset of the reference argument, where a metadata argument follows. */
|
|
50
|
+
refArgEnd: number;
|
|
51
|
+
/** Offsets of an existing metadata argument, when there is one. */
|
|
52
|
+
metadataStart?: number;
|
|
53
|
+
metadataEnd?: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Find the innermost `c.image(...)` / `c.file(...)` call whose first argument
|
|
57
|
+
* contains `offset`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getValCompletionContext(sourceFile: ts.SourceFile, offset: number): ValCompletionContext | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Re-find the `c.image(...)` / `c.file(...)` call whose reference argument starts
|
|
62
|
+
* at `refArgStart`, and report where its arguments are *now*.
|
|
63
|
+
*
|
|
64
|
+
* `completionItem/resolve` runs against a document the user may have typed into
|
|
65
|
+
* since the list was computed, so the offsets captured back then have moved.
|
|
66
|
+
* Applying them anyway inserts the metadata object into the middle of the string
|
|
67
|
+
* literal and corrupts the file, so the offsets are re-derived here instead.
|
|
68
|
+
*
|
|
69
|
+
* Returns `undefined` when no such call is found — the document changed in some
|
|
70
|
+
* way this anchor does not survive, and the caller must then offer no edit
|
|
71
|
+
* rather than a wrong one.
|
|
72
|
+
*/
|
|
73
|
+
export declare function findFileRefArgument(sourceFile: ts.SourceFile, refArgStart: number): {
|
|
74
|
+
refArgEnd: number;
|
|
75
|
+
metadataStart?: number;
|
|
76
|
+
metadataEnd?: number;
|
|
77
|
+
} | undefined;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type SchemaSourceSnapshot } from "@valbuild/shared/internal";
|
|
2
|
+
import { CompletionItem } from "vscode-languageserver";
|
|
3
|
+
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
4
|
+
import type { PublicValFiles } from "./publicValFiles.js";
|
|
5
|
+
/**
|
|
6
|
+
* Completions for file and image references.
|
|
7
|
+
*
|
|
8
|
+
* Offers the files that actually exist under the project's files directory, and
|
|
9
|
+
* — when the item is accepted — fills in the metadata argument by reading the
|
|
10
|
+
* chosen file. Getting width/height/mimeType right by hand is tedious and a
|
|
11
|
+
* frequent source of the very validation errors this server reports.
|
|
12
|
+
*/
|
|
13
|
+
/** Stashed on the item so `resolve` can do the expensive work lazily. */
|
|
14
|
+
export type ValCompletionItemData = {
|
|
15
|
+
kind: "file-ref";
|
|
16
|
+
uri: string;
|
|
17
|
+
/** Val-style ref of the chosen file. */
|
|
18
|
+
ref: string;
|
|
19
|
+
/** Absolute path of the chosen file. */
|
|
20
|
+
filePath: string;
|
|
21
|
+
subType: "image" | "file";
|
|
22
|
+
/** Where a metadata argument goes, or what it replaces. */
|
|
23
|
+
/**
|
|
24
|
+
* Start offset of the reference argument, used to re-find the call at resolve
|
|
25
|
+
* time. Offsets captured now cannot be replayed later: see
|
|
26
|
+
* {@link findFileRefArgument}.
|
|
27
|
+
*/
|
|
28
|
+
refArgStart: number;
|
|
29
|
+
};
|
|
30
|
+
export declare function createValCompletions({ document, offset, files, moduleFilePath, snapshot, }: {
|
|
31
|
+
document: TextDocument;
|
|
32
|
+
offset: number;
|
|
33
|
+
files: PublicValFiles;
|
|
34
|
+
/** This module's path, needed to look its schema up in the snapshot. */
|
|
35
|
+
moduleFilePath?: string;
|
|
36
|
+
/** Project-wide schemas and sources, for schema-driven completions. */
|
|
37
|
+
snapshot?: SchemaSourceSnapshot;
|
|
38
|
+
}): CompletionItem[];
|
|
39
|
+
/**
|
|
40
|
+
* Fill in the metadata argument for an accepted file reference.
|
|
41
|
+
*
|
|
42
|
+
* Done at resolve time because it reads the file from disk, and an editor
|
|
43
|
+
* requests completions far more often than it accepts one.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveValCompletion({ item, documents, }: {
|
|
46
|
+
item: CompletionItem;
|
|
47
|
+
documents: {
|
|
48
|
+
get(uri: string): TextDocument | undefined;
|
|
49
|
+
};
|
|
50
|
+
}): Promise<CompletionItem>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type ModuleFilePath, type ValidationFix } from "@valbuild/core";
|
|
2
|
+
import { type SchemaSourceSnapshot } from "@valbuild/shared/internal";
|
|
3
|
+
import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
|
|
4
|
+
import type { ValModuleContent } from "./ValProject.js";
|
|
5
|
+
/** Marks diagnostics as ours, so a client can filter on it. */
|
|
6
|
+
export declare const VAL_DIAGNOSTIC_SOURCE = "val";
|
|
7
|
+
/**
|
|
8
|
+
* Every diagnostic this server can produce.
|
|
9
|
+
*
|
|
10
|
+
* One naming convention, deliberately: `val/` followed by kebab-case. The
|
|
11
|
+
* VS Code extension this replaces had accumulated three conventions at once
|
|
12
|
+
* (`file-not-found`, `val:missing-module`, `image:add-to-gallery`), which made
|
|
13
|
+
* codes impossible to match on reliably.
|
|
14
|
+
*
|
|
15
|
+
* These are *diagnostic* codes and are distinct from *fix* names, which come
|
|
16
|
+
* from `ValidationFix` in `@valbuild/core` and keep Val's own `image:`/`file:`
|
|
17
|
+
* vocabulary. A diagnostic says what is wrong; a fix says what can be done
|
|
18
|
+
* about it, and travels in {@link ValDiagnosticData.fixes}.
|
|
19
|
+
*/
|
|
20
|
+
export declare const VAL_DIAGNOSTIC_CODES: readonly ["val/validation", "val/schema", "val/fatal", "val/file-not-found", "val/missing-module"];
|
|
21
|
+
export type ValDiagnosticCode = (typeof VAL_DIAGNOSTIC_CODES)[number];
|
|
22
|
+
/**
|
|
23
|
+
* Structured payload attached to every Val diagnostic.
|
|
24
|
+
*
|
|
25
|
+
* Carried in `Diagnostic.data` (LSP 3.16), which is round-tripped back to the
|
|
26
|
+
* server on `textDocument/codeAction`. This replaces encoding information into
|
|
27
|
+
* the diagnostic's `code` string and parsing it out again — that approach could
|
|
28
|
+
* not carry anything but strings and broke whenever the format shifted.
|
|
29
|
+
*/
|
|
30
|
+
export type ValDiagnosticData = {
|
|
31
|
+
code: ValDiagnosticCode;
|
|
32
|
+
/** Full source path the problem was reported at. */
|
|
33
|
+
sourcePath: string;
|
|
34
|
+
/**
|
|
35
|
+
* Fixes Val says are available, from `ValidationFix` in `@valbuild/core` —
|
|
36
|
+
* so this always reflects the installed Val rather than a list copied into a
|
|
37
|
+
* client, which is how the previous list drifted to 13 of 18 fixes.
|
|
38
|
+
*/
|
|
39
|
+
fixes?: ValidationFix[];
|
|
40
|
+
/** Fix-specific payload, for example the offending value or route. */
|
|
41
|
+
value?: unknown;
|
|
42
|
+
/** Absolute path of the missing file, for `val/file-not-found`. */
|
|
43
|
+
filePath?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Severity policy, in one place.
|
|
47
|
+
*
|
|
48
|
+
* - **Warning** — Val can fix it automatically. Mirrors the CLI, which prints
|
|
49
|
+
* these with `⚠` (its `validation-fixable-error` event) rather than `✘`. In an
|
|
50
|
+
* editor a one-click-fixable metadata mismatch should not shout as loudly as a
|
|
51
|
+
* type error.
|
|
52
|
+
* - **Error** — everything else: the content is wrong, the schema is wrong, or
|
|
53
|
+
* the module does not work at all.
|
|
54
|
+
*
|
|
55
|
+
* Note that `val validate` still exits non-zero for fixable errors, so Warning
|
|
56
|
+
* here is about presentation, not about the problem being optional.
|
|
57
|
+
*/
|
|
58
|
+
export declare function severityFor({ code, fixes, }: {
|
|
59
|
+
code: ValDiagnosticCode;
|
|
60
|
+
fixes?: ValidationFix[];
|
|
61
|
+
}): DiagnosticSeverity;
|
|
62
|
+
export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, }: {
|
|
63
|
+
moduleFilePath: ModuleFilePath;
|
|
64
|
+
content: ValModuleContent;
|
|
65
|
+
/** Current text of the module, as the editor sees it. */
|
|
66
|
+
text: string;
|
|
67
|
+
/**
|
|
68
|
+
* Val root, used to resolve file references. When omitted, file existence is
|
|
69
|
+
* not checked.
|
|
70
|
+
*/
|
|
71
|
+
valRoot?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Project-wide schemas and sources.
|
|
74
|
+
*
|
|
75
|
+
* `keyOf` and `route` validation cannot be completed by core alone — it has to
|
|
76
|
+
* look at other modules — so core emits a placeholder error carrying the fix
|
|
77
|
+
* name and a developer-facing message. Given a snapshot, those are resolved
|
|
78
|
+
* here: valid references drop out, invalid ones become real messages. Without
|
|
79
|
+
* one they are suppressed, since showing the placeholder would put
|
|
80
|
+
* unactionable noise on correct code.
|
|
81
|
+
*/
|
|
82
|
+
snapshot?: SchemaSourceSnapshot;
|
|
83
|
+
}): Diagnostic[];
|
|
84
|
+
/**
|
|
85
|
+
* Diagnostic for a project that could not be evaluated at all.
|
|
86
|
+
*
|
|
87
|
+
* `createService` evaluates the whole `val.modules` graph, so one module that
|
|
88
|
+
* throws stops every module from being evaluated — as do a missing tsconfig and
|
|
89
|
+
* an unresolvable `@valbuild/core`. Reporting it on the file the user is looking
|
|
90
|
+
* at is imprecise, but the alternative is that all Val diagnostics silently
|
|
91
|
+
* disappear the moment a project stops evaluating.
|
|
92
|
+
*/
|
|
93
|
+
export declare function createProjectErrorDiagnostic({ moduleFilePath, message, }: {
|
|
94
|
+
moduleFilePath: ModuleFilePath;
|
|
95
|
+
message: string;
|
|
96
|
+
}): Diagnostic;
|
|
97
|
+
/**
|
|
98
|
+
* Diagnostic for a Val module that is not registered in `val.modules`.
|
|
99
|
+
*
|
|
100
|
+
* Val only serves modules listed there, so an unregistered module silently does
|
|
101
|
+
* nothing — worth surfacing even though it is not a validation error.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createMissingModuleDiagnostic({ moduleFilePath, }: {
|
|
104
|
+
moduleFilePath: ModuleFilePath;
|
|
105
|
+
}): Diagnostic;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { main, createValLanguageServer, type ValSession } from "./server.js";
|
|
2
|
+
export { getLanguageServerVersion } from "./version.js";
|
|
3
|
+
export { createEditorFsHost, mapOpenDocuments, type OpenDocuments, } from "./EditorFsHost.js";
|
|
4
|
+
export { createValProject, defaultCoreResolver, type CoreResolver, type ValProject, type ValProjectInitError, type ValModuleContent, } from "./ValProject.js";
|
|
5
|
+
export { createValDiagnostics, createMissingModuleDiagnostic, createProjectErrorDiagnostic, severityFor, VAL_DIAGNOSTIC_SOURCE, VAL_DIAGNOSTIC_CODES, type ValDiagnosticCode, type ValDiagnosticData, } from "./diagnostics.js";
|
|
6
|
+
export { findRegisteredModuleSpecifiers, isModuleRegistered, } from "./valModulesRegistry.js";
|
|
7
|
+
export { createValCodeActions, isLocalFix, minimalTextEdit, } from "./codeActions.js";
|
|
8
|
+
export { createValCompletions, resolveValCompletion, type ValCompletionItemData, } from "./completions.js";
|
|
9
|
+
export { getValCompletionContext, type ValCompletionContext, type ValFileRefContext, type ValStringValueContext, } from "./completionContext.js";
|
|
10
|
+
export { createPublicValFiles, DEFAULT_FILES_DIRECTORY, type PublicValFile, type PublicValFiles, } from "./publicValFiles.js";
|
|
11
|
+
export { createModulePathMap, findModulePathAtPosition, getModulePathRange, type ModulePathMap, type ModulePathRange, type ModulePosition, } from "./modulePathMap.js";
|
|
12
|
+
export { isValModuleUri, pathToUri, toModuleFilePath, uriToPath } from "./uri.js";
|
|
13
|
+
export { PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, VAL_FEATURES, VAL_PICK_REQUEST, VAL_INPUT_REQUEST, negotiateProtocolVersion, type ProtocolVersionRange, type ProtocolNegotiationResult, type ValClientCapabilities, type ValClientInfo, type ValEnvOverrides, type ValFeature, type ValInitializationOptions, type ValInputParams, type ValInputResult, type ValPickItem, type ValPickParams, type ValPickResult, type ValServerCapabilities, } from "./protocol.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { type ModulePath } from "@valbuild/core";
|
|
3
|
+
/**
|
|
4
|
+
* Maps Val module paths onto positions in the module's TypeScript source.
|
|
5
|
+
*
|
|
6
|
+
* Validation errors come back keyed by `SourcePath` (for example
|
|
7
|
+
* `/content/page.val.ts?p="hero"."image"`), but an editor needs a line/column
|
|
8
|
+
* range. This walks the source expression passed to `c.define(...)` and records
|
|
9
|
+
* where each addressable path lands in the file.
|
|
10
|
+
*
|
|
11
|
+
* This is version-sensitive by nature: it encodes how Val's module paths
|
|
12
|
+
* correspond to source syntax, which is why it lives with Val rather than in an
|
|
13
|
+
* editor extension.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: `@valbuild/server` has a near-identical `modulePathMap.ts`, used for the
|
|
16
|
+
* CLI's code frames and the Val UI. This copy differs in locating the source
|
|
17
|
+
* expression via `analyzeValModule` and in exposing
|
|
18
|
+
* {@link findModulePathAtPosition}. Fix traversal bugs in both, or fold them
|
|
19
|
+
* together.
|
|
20
|
+
*/
|
|
21
|
+
export type ModulePosition = {
|
|
22
|
+
line: number;
|
|
23
|
+
character: number;
|
|
24
|
+
};
|
|
25
|
+
export type ModulePathRange = {
|
|
26
|
+
start: ModulePosition;
|
|
27
|
+
end: ModulePosition;
|
|
28
|
+
};
|
|
29
|
+
export type ModulePathMap = {
|
|
30
|
+
[modulePath: string]: ModulePathRange & {
|
|
31
|
+
children: ModulePathMap;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Look up the source range for a module path.
|
|
36
|
+
*
|
|
37
|
+
* Returns `undefined` when the path cannot be resolved — an unparseable path, or
|
|
38
|
+
* one that does not correspond to any node. That happens legitimately when
|
|
39
|
+
* schema serialization failed upstream, so callers should treat a missing range
|
|
40
|
+
* as "report this diagnostic on the module instead" rather than as a bug.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getModulePathRange(modulePath: string, modulePathMap: ModulePathMap): ModulePathRange | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Find the module path of the innermost entry whose range contains `position`.
|
|
45
|
+
*
|
|
46
|
+
* The inverse of the map's normal use: given where the cursor is, work out which
|
|
47
|
+
* part of the module it addresses, so the schema there can be looked up. Used by
|
|
48
|
+
* schema-driven completions.
|
|
49
|
+
*
|
|
50
|
+
* Segments are encoded with `Internal.patchPathToModulePath`, so the result is
|
|
51
|
+
* addressable by the same functions that consume validation error paths.
|
|
52
|
+
*/
|
|
53
|
+
export declare function findModulePathAtPosition(modulePathMap: ModulePathMap, position: ModulePosition): ModulePath | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Build a {@link ModulePathMap} for a Val module source file.
|
|
56
|
+
*
|
|
57
|
+
* Returns `undefined` when the file is not a recognisable Val module (no
|
|
58
|
+
* `export default c.define(...)`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function createModulePathMap(sourceFile: ts.SourceFile): ModulePathMap | undefined;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Val language server protocol.
|
|
3
|
+
*
|
|
4
|
+
* This module is the contract between an editor client (for example the Val
|
|
5
|
+
* VS Code extension) and the language server that ships with the Val version
|
|
6
|
+
* installed in the user's project.
|
|
7
|
+
*
|
|
8
|
+
* The whole point of this package is that ONE editor client works against MANY
|
|
9
|
+
* versions of Val. That means:
|
|
10
|
+
*
|
|
11
|
+
* - Clients MUST NOT assume they can import this module. They resolve the
|
|
12
|
+
* server from the user's `node_modules` at runtime, and the constants below
|
|
13
|
+
* are intentionally small and pure so a client can vendor a copy of them.
|
|
14
|
+
* A client may take a type-only devDependency on this package for the types.
|
|
15
|
+
* - Anything added here must degrade gracefully. New capabilities are
|
|
16
|
+
* announced through `features` / `commands` so that a client which has never
|
|
17
|
+
* heard of them simply does not offer them, instead of breaking.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately dependency-free: no `vscode-languageserver` import, no Val
|
|
20
|
+
* imports, no I/O.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The current protocol version.
|
|
24
|
+
*
|
|
25
|
+
* Bump this ONLY for a breaking change to the client/server contract — a
|
|
26
|
+
* removed or renamed request, a changed payload shape, or changed semantics
|
|
27
|
+
* that an older client would misinterpret. Additive changes (a new feature
|
|
28
|
+
* flag, a new command, a new optional field) do NOT need a bump: they are
|
|
29
|
+
* negotiated through `features` and `commands`.
|
|
30
|
+
*
|
|
31
|
+
* This is a hand-maintained literal on purpose. Deriving it from package.json
|
|
32
|
+
* (as `Internal.VERSION.core` does) breaks under bundling, and build-time
|
|
33
|
+
* string substitution (as `@valbuild/ui`'s VERSION does) is easy to get wrong.
|
|
34
|
+
*/
|
|
35
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
36
|
+
/**
|
|
37
|
+
* The range of protocol versions this server can speak. Kept separate from
|
|
38
|
+
* {@link PROTOCOL_VERSION} so that a future server can continue to serve older
|
|
39
|
+
* clients by lowering `min`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SUPPORTED_PROTOCOL_VERSIONS: ProtocolVersionRange;
|
|
42
|
+
export type ProtocolVersionRange = {
|
|
43
|
+
min: number;
|
|
44
|
+
max: number;
|
|
45
|
+
};
|
|
46
|
+
export type ValClientInfo = {
|
|
47
|
+
/** For example `"vscode-val-build"`. Used for logging and telemetry only. */
|
|
48
|
+
name: string;
|
|
49
|
+
version: string | null;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The only environment variables a client may override. `initializationOptions`
|
|
53
|
+
* is untyped JSON at runtime, so this list is what the server enforces: without
|
|
54
|
+
* it a client could set `PATH` or `NODE_OPTIONS` on the server process.
|
|
55
|
+
*/
|
|
56
|
+
export declare const VAL_ENV_OVERRIDE_KEYS: readonly ["VAL_CONTENT_URL", "VAL_REMOTE_HOST", "VAL_BUILD_URL"];
|
|
57
|
+
/**
|
|
58
|
+
* Environment overrides forwarded from the client. These mirror the
|
|
59
|
+
* `VAL_*` environment variables so that an editor can point a session at a
|
|
60
|
+
* non-production Val backend without the user having to restart their editor
|
|
61
|
+
* with a modified environment.
|
|
62
|
+
*/
|
|
63
|
+
export type ValEnvOverrides = Partial<Record<(typeof VAL_ENV_OVERRIDE_KEYS)[number], string>>;
|
|
64
|
+
/**
|
|
65
|
+
* Sent by the client as `InitializeParams.initializationOptions`.
|
|
66
|
+
*
|
|
67
|
+
* One server instance serves exactly one Val root. A workspace containing
|
|
68
|
+
* several Val roots (a monorepo) gets one server per root, because different
|
|
69
|
+
* roots may pin different versions of Val.
|
|
70
|
+
*/
|
|
71
|
+
export type ValInitializationOptions = {
|
|
72
|
+
client: ValClientInfo;
|
|
73
|
+
/** Protocol versions the client can speak. */
|
|
74
|
+
supportedProtocolVersions: ProtocolVersionRange;
|
|
75
|
+
/** Absolute path to the directory containing this project's `package.json`. */
|
|
76
|
+
valRoot: string;
|
|
77
|
+
env?: ValEnvOverrides;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Optional capabilities a client may implement, announced by the client under
|
|
81
|
+
* `capabilities.experimental.val`.
|
|
82
|
+
*
|
|
83
|
+
* The server uses this to decide whether it can offer flows that need user
|
|
84
|
+
* interaction. A client that implements neither still gets diagnostics and
|
|
85
|
+
* completions.
|
|
86
|
+
*/
|
|
87
|
+
export type ValClientCapabilities = {
|
|
88
|
+
/** Client implements the {@link VAL_PICK_REQUEST} request. */
|
|
89
|
+
pick?: boolean;
|
|
90
|
+
/** Client implements the {@link VAL_INPUT_REQUEST} request. */
|
|
91
|
+
input?: boolean;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Feature flags announced by the server under
|
|
95
|
+
* `capabilities.experimental.val.features`.
|
|
96
|
+
*
|
|
97
|
+
* A client should treat an unknown string as "something this Val version can do
|
|
98
|
+
* that I do not know about" and ignore it, and a missing string as "not
|
|
99
|
+
* available in this Val version" and hide the corresponding UI.
|
|
100
|
+
*/
|
|
101
|
+
export declare const VAL_FEATURES: readonly ["diagnostics", "diagnostics/gallery", "completions/route", "completions/keyOf", "completions/mediaPath", "completions/galleryKey", "completions/richtextLink", "fix/metadata", "fix/upload-remote", "fix/download-remote", "fix/missing-module", "fix/gallery", "login"];
|
|
102
|
+
export type ValFeature = (typeof VAL_FEATURES)[number];
|
|
103
|
+
/**
|
|
104
|
+
* Announced by the server as
|
|
105
|
+
* `InitializeResult.capabilities.experimental.val`.
|
|
106
|
+
*/
|
|
107
|
+
export type ValServerCapabilities = {
|
|
108
|
+
/**
|
|
109
|
+
* The negotiated protocol version — within the client's range unless
|
|
110
|
+
* {@link ValServerCapabilities.incompatible} is set, in which case this is
|
|
111
|
+
* the highest version the server itself can speak.
|
|
112
|
+
*/
|
|
113
|
+
protocolVersion: number;
|
|
114
|
+
/**
|
|
115
|
+
* Present only when version negotiation failed. `features` and `commands`
|
|
116
|
+
* are then empty; the client should stop the server and tell the user which
|
|
117
|
+
* side to update. Checking this first is what turns an "incompatible
|
|
118
|
+
* versions" dead end into an actionable message.
|
|
119
|
+
*/
|
|
120
|
+
incompatible?: Exclude<ProtocolNegotiationResult, {
|
|
121
|
+
status: "ok";
|
|
122
|
+
}>;
|
|
123
|
+
versions: {
|
|
124
|
+
/** Version of `@valbuild/core` resolved in the user's project. */
|
|
125
|
+
core: string | null;
|
|
126
|
+
/** Version of this package. */
|
|
127
|
+
languageServer: string | null;
|
|
128
|
+
};
|
|
129
|
+
/** Echoed back so the client can label the session (status bar, logs). */
|
|
130
|
+
valRoot: string;
|
|
131
|
+
/**
|
|
132
|
+
* Features this server actually serves. Narrower than {@link VAL_FEATURES}
|
|
133
|
+
* when a capability could not be initialised for this project.
|
|
134
|
+
*/
|
|
135
|
+
features: ValFeature[];
|
|
136
|
+
/** `workspace/executeCommand` names this server offers. */
|
|
137
|
+
commands: string[];
|
|
138
|
+
};
|
|
139
|
+
/** Ask the user to choose one of a list of options (a "quick pick"). */
|
|
140
|
+
export declare const VAL_PICK_REQUEST = "val/pick";
|
|
141
|
+
export type ValPickItem = {
|
|
142
|
+
label: string;
|
|
143
|
+
/** Rendered next to the label. */
|
|
144
|
+
description?: string;
|
|
145
|
+
/** Rendered below the label. */
|
|
146
|
+
detail?: string;
|
|
147
|
+
/** Opaque to the client; returned verbatim in {@link ValPickResult}. */
|
|
148
|
+
value: string;
|
|
149
|
+
};
|
|
150
|
+
export type ValPickParams = {
|
|
151
|
+
title: string;
|
|
152
|
+
placeholder?: string;
|
|
153
|
+
items: ValPickItem[];
|
|
154
|
+
};
|
|
155
|
+
/** `null` when the user dismissed the picker. */
|
|
156
|
+
export type ValPickResult = {
|
|
157
|
+
value: string;
|
|
158
|
+
} | null;
|
|
159
|
+
/** Ask the user to type a value (an "input box"). */
|
|
160
|
+
export declare const VAL_INPUT_REQUEST = "val/input";
|
|
161
|
+
export type ValInputParams = {
|
|
162
|
+
title: string;
|
|
163
|
+
prompt?: string;
|
|
164
|
+
/** Pre-filled value. */
|
|
165
|
+
value?: string;
|
|
166
|
+
placeholder?: string;
|
|
167
|
+
password?: boolean;
|
|
168
|
+
};
|
|
169
|
+
/** `null` when the user dismissed the input box. */
|
|
170
|
+
export type ValInputResult = {
|
|
171
|
+
value: string;
|
|
172
|
+
} | null;
|
|
173
|
+
export type ProtocolNegotiationResult = {
|
|
174
|
+
status: "ok";
|
|
175
|
+
/** Highest version both sides can speak. */
|
|
176
|
+
protocolVersion: number;
|
|
177
|
+
} | {
|
|
178
|
+
/**
|
|
179
|
+
* The server is newer than anything the client understands: the user
|
|
180
|
+
* should update their editor client.
|
|
181
|
+
*/
|
|
182
|
+
status: "client-too-old";
|
|
183
|
+
server: ProtocolVersionRange;
|
|
184
|
+
client: ProtocolVersionRange;
|
|
185
|
+
} | {
|
|
186
|
+
/**
|
|
187
|
+
* The server is older than anything the client understands: the user
|
|
188
|
+
* should update Val in their project.
|
|
189
|
+
*/
|
|
190
|
+
status: "server-too-old";
|
|
191
|
+
server: ProtocolVersionRange;
|
|
192
|
+
client: ProtocolVersionRange;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Pick the highest protocol version both sides can speak.
|
|
196
|
+
*
|
|
197
|
+
* Returns a *directional* failure so the client can tell the user which side to
|
|
198
|
+
* update, rather than showing a generic "incompatible versions" error.
|
|
199
|
+
*/
|
|
200
|
+
export declare function negotiateProtocolVersion(client: ProtocolVersionRange, server?: ProtocolVersionRange): ProtocolNegotiationResult;
|