@valbuild/language-server 0.102.0 → 0.103.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.
@@ -6,26 +6,30 @@ import type { PublicValFiles } from "./publicValFiles.js";
6
6
  * Completions for file and image references.
7
7
  *
8
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
9
+ * — when the item is accepted — fills in the metadata siblings by reading the
10
10
  * chosen file. Getting width/height/mimeType right by hand is tedious and a
11
11
  * frequent source of the very validation errors this server reports.
12
12
  */
13
13
  /** Stashed on the item so `resolve` can do the expensive work lazily. */
14
14
  export type ValCompletionItemData = {
15
- kind: "file-ref";
15
+ kind: "media-path";
16
16
  uri: string;
17
17
  /** Val-style ref of the chosen file. */
18
18
  ref: string;
19
19
  /** Absolute path of the chosen file. */
20
20
  filePath: string;
21
- subType: "image" | "file";
22
- /** Where a metadata argument goes, or what it replaces. */
21
+ mediaType: "image" | "file";
23
22
  /**
24
- * Start offset of the reference argument, used to re-find the call at resolve
23
+ * True when the field is backed by a gallery, in which case the dimensions
24
+ * and mime type live there and must not be written here too.
25
+ */
26
+ gallery: boolean;
27
+ /**
28
+ * Start offset of the `path` value, used to re-find its object at resolve
25
29
  * time. Offsets captured now cannot be replayed later: see
26
- * {@link findFileRefArgument}.
30
+ * {@link findMediaPathObject}.
27
31
  */
28
- refArgStart: number;
32
+ pathValueStart: number;
29
33
  };
