@valbuild/language-server 0.116.0 → 0.117.1
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/CHANGELOG.md +68 -0
- package/dist/declarations/src/diagnostics.d.ts +11 -1
- package/dist/declarations/src/mediaMetadataChecks.d.ts +105 -0
- package/dist/valbuild-language-server.cjs.dev.js +364 -1
- package/dist/valbuild-language-server.cjs.prod.js +364 -1
- package/dist/valbuild-language-server.esm.js +365 -2
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @valbuild/language-server
|
|
2
|
+
|
|
3
|
+
## 0.117.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies []:
|
|
8
|
+
- @valbuild/server@0.117.1
|
|
9
|
+
|
|
10
|
+
## 0.117.0
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#581](https://github.com/valbuild/val/pull/581) [`d94a40f`](https://github.com/valbuild/val/commit/d94a40f8bd11027636d183e293aced820b6f341f) Thanks [@freekh](https://github.com/freekh)! - Images no longer show a validation warning in the editor when nothing is wrong
|
|
15
|
+
with them.
|
|
16
|
+
|
|
17
|
+
Every `s.image()` carrying width, height or a mime type used to be marked in VS
|
|
18
|
+
Code with "Found image metadata, but it could not be validated", whether or not
|
|
19
|
+
the metadata was correct — so the warning sat on every image in the project and
|
|
20
|
+
never went away, not even after applying its own quick fix.
|
|
21
|
+
|
|
22
|
+
That message was never a finding. `@valbuild/core` cannot read files, so it
|
|
23
|
+
cannot answer whether stored dimensions match the image, and it hands the
|
|
24
|
+
question on as an `image:check-metadata` fix instead. `val validate` has always
|
|
25
|
+
resolved that by reading the file and comparing; the language server now does
|
|
26
|
+
the same, and reports only what actually disagrees:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Image width is incorrect! Found: 800. Expected: 944
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
An image whose metadata is right gets nothing. A missing `width`, `height` or
|
|
33
|
+
`mimeType` is reported, as is a stale one, with the quick fix still offered. A
|
|
34
|
+
file that is not on disk is still reported as a missing file rather than as a
|
|
35
|
+
metadata problem.
|
|
36
|
+
|
|
37
|
+
The four messages involved say what they mean now, in the editor and in
|
|
38
|
+
`val validate` alike:
|
|
39
|
+
|
|
40
|
+
- "Image metadata has not been checked against the file." (was "Found image
|
|
41
|
+
metadata, but it could not be validated. An image must have a width (positive
|
|
42
|
+
number), a height (positive number) and a mime type." — which described a
|
|
43
|
+
check that never ran)
|
|
44
|
+
- "Image metadata is missing: width, height and mimeType." (was "Could not
|
|
45
|
+
validate Image metadata.")
|
|
46
|
+
- "File mimeType has not been checked against the file." (was "Found mimeType,
|
|
47
|
+
but it could not be validated.")
|
|
48
|
+
- "File metadata is missing: mimeType." (was "Missing File mimeType.")
|
|
49
|
+
|
|
50
|
+
Also fixes an `s.file()` whose `mimeType` is missing: it reported "Mime type and
|
|
51
|
+
file extension not matching. Mime type is 'undefined'" with no fix attached, so
|
|
52
|
+
no quick fix was offered and the `file:add-metadata` case was unreachable. It
|
|
53
|
+
now reports the missing mime type and offers to add it.
|
|
54
|
+
|
|
55
|
+
- [#579](https://github.com/valbuild/val/pull/579) [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d) Thanks [@freekh](https://github.com/freekh)! - Every release now ships a changelog. Each package's `CHANGELOG.md` records what
|
|
56
|
+
changed under the version that shipped it — with a link to the pull request, the
|
|
57
|
+
commit and the author — and the same entry becomes the body of the GitHub
|
|
58
|
+
Release for the tag. The file is included in the npm tarball, so it is also
|
|
59
|
+
readable from an installed copy.
|
|
60
|
+
|
|
61
|
+
Up to now those changelogs were generated empty, and the GitHub Releases with
|
|
62
|
+
them, so there was no record of what any given version contained. Releases from
|
|
63
|
+
this one on have one; earlier versions stay blank.
|
|
64
|
+
|
|
65
|
+
- Updated dependencies [[`fca3efa`](https://github.com/valbuild/val/commit/fca3efa389e2817401f55ea3dd184af7c611b807), [`d94a40f`](https://github.com/valbuild/val/commit/d94a40f8bd11027636d183e293aced820b6f341f), [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d)]:
|
|
66
|
+
- @valbuild/server@0.117.0
|
|
67
|
+
- @valbuild/core@0.117.0
|
|
68
|
+
- @valbuild/shared@0.117.0
|
|
@@ -1,6 +1,7 @@
|
|
|
1
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
|
+
import { type MediaMetadataVerdict } from "./mediaMetadataChecks.js";
|
|
4
5
|
import type { ValModuleContent } from "./ValProject.js";
|
|
5
6
|
/** Marks diagnostics as ours, so a client can filter on it. */
|
|
6
7
|
export declare const VAL_DIAGNOSTIC_SOURCE = "val";
|
|
@@ -73,7 +74,7 @@ export declare function severityFor({ code, fixes, }: {
|
|
|
73
74
|
code: ValDiagnosticCode;
|
|
74
75
|
fixes?: ValidationFix[];
|
|
75
76
|
}): DiagnosticSeverity;
|
|
76
|
-
export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, galleryChecks, }: {
|
|
77
|
+
export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, galleryChecks, mediaMetadataChecks, }: {
|
|
77
78
|
moduleFilePath: ModuleFilePath;
|
|
78
79
|
content: ValModuleContent;
|
|
79
80
|
/** Current text of the module, as the editor sees it. */
|
|
@@ -101,6 +102,15 @@ export declare function createValDiagnostics({ moduleFilePath, content, text, va
|
|
|
101
102
|
* the caller that can adjudicate them is the one that has a `Service`.
|
|
102
103
|
*/
|
|
103
104
|
galleryChecks?: ReadonlyMap<string, GalleryCheckVerdict>;
|
|
105
|
+
/**
|
|
106
|
+
* Verdicts for the media metadata placeholders core emits unconditionally,
|
|
107
|
+
* from {@link resolveMediaMetadataChecks}. Without it the placeholders are
|
|
108
|
+
* dropped, for the same reason the gallery ones are: core reports
|
|
109
|
+
* `image:check-metadata` on every `s.image()` that carries metadata whether
|
|
110
|
+
* or not anything is wrong, so publishing them raw puts a permanent warning
|
|
111
|
+
* on every image in the project.
|
|
112
|
+
*/
|
|
113
|
+
mediaMetadataChecks?: ReadonlyMap<string, MediaMetadataVerdict>;
|
|
104
114
|
}): Diagnostic[];
|
|
105
115
|
/**
|
|
106
116
|
* Diagnostic for a project that could not be evaluated at all.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adjudicating the media metadata placeholders core emits unconditionally.
|
|
3
|
+
*
|
|
4
|
+
* `ImageSchema.validate` cannot read bytes, so it never answers "does the
|
|
5
|
+
* stored width/height/mimeType match the file". It defers instead: any
|
|
6
|
+
* `s.image()` carrying metadata gets an `image:check-metadata` error whether
|
|
7
|
+
* or not anything is wrong (`packages/core/src/schema/image.ts`), and
|
|
8
|
+
* `s.file()` does the same with `file:check-metadata`.
|
|
9
|
+
*
|
|
10
|
+
* `val validate` resolves those by running the fix handler and then
|
|
11
|
+
* `createFixPatch` in report mode, which compares each field against the file
|
|
12
|
+
* and returns one error per field that disagrees. The Studio cannot do that at
|
|
13
|
+
* all -- a browser has no filesystem -- so it drops them wholesale
|
|
14
|
+
* (`partitionValidationErrors` in `@valbuild/shared`).
|
|
15
|
+
*
|
|
16
|
+
* An editor is in the CLI's position, not the browser's, and publishing the
|
|
17
|
+
* placeholders raw put a permanent warning on every image in the project. So
|
|
18
|
+
* they are adjudicated here, by the same comparison `val validate` makes, and
|
|
19
|
+
* only a real disagreement is shown.
|
|
20
|
+
*/
|
|
21
|
+
import { type SourcePath, type ValidationError, type ValidationFix } from "@valbuild/core";
|
|
22
|
+
import type { ValModuleContent } from "./ValProject.js";
|
|
23
|
+
/** One field of a media value that disagrees with the file behind it. */
|
|
24
|
+
export type MediaMetadataFinding = {
|
|
25
|
+
message: string;
|
|
26
|
+
/** The placeholder's own fixes, so the quick fix and severity survive. */
|
|
27
|
+
fixes?: ValidationFix[];
|
|
28
|
+
/** The placeholder's `value`, which `createFixPatch` reads. */
|
|
29
|
+
value?: unknown;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The verdict on one placeholder. Empty means the metadata agrees with the
|
|
33
|
+
* file and nothing should be published.
|
|
34
|
+
*/
|
|
35
|
+
export type MediaMetadataVerdict = MediaMetadataFinding[];
|
|
36
|
+
/**
|
|
37
|
+
* Whether this error is the unconditional deferral rather than a real finding.
|
|
38
|
+
*
|
|
39
|
+
* This is the whole difficulty of the change. `image:check-metadata` is
|
|
40
|
+
* attached to FIVE different image errors, and only the last is a placeholder:
|
|
41
|
+
*
|
|
42
|
+
* 1. `Invalid mime type format` -- `accept` set, mimeType has no `/`
|
|
43
|
+
* 2. `Mime type mismatch` -- `accept` set and not satisfied
|
|
44
|
+
* 3. `Could not determine mime type from file extension`
|
|
45
|
+
* 4. `Mime type and file extension not matching`
|
|
46
|
+
* 5. the fall-through deferral
|
|
47
|
+
*
|
|
48
|
+
* The first four are real: they are about the mime type disagreeing with the
|
|
49
|
+
* schema or with the filename, neither of which reading the file can settle.
|
|
50
|
+
* Adjudicating one of them would find the stored metadata matches the bytes and
|
|
51
|
+
* drop a genuine error -- so they have to be told apart before any file is read.
|
|
52
|
+
*
|
|
53
|
+
* There is no flag on `ValidationError` saying which is which, so the four
|
|
54
|
+
* conditions are re-derived here from the same inputs core used. They are
|
|
55
|
+
* transcribed from `ImageSchema.executeValidate`, in its order, and
|
|
56
|
+
* `mediaMetadataChecks.test.ts` drives real core through all five cases to
|
|
57
|
+
* check that this agrees with it. If core grows a sixth condition, that test is
|
|
58
|
+
* what catches it.
|
|
59
|
+
*
|
|
60
|
+
* `FileSchema` needs none of this: its four equivalent errors carry no `fixes`
|
|
61
|
+
* at all, so `file:check-metadata` is unambiguous. The test pins that too.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isDeferredMediaMetadataCheck({ error, schema, }: {
|
|
64
|
+
/**
|
|
65
|
+
* The error to classify. Its `value` is the media value core validated, and
|
|
66
|
+
* so is what core's conditions are re-derived from -- the same value
|
|
67
|
+
* `createFixPatch` compares against the file, so the classification and the
|
|
68
|
+
* comparison cannot disagree about which image they are talking about.
|
|
69
|
+
*/
|
|
70
|
+
error: ValidationError;
|
|
71
|
+
/** The resolved serialized schema, which is where `accept` lives. */
|
|
72
|
+
schema: unknown;
|
|
73
|
+
}): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* {@link isDeferredMediaMetadataCheck} for an error in a module, resolving the
|
|
76
|
+
* schema it needs.
|
|
77
|
+
*
|
|
78
|
+
* Both the adjudicator and `createValDiagnostics` have to make the same call,
|
|
79
|
+
* on the same inputs -- one to decide what to adjudicate, the other to decide
|
|
80
|
+
* what to publish -- so it lives here rather than being written out twice.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isDeferredMediaMetadataCheckAt({ sourcePath, error, content, }: {
|
|
83
|
+
sourcePath: string;
|
|
84
|
+
error: ValidationError;
|
|
85
|
+
content: ValModuleContent;
|
|
86
|
+
}): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Adjudicate every deferred metadata placeholder in `validation`.
|
|
89
|
+
*
|
|
90
|
+
* Async, and therefore separate from `createValDiagnostics`, which stays
|
|
91
|
+
* synchronous so it can be tested without a project -- the same split
|
|
92
|
+
* `resolveGalleryChecks` uses. The caller runs this first and passes the
|
|
93
|
+
* result in.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolveMediaMetadataChecks({ validation, content, valRoot, remoteHost, }: {
|
|
96
|
+
validation: Record<SourcePath, ValidationError[]>;
|
|
97
|
+
content: ValModuleContent;
|
|
98
|
+
valRoot: string;
|
|
99
|
+
remoteHost?: string;
|
|
100
|
+
}): Promise<Map<string, MediaMetadataVerdict>>;
|
|
101
|
+
/**
|
|
102
|
+
* Key for one placeholder. A value can in principle carry more than one, so
|
|
103
|
+
* the fix names are part of the key -- as they are for the gallery checks.
|
|
104
|
+
*/
|
|
105
|
+
export declare function mediaMetadataCheckKey(sourcePath: string, error: ValidationError): string;
|
|
@@ -857,6 +857,314 @@ function traverseObjectLiteral(node, sourceFile) {
|
|
|
857
857
|
return map;
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Adjudicating the media metadata placeholders core emits unconditionally.
|
|
862
|
+
*
|
|
863
|
+
* `ImageSchema.validate` cannot read bytes, so it never answers "does the
|
|
864
|
+
* stored width/height/mimeType match the file". It defers instead: any
|
|
865
|
+
* `s.image()` carrying metadata gets an `image:check-metadata` error whether
|
|
866
|
+
* or not anything is wrong (`packages/core/src/schema/image.ts`), and
|
|
867
|
+
* `s.file()` does the same with `file:check-metadata`.
|
|
868
|
+
*
|
|
869
|
+
* `val validate` resolves those by running the fix handler and then
|
|
870
|
+
* `createFixPatch` in report mode, which compares each field against the file
|
|
871
|
+
* and returns one error per field that disagrees. The Studio cannot do that at
|
|
872
|
+
* all -- a browser has no filesystem -- so it drops them wholesale
|
|
873
|
+
* (`partitionValidationErrors` in `@valbuild/shared`).
|
|
874
|
+
*
|
|
875
|
+
* An editor is in the CLI's position, not the browser's, and publishing the
|
|
876
|
+
* placeholders raw put a permanent warning on every image in the project. So
|
|
877
|
+
* they are adjudicated here, by the same comparison `val validate` makes, and
|
|
878
|
+
* only a real disagreement is shown.
|
|
879
|
+
*/
|
|
880
|
+
/**
|
|
881
|
+
* The fixes that carry an unconditional metadata placeholder.
|
|
882
|
+
*
|
|
883
|
+
* `*:add-metadata` is deliberately absent: it is emitted only when every
|
|
884
|
+
* metadata field is missing, which needs no adjudication -- there is nothing
|
|
885
|
+
* stored to compare, and the error stands on its own.
|
|
886
|
+
*/
|
|
887
|
+
const METADATA_CHECK_FIXES = ["image:check-metadata", "file:check-metadata"];
|
|
888
|
+
|
|
889
|
+
/** One field of a media value that disagrees with the file behind it. */
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* The verdict on one placeholder. Empty means the metadata agrees with the
|
|
893
|
+
* file and nothing should be published.
|
|
894
|
+
*/
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Whether this error is the unconditional deferral rather than a real finding.
|
|
898
|
+
*
|
|
899
|
+
* This is the whole difficulty of the change. `image:check-metadata` is
|
|
900
|
+
* attached to FIVE different image errors, and only the last is a placeholder:
|
|
901
|
+
*
|
|
902
|
+
* 1. `Invalid mime type format` -- `accept` set, mimeType has no `/`
|
|
903
|
+
* 2. `Mime type mismatch` -- `accept` set and not satisfied
|
|
904
|
+
* 3. `Could not determine mime type from file extension`
|
|
905
|
+
* 4. `Mime type and file extension not matching`
|
|
906
|
+
* 5. the fall-through deferral
|
|
907
|
+
*
|
|
908
|
+
* The first four are real: they are about the mime type disagreeing with the
|
|
909
|
+
* schema or with the filename, neither of which reading the file can settle.
|
|
910
|
+
* Adjudicating one of them would find the stored metadata matches the bytes and
|
|
911
|
+
* drop a genuine error -- so they have to be told apart before any file is read.
|
|
912
|
+
*
|
|
913
|
+
* There is no flag on `ValidationError` saying which is which, so the four
|
|
914
|
+
* conditions are re-derived here from the same inputs core used. They are
|
|
915
|
+
* transcribed from `ImageSchema.executeValidate`, in its order, and
|
|
916
|
+
* `mediaMetadataChecks.test.ts` drives real core through all five cases to
|
|
917
|
+
* check that this agrees with it. If core grows a sixth condition, that test is
|
|
918
|
+
* what catches it.
|
|
919
|
+
*
|
|
920
|
+
* `FileSchema` needs none of this: its four equivalent errors carry no `fixes`
|
|
921
|
+
* at all, so `file:check-metadata` is unambiguous. The test pins that too.
|
|
922
|
+
*/
|
|
923
|
+
function isDeferredMediaMetadataCheck({
|
|
924
|
+
error,
|
|
925
|
+
schema
|
|
926
|
+
}) {
|
|
927
|
+
const fixes = error.fixes ?? [];
|
|
928
|
+
if (!fixes.some(fix => METADATA_CHECK_FIXES.includes(fix))) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
if (fixes.includes("file:check-metadata")) {
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
const value = mediaValueOf(error.value);
|
|
935
|
+
if (typeof value?.path !== "string") {
|
|
936
|
+
// Nothing to re-derive the conditions from. Keep the error: claiming an
|
|
937
|
+
// image is fine on no evidence is the worse failure.
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
const accept = acceptOf(schema);
|
|
941
|
+
// Core reads `src.mimeType ?? ""`, and every condition below is guarded on
|
|
942
|
+
// it being non-empty, so an absent mimeType passes all four.
|
|
943
|
+
const mimeType = typeof value.mimeType === "string" ? value.mimeType : "";
|
|
944
|
+
if (accept && mimeType && !mimeType.includes("/")) {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
if (accept && mimeType && mimeType.includes("/") && !core.Internal.mimeTypeMatchesAccept(mimeType, accept)) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
const fileMimeType = core.Internal.filenameToMimeType(value.path);
|
|
951
|
+
if (!fileMimeType) {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
if (mimeType && fileMimeType !== mimeType) {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* {@link isDeferredMediaMetadataCheck} for an error in a module, resolving the
|
|
962
|
+
* schema it needs.
|
|
963
|
+
*
|
|
964
|
+
* Both the adjudicator and `createValDiagnostics` have to make the same call,
|
|
965
|
+
* on the same inputs -- one to decide what to adjudicate, the other to decide
|
|
966
|
+
* what to publish -- so it lives here rather than being written out twice.
|
|
967
|
+
*/
|
|
968
|
+
function isDeferredMediaMetadataCheckAt({
|
|
969
|
+
sourcePath,
|
|
970
|
+
error,
|
|
971
|
+
content
|
|
972
|
+
}) {
|
|
973
|
+
return isDeferredMediaMetadataCheck({
|
|
974
|
+
error,
|
|
975
|
+
schema: resolveSchemaAt$1(sourcePath, content)
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Adjudicate every deferred metadata placeholder in `validation`.
|
|
981
|
+
*
|
|
982
|
+
* Async, and therefore separate from `createValDiagnostics`, which stays
|
|
983
|
+
* synchronous so it can be tested without a project -- the same split
|
|
984
|
+
* `resolveGalleryChecks` uses. The caller runs this first and passes the
|
|
985
|
+
* result in.
|
|
986
|
+
*/
|
|
987
|
+
async function resolveMediaMetadataChecks({
|
|
988
|
+
validation,
|
|
989
|
+
content,
|
|
990
|
+
valRoot,
|
|
991
|
+
remoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST
|
|
992
|
+
}) {
|
|
993
|
+
const verdicts = new Map();
|
|
994
|
+
for (const [sourcePath, errors] of Object.entries(validation)) {
|
|
995
|
+
for (const error of errors) {
|
|
996
|
+
if (!isDeferredMediaMetadataCheckAt({
|
|
997
|
+
sourcePath,
|
|
998
|
+
error,
|
|
999
|
+
content
|
|
1000
|
+
})) {
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
const key = mediaMetadataCheckKey(sourcePath, error);
|
|
1004
|
+
try {
|
|
1005
|
+
verdicts.set(key, await adjudicate({
|
|
1006
|
+
sourcePath,
|
|
1007
|
+
error,
|
|
1008
|
+
content,
|
|
1009
|
+
valRoot,
|
|
1010
|
+
remoteHost
|
|
1011
|
+
}));
|
|
1012
|
+
} catch {
|
|
1013
|
+
// This runs inside `validate`, which publishes NOTHING if it throws --
|
|
1014
|
+
// one bad image would silently clear every Val diagnostic in the file.
|
|
1015
|
+
// Keep the placeholder rather than claiming the metadata is fine.
|
|
1016
|
+
verdicts.set(key, keep(error));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return verdicts;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Key for one placeholder. A value can in principle carry more than one, so
|
|
1025
|
+
* the fix names are part of the key -- as they are for the gallery checks.
|
|
1026
|
+
*/
|
|
1027
|
+
function mediaMetadataCheckKey(sourcePath, error) {
|
|
1028
|
+
return `${sourcePath}|${(error.fixes ?? []).join(",")}`;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/** Keeping the placeholder: what to publish when we learned nothing. */
|
|
1032
|
+
function keep(error) {
|
|
1033
|
+
return [{
|
|
1034
|
+
message: error.message,
|
|
1035
|
+
...(error.fixes ? {
|
|
1036
|
+
fixes: error.fixes
|
|
1037
|
+
} : {}),
|
|
1038
|
+
...(error.value !== undefined ? {
|
|
1039
|
+
value: error.value
|
|
1040
|
+
} : {})
|
|
1041
|
+
}];
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* What `createFixPatch` says about one placeholder, in report mode.
|
|
1046
|
+
*
|
|
1047
|
+
* `val validate` runs the fix handler first and then `createFixPatch`, but for
|
|
1048
|
+
* these four fixes the handler is `handleFileMetadata`, whose whole job is the
|
|
1049
|
+
* precondition "the ref resolves and the file is on disk". That precondition is
|
|
1050
|
+
* checked directly here instead, for two reasons:
|
|
1051
|
+
*
|
|
1052
|
+
* - `createValDiagnostics` already reports a missing file as
|
|
1053
|
+
* `val/file-not-found`, which is a better diagnostic than a metadata
|
|
1054
|
+
* mismatch and is produced before this verdict is consulted.
|
|
1055
|
+
* - `handleFileMetadata` resolves the source path through the module, and that
|
|
1056
|
+
* throws outright for a `.jsonValues()` entry ("Cannot resolve path into a
|
|
1057
|
+
* jsonValues entry until its content is loaded") -- so routing through it
|
|
1058
|
+
* would leave every entry-backed image stuck on the placeholder.
|
|
1059
|
+
*
|
|
1060
|
+
* The comparison itself is still `createFixPatch`, unchanged, which is the
|
|
1061
|
+
* parity that matters: the editor's wording is the CLI's wording because it is
|
|
1062
|
+
* the CLI's code -- including for bytes it cannot measure, where it reports
|
|
1063
|
+
* what `val validate` reports rather than a second opinion of our own.
|
|
1064
|
+
*
|
|
1065
|
+
* `createFixPatch` reads and decodes the image itself, so this is one file read
|
|
1066
|
+
* per media field per validation pass, and nothing here should add another.
|
|
1067
|
+
*/
|
|
1068
|
+
async function adjudicate({
|
|
1069
|
+
sourcePath,
|
|
1070
|
+
error,
|
|
1071
|
+
content,
|
|
1072
|
+
valRoot,
|
|
1073
|
+
remoteHost
|
|
1074
|
+
}) {
|
|
1075
|
+
const ref = mediaValueOf(error.value)?.path;
|
|
1076
|
+
if (typeof ref !== "string") {
|
|
1077
|
+
return keep(error);
|
|
1078
|
+
}
|
|
1079
|
+
// A remote ref is a URL and is not expected on disk. Core does not emit these
|
|
1080
|
+
// fixes for one, but a stale ref should not be reported as a local mismatch.
|
|
1081
|
+
if (core.Internal.remote.splitRemoteRef(ref).status === "success") {
|
|
1082
|
+
return keep(error);
|
|
1083
|
+
}
|
|
1084
|
+
if (!fs__default["default"].existsSync(path__default["default"].join(valRoot, ref))) {
|
|
1085
|
+
// Reported as `val/file-not-found` instead; keeping the placeholder here
|
|
1086
|
+
// means this verdict never has the last word on a file that is not there.
|
|
1087
|
+
return keep(error);
|
|
1088
|
+
}
|
|
1089
|
+
let fixed;
|
|
1090
|
+
try {
|
|
1091
|
+
fixed = await server.createFixPatch({
|
|
1092
|
+
projectRoot: valRoot,
|
|
1093
|
+
remoteHost
|
|
1094
|
+
},
|
|
1095
|
+
// `false`: this is a question, not a fix. Asking for the patch would have
|
|
1096
|
+
// createFixPatch read and rewrite files behind the editor's back.
|
|
1097
|
+
false, sourcePath, error, {}, content.source, content.schema);
|
|
1098
|
+
} catch {
|
|
1099
|
+
return keep(error);
|
|
1100
|
+
}
|
|
1101
|
+
return (fixed?.remainingErrors ?? []).map(remaining => ({
|
|
1102
|
+
message: remaining.message,
|
|
1103
|
+
// `createFixPatch` clears `fixes` on the per-field errors it reports, but
|
|
1104
|
+
// the fix is still available and still the remedy -- it is what the
|
|
1105
|
+
// placeholder was asking for. Carrying the placeholder's own fixes is what
|
|
1106
|
+
// keeps the "update image metadata" quick fix offered, and the diagnostic
|
|
1107
|
+
// a Warning rather than an Error.
|
|
1108
|
+
...(error.fixes ? {
|
|
1109
|
+
fixes: error.fixes
|
|
1110
|
+
} : {}),
|
|
1111
|
+
...(error.value !== undefined ? {
|
|
1112
|
+
value: error.value
|
|
1113
|
+
} : {})
|
|
1114
|
+
}));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* The `accept` a media schema declares, if any.
|
|
1119
|
+
*
|
|
1120
|
+
* Read from the serialized schema (`SerializedImageSchema.options`) rather
|
|
1121
|
+
* than from a schema instance: this runs against what `Service.get` returns.
|
|
1122
|
+
*/
|
|
1123
|
+
function acceptOf(schema) {
|
|
1124
|
+
if (typeof schema !== "object" || schema === null || !("options" in schema)) {
|
|
1125
|
+
return undefined;
|
|
1126
|
+
}
|
|
1127
|
+
const {
|
|
1128
|
+
options
|
|
1129
|
+
} = schema;
|
|
1130
|
+
if (typeof options !== "object" || options === null || !("accept" in options)) {
|
|
1131
|
+
return undefined;
|
|
1132
|
+
}
|
|
1133
|
+
const {
|
|
1134
|
+
accept
|
|
1135
|
+
} = options;
|
|
1136
|
+
return typeof accept === "string" ? accept : undefined;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/** A media value, read as a plain record. */
|
|
1140
|
+
function mediaValueOf(value) {
|
|
1141
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1142
|
+
return undefined;
|
|
1143
|
+
}
|
|
1144
|
+
return value;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* The serialized schema at a source path.
|
|
1149
|
+
*
|
|
1150
|
+
* Only the schema is taken from resolution -- `accept` is not on the value, and
|
|
1151
|
+
* the value itself comes from the error. Same call `missingFileRef` in
|
|
1152
|
+
* `diagnostics.ts` makes, and it can fail the same ways (a schema that failed
|
|
1153
|
+
* to serialize, a path that no longer resolves), so failure is not an error
|
|
1154
|
+
* here, just an absence.
|
|
1155
|
+
*/
|
|
1156
|
+
function resolveSchemaAt$1(sourcePath, content) {
|
|
1157
|
+
if (!content.source || !content.schema) {
|
|
1158
|
+
return undefined;
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
|
|
1162
|
+
return core.Internal.resolvePath(modulePath, content.source, content.schema).schema;
|
|
1163
|
+
} catch {
|
|
1164
|
+
return undefined;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
860
1168
|
/** Marks diagnostics as ours, so a client can filter on it. */
|
|
861
1169
|
const VAL_DIAGNOSTIC_SOURCE = "val";
|
|
862
1170
|
|
|
@@ -948,7 +1256,8 @@ function createValDiagnostics({
|
|
|
948
1256
|
text,
|
|
949
1257
|
valRoot,
|
|
950
1258
|
snapshot,
|
|
951
|
-
galleryChecks
|
|
1259
|
+
galleryChecks,
|
|
1260
|
+
mediaMetadataChecks
|
|
952
1261
|
}) {
|
|
953
1262
|
if (content.errors === false) {
|
|
954
1263
|
return [];
|
|
@@ -1020,6 +1329,33 @@ function createValDiagnostics({
|
|
|
1020
1329
|
continue;
|
|
1021
1330
|
}
|
|
1022
1331
|
|
|
1332
|
+
// An unconditional media metadata placeholder: core reports
|
|
1333
|
+
// `image:check-metadata` on every `s.image()` carrying metadata, whether
|
|
1334
|
+
// or not it disagrees with the file. Show only what the adjudication
|
|
1335
|
+
// actually found, and nothing when it found nothing. Deliberately after
|
|
1336
|
+
// the missing-file branch above, so a deleted file is still reported as
|
|
1337
|
+
// `val/file-not-found` rather than as a metadata mismatch.
|
|
1338
|
+
if (isDeferredMediaMetadataCheckAt({
|
|
1339
|
+
sourcePath,
|
|
1340
|
+
error,
|
|
1341
|
+
content
|
|
1342
|
+
})) {
|
|
1343
|
+
const verdict = mediaMetadataChecks?.get(mediaMetadataCheckKey(sourcePath, error));
|
|
1344
|
+
for (const finding of verdict ?? []) {
|
|
1345
|
+
diagnostics.push(build(rangeOf(sourcePath, modulePathMap), finding.message, {
|
|
1346
|
+
code: "val/validation",
|
|
1347
|
+
sourcePath,
|
|
1348
|
+
...(finding.fixes ? {
|
|
1349
|
+
fixes: finding.fixes
|
|
1350
|
+
} : {}),
|
|
1351
|
+
...(finding.value !== undefined ? {
|
|
1352
|
+
value: finding.value
|
|
1353
|
+
} : {})
|
|
1354
|
+
}));
|
|
1355
|
+
}
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1023
1359
|
// A gallery-backed field whose path the gallery does not track. Core
|
|
1024
1360
|
// reports it with no fix because the remedy is elsewhere; giving it its
|
|
1025
1361
|
// own code is what lets the editor offer the two remedies.
|
|
@@ -2110,6 +2446,17 @@ async function createValCodeActions({
|
|
|
2110
2446
|
remoteHost = process.env.VAL_REMOTE_HOST || core.DEFAULT_VAL_REMOTE_HOST
|
|
2111
2447
|
}) {
|
|
2112
2448
|
const actions = [];
|
|
2449
|
+
/**
|
|
2450
|
+
* Fixes already offered, as `<fix target>|<fix>`.
|
|
2451
|
+
*
|
|
2452
|
+
* One problem can be reported as several diagnostics that share a fix: a
|
|
2453
|
+
* stale image reports its width and its height separately, and a gallery
|
|
2454
|
+
* check reports one finding per entry. The fix is the same patch each time --
|
|
2455
|
+
* `createFixPatch` corrects every field, and the gallery branch walks every
|
|
2456
|
+
* entry -- so without this the editor offers the identical "Val: update image
|
|
2457
|
+
* metadata" twice, and recomputes it (re-reading the image) to do so.
|
|
2458
|
+
*/
|
|
2459
|
+
const offered = new Set();
|
|
2113
2460
|
for (const diagnostic of diagnostics) {
|
|
2114
2461
|
const data = diagnostic.data;
|
|
2115
2462
|
if (!data?.fixes?.length) {
|
|
@@ -2148,6 +2495,10 @@ async function createValCodeActions({
|
|
|
2148
2495
|
if (!isLocalFix(fix)) {
|
|
2149
2496
|
continue;
|
|
2150
2497
|
}
|
|
2498
|
+
const fixTarget = data.fixSourcePath ?? data.sourcePath;
|
|
2499
|
+
if (offered.has(`${fixTarget}|${fix}`)) {
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2151
2502
|
const fixEdit = await computeFixEdit({
|
|
2152
2503
|
document,
|
|
2153
2504
|
// A gallery check is reported on the entry but fixed against the record
|
|
@@ -2169,6 +2520,7 @@ async function createValCodeActions({
|
|
|
2169
2520
|
if (!fixEdit) {
|
|
2170
2521
|
continue;
|
|
2171
2522
|
}
|
|
2523
|
+
offered.add(`${fixTarget}|${fix}`);
|
|
2172
2524
|
actions.push(vscodeLanguageserver.CodeAction.create(FIX_TITLES[fix] ?? `Val: ${fix}`,
|
|
2173
2525
|
// Not always `document.uri`: a fix inside a `.jsonValues()` entry
|
|
2174
2526
|
// edits the entry's own `*.val.json`, which is a different file from
|
|
@@ -3530,6 +3882,14 @@ function createValLanguageServer(connection) {
|
|
|
3530
3882
|
runFixHandler: args => activeProject.runFixHandler(args)
|
|
3531
3883
|
})
|
|
3532
3884
|
}) : undefined;
|
|
3885
|
+
// Core also defers "does this image's stored metadata match its file",
|
|
3886
|
+
// reporting it on every `s.image()` that carries any metadata. Same
|
|
3887
|
+
// treatment: adjudicate with the fix machinery before showing anything.
|
|
3888
|
+
const mediaMetadataChecks = result.content.errors !== false && result.content.errors.validation ? await resolveMediaMetadataChecks({
|
|
3889
|
+
validation: result.content.errors.validation,
|
|
3890
|
+
content: result.content,
|
|
3891
|
+
valRoot: activeProject.valRoot
|
|
3892
|
+
}) : undefined;
|
|
3533
3893
|
const diagnostics = createValDiagnostics({
|
|
3534
3894
|
moduleFilePath,
|
|
3535
3895
|
content: result.content,
|
|
@@ -3540,6 +3900,9 @@ function createValLanguageServer(connection) {
|
|
|
3540
3900
|
} : {}),
|
|
3541
3901
|
...(galleryChecks ? {
|
|
3542
3902
|
galleryChecks
|
|
3903
|
+
} : {}),
|
|
3904
|
+
...(mediaMetadataChecks ? {
|
|
3905
|
+
mediaMetadataChecks
|
|
3543
3906
|
} : {})
|
|
3544
3907
|
});
|
|
3545
3908
|
const unregistered = findMissingModuleDiagnostic(project.valRoot, moduleFilePath, readOpenDocument);
|