30
34
  export declare function createValCompletions({ document, offset, files, moduleFilePath, snapshot, }: {
31
35
  document: TextDocument;
@@ -37,7 +41,7 @@ export declare function createValCompletions({ document, offset, files, moduleFi
37
41
  snapshot?: SchemaSourceSnapshot;
38
42
  }): CompletionItem[];
39
43
  /**
40
- * Fill in the metadata argument for an accepted file reference.
44
+ * Fill in the metadata siblings for an accepted media path.
41
45
  *
42
46
  * Done at resolve time because it reads the file from disk, and an editor
43
47
  * requests completions far more often than it accepts one.
@@ -1,4 +1,4 @@
1
- import { type ModuleFilePath, type ValidationFix } from "@valbuild/core";
1
+ import { type ModuleFilePath, type SourcePath, type ValidationError, type ValidationFix } from "@valbuild/core";
2
2
  import { type SchemaSourceSnapshot } from "@valbuild/shared/internal";
3
3
  import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
4
4
  import type { ValModuleContent } from "./ValProject.js";
@@ -17,7 +17,7 @@ export declare const VAL_DIAGNOSTIC_SOURCE = "val";
17
17
  * vocabulary. A diagnostic says what is wrong; a fix says what can be done
18
18
  * about it, and travels in {@link ValDiagnosticData.fixes}.
19
19
  */
20
- export declare const VAL_DIAGNOSTIC_CODES: readonly ["val/validation", "val/schema", "val/fatal", "val/file-not-found", "val/missing-module"];
20
+ export declare const VAL_DIAGNOSTIC_CODES: readonly ["val/validation", "val/schema", "val/fatal", "val/file-not-found", "val/missing-module", "val/gallery-membership"];
21
21
  export type ValDiagnosticCode = (typeof VAL_DIAGNOSTIC_CODES)[number];
22
22
  /**
23
23
  * Structured payload attached to every Val diagnostic.
@@ -41,6 +41,20 @@ export type ValDiagnosticData = {
41
41
  value?: unknown;
42
42
  /** Absolute path of the missing file, for `val/file-not-found`. */
43
43
  filePath?: string;
44
+ /**
45
+ * Where the gallery is and what the field points at, for `val/gallery-membership`.
46
+ * Carried here so a code action does not have to re-resolve the schema, and so
47
+ * a client could show it.
48
+ */
49
+ gallery?: GalleryMembership;
50
+ /**
51
+ * The path to hand `createFixPatch`, when it differs from
52
+ * {@link ValDiagnosticData.sourcePath}.
53
+ *
54
+ * Only the gallery checks use this: they are reported on the entry that is
55
+ * wrong but fixed against the record that contains it.
56
+ */
57
+ fixSourcePath?: string;
44
58
  };
45
59
  /**
46
60
  * Severity policy, in one place.
@@ -59,7 +73,7 @@ export declare function severityFor({ code, fixes, }: {
59
73
  code: ValDiagnosticCode;
60
74
  fixes?: ValidationFix[];
61
75
  }): DiagnosticSeverity;
62
- export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, }: {
76
+ export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, galleryChecks, }: {
63
77
  moduleFilePath: ModuleFilePath;
64
78
  content: ValModuleContent;
65
79
  /** Current text of the module, as the editor sees it. */
@@ -80,6 +94,13 @@ export declare function createValDiagnostics({ moduleFilePath, content, text, va
80
94
  * unactionable noise on correct code.
81
95
  */
82
96
  snapshot?: SchemaSourceSnapshot;
97
+ /**
98
+ * Verdicts for the gallery placeholders core emits unconditionally, from
99
+ * {@link resolveGalleryChecks}. Without it the placeholders are dropped: two
100
+ * permanent warnings on every gallery module is worse than a missed one, and
101
+ * the caller that can adjudicate them is the one that has a `Service`.
102
+ */
103
+ galleryChecks?: ReadonlyMap<string, GalleryCheckVerdict>;
83
104
  }): Diagnostic[];
84
105
  /**
85
106
  * Diagnostic for a project that could not be evaluated at all.
@@ -103,3 +124,86 @@ export declare function createProjectErrorDiagnostic({ moduleFilePath, message,
103
124
  export declare function createMissingModuleDiagnostic({ moduleFilePath, }: {
104
125
  moduleFilePath: ModuleFilePath;
105
126
  }): Diagnostic;
127
+ export declare function isGalleryCheckFix(fix: string): boolean;
128
+ /**
129
+ * One real problem found behind a gallery placeholder.
130
+ *
131
+ * A single placeholder can expand into several of these, each pointing at its
132
+ * own entry: `handleCheckAllFiles` reporting `shouldApplyPatch` means "no
133
+ * membership problem, now check the metadata", and `createFixPatch` then returns
134
+ * one error per entry whose stored metadata disagrees with its file. This is why
135
+ * the verdict is a list rather than a message, and why each carries its own
136
+ * source path.
137
+ */
138
+ export type GalleryCheckFinding = {
139
+ /** Where to show it: the entry the problem is about. */
140
+ sourcePath: string;
141
+ message: string;
142
+ fixes?: ValidationFix[];
143
+ /**
144
+ * Where to *fix* it, when that is not where it is shown.
145
+ *
146
+ * The gallery fix is expressed against the record as a whole — it walks every
147
+ * entry — so `createFixPatch` must be given the record's path and the
148
+ * placeholder's value, while the diagnostic itself belongs on the offending
149
+ * entry. Splitting the two is what lets the message be precise without
150
+ * breaking the fix.
151
+ */
152
+ fixSourcePath?: string;
153
+ /** The placeholder's `value`, which the fix reads. */
154
+ value?: unknown;
155
+ };
156
+ /**
157
+ * The verdict on one gallery check. Empty means nothing is wrong and the
158
+ * placeholder is dropped.
159
+ */
160
+ export type GalleryCheckVerdict = GalleryCheckFinding[];
161
+ /**
162
+ * Adjudicate every gallery placeholder in `validation`, by running the same fix
163
+ * handler `val validate` runs.
164
+ *
165
+ * Async and therefore separate from {@link createValDiagnostics}, which stays
166
+ * synchronous so it can be tested without a project. The caller runs this first
167
+ * and passes the result in.
168
+ */
169
+ export declare function resolveGalleryChecks({ validation, runHandler, }: {
170
+ validation: Record<SourcePath, ValidationError[]>;
171
+ /**
172
+ * Runs the fix handler for one error and reports what it found. Injected so
173
+ * that this module needs no `Service`, and so tests can drive both outcomes.
174
+ */
175
+ runHandler: (sourcePath: SourcePath, error: ValidationError) => Promise<GalleryCheckVerdict>;
176
+ }): Promise<Map<string, GalleryCheckVerdict>>;
177
+ /**
178
+ * Key for one placeholder. A gallery module carries several, all at the same
179
+ * source path, so the fix names have to be part of the key.
180
+ */
181
+ export declare function galleryCheckKey(sourcePath: string, error: ValidationError): string;
182
+ /**
183
+ * A gallery-backed field pointing at something the gallery does not have.
184
+ *
185
+ * `ImageSchema.validate` reports this (`packages/core/src/schema/image.ts`:
186
+ * "The gallery does not have an image at '…'") but attaches no `ValidationFix`,
187
+ * because the remedy is not a change to this module: either the gallery gains an
188
+ * entry, or the file moves into the gallery's directory. Both are edits to
189
+ * somewhere else, which is not what a `ValidationFix` describes.
190
+ *
191
+ * So the fix is built in the editor instead, and this is what it needs. Derived
192
+ * from the **schema**, not from the message: matching on message text would break
193
+ * the moment the wording changed, silently.
194
+ */
195
+ export type GalleryMembership = {
196
+ /** Module path of the gallery this field points at. */
197
+ referencedModule: string;
198
+ /** The gallery's directory, when it declares one. */
199
+ directory?: string;
200
+ /** The path the field currently holds. */
201
+ path: string;
202
+ /** `image` or `file`, for wording and for which metadata to read. */
203
+ mediaType: "image" | "file";
204
+ };
205
+ export declare function galleryMembershipAt({ sourcePath, content, snapshot, }: {
206
+ sourcePath: string;
207
+ content: ValModuleContent;
208
+ snapshot?: SchemaSourceSnapshot;
209
+ }): GalleryMembership | undefined;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The two remedies for a gallery-backed field pointing at something its gallery
3
+ * does not have.
4
+ *
5
+ * Core reports the problem (`packages/core/src/schema/image.ts`) and offers no
6
+ * `ValidationFix`, because neither remedy is a change to the module holding the
7
+ * field:
8
+ *
9
+ * - **register it** — add an entry to the *gallery* module, keyed by the path,
10
+ * carrying the metadata read from the file. An edit to another document.
11
+ * - **move the file** — when the file is on disk but outside the gallery's
12
+ * directory, move it there and update the path. A file rename plus an edit.
13
+ *
14
+ * Both used to be VS Code commands in the extension, driven by diagnostics the
15
+ * extension computed itself. They are here so that every editor gets them, and
16
+ * so that a change to how galleries work has one place to be reflected.
17
+ */
18
+ import ts from "typescript";
19
+ import { CodeAction } from "vscode-languageserver";
20
+ import type { TextDocument } from "vscode-languageserver-textdocument";
21
+ import type { GalleryMembership } from "./diagnostics.js";
22
+ /**
23
+ * Whether the client will honour a file rename inside a `WorkspaceEdit`.
24
+ *
25
+ * A `RenameFile` sent to a client that did not announce `resourceOperations`
26
+ * is silently dropped, which would leave the path rewritten and the file where it
27
+ * was — worse than not offering the fix.
28
+ */
29
+ export declare function canRenameFiles(capabilities: unknown): boolean;
30
+ export declare function createGalleryMembershipActions({ document, gallery, valRoot, read, allowRename, }: {
31
+ document: TextDocument;
32
+ gallery: GalleryMembership;
33
+ valRoot: string;
34
+ /** The editor's view of a file, falling back to disk. */
35
+ read: (fsPath: string) => string | undefined;
36
+ allowRename: boolean;
37
+ }): Promise<CodeAction[]>;
38
+ /**
39
+ * Where to insert into the record that is a gallery module's content -- the third
40
+ * argument of its `c.define(...)`.
41
+ */
42
+ export declare function findRecordInsertion(sourceFile: ts.SourceFile): {
43
+ insertOffset: number;
44
+ indentation: string;
45
+ hasProperties: boolean;
46
+ } | null;
@@ -2,11 +2,15 @@ export { main, createValLanguageServer, type ValSession } from "./server.js";
2
2
  export { getLanguageServerVersion } from "./version.js";
3
3
  export { createEditorFsHost, mapOpenDocuments, type OpenDocuments, } from "./EditorFsHost.js";
4
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";
5
+ export { createValDiagnostics, createMissingModuleDiagnostic, createProjectErrorDiagnostic, severityFor, VAL_DIAGNOSTIC_SOURCE, VAL_DIAGNOSTIC_CODES, type ValDiagnosticCode, type ValDiagnosticData, resolveGalleryChecks, isGalleryCheckFix, galleryCheckKey, type GalleryCheckFinding, type GalleryCheckVerdict, galleryMembershipAt, type GalleryMembership, } from "./diagnostics.js";
6
6
  export { findRegisteredModuleSpecifiers, isModuleRegistered, } from "./valModulesRegistry.js";
7
- export { createValCodeActions, isLocalFix, minimalTextEdit, } from "./codeActions.js";
7
+ export { createValCodeActions, createMissingModuleCodeAction, adjudicateGalleryCheck, isLocalFix, } from "./codeActions.js";
8
+ export { minimalTextEdit } from "./textEdit.js";
9
+ export { createValCommands, isRemoteFix, valCommandNames, readPersonalAccessToken, REMOTE_FIX_COMMANDS, REMOTE_FIX_TITLES, VAL_LOGIN_COMMAND, VAL_UPLOAD_REMOTE_COMMAND, VAL_DOWNLOAD_REMOTE_COMMAND, type RemoteFixCommandArgs, } from "./commands.js";
10
+ export { findValModulesInsertion, valModuleSpecifier, valModulesEntryText, } from "./valModulesRegistry.js";
11
+ export { canRenameFiles, createGalleryMembershipActions, findRecordInsertion, } from "./galleryFixes.js";
8
12
  export { createValCompletions, resolveValCompletion, type ValCompletionItemData, } from "./completions.js";
9
- export { getValCompletionContext, type ValCompletionContext, type ValFileRefContext, type ValStringValueContext, } from "./completionContext.js";
13
+ export { getValCompletionContext, findMediaPathObject, MEDIA_METADATA_KEYS, type MediaMetadataKey, type MediaPathObject, type ValCompletionContext, type ValStringValueContext, } from "./completionContext.js";
10
14
  export { createPublicValFiles, DEFAULT_FILES_DIRECTORY, type PublicValFile, type PublicValFiles, } from "./publicValFiles.js";
11
15
  export { createModulePathMap, findModulePathAtPosition, getModulePathRange, type ModulePathMap, type ModulePathRange, type ModulePosition, } from "./modulePathMap.js";
12
16
  export { isValModuleUri, pathToUri, toModuleFilePath, uriToPath } from "./uri.js";
@@ -0,0 +1,10 @@
1
+ import type { TextEdit } from "vscode-languageserver";
2
+ import type { TextDocument } from "vscode-languageserver-textdocument";
3
+ /**
4
+ * Narrow an edit down to the region that actually changed.
5
+ *
6
+ * A whole-document replacement would work, but it moves the cursor and shows up
7
+ * as a full-file change in review. Trimming the common prefix and suffix keeps
8
+ * the edit tight without needing a real diff algorithm.
9
+ */
10
+ export declare function minimalTextEdit(before: string, after: string, document: TextDocument): TextEdit | undefined;
@@ -30,3 +30,38 @@ export declare function isModuleRegistered({ sourceFile, valModulesDir, moduleFi
30
30
  valModulesDir: string;
31
31
  moduleFilePath: ModuleFilePath;
32
32
  }): boolean;
33
+ /**
34
+ * Where to insert a new entry in a `val.modules` file, and how to indent it.
35
+ *
36
+ * Only `modules(config, [ … ])` is matched, because that is the shape Val
37
+ * actually accepts and the shape `examples/next/val.modules.ts` uses. The
38
+ * over-reporting rule in {@link findRegisteredModuleSpecifiers} is right for
39
+ * *reading* a file someone else wrote; writing into one has to commit to a
40
+ * shape, and guessing wrong produces a file that no longer compiles.
41
+ *
42
+ * Returns `null` when the array cannot be found — the caller then offers no fix
43
+ * rather than inserting somewhere arbitrary.
44
+ */
45
+ export declare function findValModulesInsertion(sourceFile: ts.SourceFile): {
46
+ insertOffset: number;
47
+ indentation: string;
48
+ hasElements: boolean;
49
+ } | null;
50
+ /**
51
+ * The specifier to write for `moduleFilePath`, relative to the `val.modules`
52
+ * file that will hold it.
53
+ *
54
+ * Both paths are Val-style (root-relative, leading slash). POSIX separators
55
+ * always: the string ends up in an `import()` in source, where a backslash is an
56
+ * escape rather than a separator.
57
+ */
58
+ export declare function valModuleSpecifier({ valModulesFilePath, moduleFilePath, }: {
59
+ valModulesFilePath: string;
60
+ moduleFilePath: ModuleFilePath;
61
+ }): string;
62
+ /** The text to insert for one new entry, including its separator. */
63
+ export declare function valModulesEntryText({ specifier, indentation, hasElements, }: {
64
+ specifier: string;
65
+ indentation: string;
66
+ hasElements: boolean;
67
+ }): string;