@wildwinter/toolkit 0.2.3 → 0.2.5
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 +21 -0
- package/README.md +14 -0
- package/conformance/extensions/defaults.json +1 -0
- package/conformance/extensions/manifest.json +314 -0
- package/conformance/extensions/values.json +527 -1
- package/dist/extensions.cjs +158 -2
- package/dist/extensions.cjs.map +1 -1
- package/dist/extensions.d.cts +54 -3
- package/dist/extensions.d.ts +54 -3
- package/dist/extensions.js +155 -3
- package/dist/extensions.js.map +1 -1
- package/dist/index.cjs +39 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +36 -1
- package/dist/index.js.map +1 -1
- package/extensions.md +28 -1
- package/package.json +1 -1
package/dist/extensions.d.cts
CHANGED
|
@@ -18,7 +18,7 @@ declare const HOSTS: Readonly<Record<HostName, HostSpec>>;
|
|
|
18
18
|
declare const SCALAR_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
19
19
|
type ScalarType = typeof SCALAR_TYPES[number];
|
|
20
20
|
/** Every type a value can take. */
|
|
21
|
-
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "record", "map", "list"];
|
|
21
|
+
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "pick", "record", "map", "list"];
|
|
22
22
|
type ValueType = typeof VALUE_TYPES[number];
|
|
23
23
|
/** A JSON value, as extension data holds it. */
|
|
24
24
|
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
@@ -39,6 +39,8 @@ interface TypeDecl {
|
|
|
39
39
|
readonly renamedStages?: Readonly<Record<string, string>>;
|
|
40
40
|
/** A reference's node kind. */
|
|
41
41
|
readonly to?: string;
|
|
42
|
+
/** A pick's sources, in the order its choices are shown. */
|
|
43
|
+
readonly from?: readonly PickSource[];
|
|
42
44
|
/** A record's fields. */
|
|
43
45
|
readonly fields?: readonly FieldDecl[];
|
|
44
46
|
/** A map's key. */
|
|
@@ -58,6 +60,17 @@ interface TypeDecl {
|
|
|
58
60
|
/** Optional in source: the type's own default when absent (`defaultOf`). */
|
|
59
61
|
readonly default?: unknown;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Where a pick's choices come from: fixed words, the nodes of a reference kind (by id, as a reference holds
|
|
65
|
+
* them), or a list of text on the extension's own project node, by that field's name.
|
|
66
|
+
*/
|
|
67
|
+
type PickSource = {
|
|
68
|
+
readonly words: readonly string[];
|
|
69
|
+
} | {
|
|
70
|
+
readonly to: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly list: string;
|
|
73
|
+
};
|
|
61
74
|
/** A named field: a target's, or a record's. */
|
|
62
75
|
interface FieldDecl extends TypeDecl {
|
|
63
76
|
readonly name: string;
|
|
@@ -162,16 +175,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
|
|
|
162
175
|
readonly default?: unknown;
|
|
163
176
|
}): C;
|
|
164
177
|
|
|
165
|
-
/** What the host knows about its nodes, for references. */
|
|
178
|
+
/** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
|
|
166
179
|
interface ValueContext {
|
|
167
180
|
/** Does a node of `kind` with `id` exist? Absent, references are not checked
|
|
168
181
|
* (a host checking a manifest's default, say, has no nodes to hand). */
|
|
169
182
|
readonly exists?: (kind: string, id: string) => boolean;
|
|
183
|
+
/** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
|
|
184
|
+
* pick's `list` source. Absent, list sources are not checked. */
|
|
185
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
170
186
|
}
|
|
171
187
|
/** Every problem with `value` as a value of `decl`, paths relative to the value. */
|
|
172
188
|
declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
173
189
|
/** Every problem with one node's entry, against its target. */
|
|
174
190
|
declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
191
|
+
/** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
|
|
192
|
+
interface PickSources {
|
|
193
|
+
/** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
|
|
194
|
+
readonly nodes?: (kind: string) => readonly {
|
|
195
|
+
readonly id: string;
|
|
196
|
+
readonly label?: string;
|
|
197
|
+
}[];
|
|
198
|
+
/** As `ValueContext.list`. */
|
|
199
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
200
|
+
}
|
|
201
|
+
/** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
|
|
202
|
+
interface PickChoice {
|
|
203
|
+
readonly value: string;
|
|
204
|
+
readonly label: string;
|
|
205
|
+
readonly source: PickSource;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
|
|
209
|
+
* in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
|
|
210
|
+
* automatic.
|
|
211
|
+
*/
|
|
212
|
+
declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
|
|
213
|
+
/**
|
|
214
|
+
* A pick's names that more than one of its sources holds (a character and a point of interest both called
|
|
215
|
+
* Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
|
|
216
|
+
*/
|
|
217
|
+
declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
|
|
218
|
+
/**
|
|
219
|
+
* The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
|
|
220
|
+
* the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
|
|
221
|
+
* name that is no such field.
|
|
222
|
+
*/
|
|
223
|
+
declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
|
|
224
|
+
/** A list without ids holding text: what a pick's `list` source names. */
|
|
225
|
+
declare function isTextList(decl: TypeDecl): boolean;
|
|
175
226
|
/**
|
|
176
227
|
* A declaration's default: the one it gives, else its type's. A scalar's is
|
|
177
228
|
* the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
|
|
@@ -345,4 +396,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
345
396
|
/** `base` and then list position `i`. */
|
|
346
397
|
declare const atIndex: (base: string, i: number) => string;
|
|
347
398
|
|
|
348
|
-
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
399
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type PickChoice, type PickSource, type PickSources, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
package/dist/extensions.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const HOSTS: Readonly<Record<HostName, HostSpec>>;
|
|
|
18
18
|
declare const SCALAR_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
19
19
|
type ScalarType = typeof SCALAR_TYPES[number];
|
|
20
20
|
/** Every type a value can take. */
|
|
21
|
-
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "record", "map", "list"];
|
|
21
|
+
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "pick", "record", "map", "list"];
|
|
22
22
|
type ValueType = typeof VALUE_TYPES[number];
|
|
23
23
|
/** A JSON value, as extension data holds it. */
|
|
24
24
|
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
@@ -39,6 +39,8 @@ interface TypeDecl {
|
|
|
39
39
|
readonly renamedStages?: Readonly<Record<string, string>>;
|
|
40
40
|
/** A reference's node kind. */
|
|
41
41
|
readonly to?: string;
|
|
42
|
+
/** A pick's sources, in the order its choices are shown. */
|
|
43
|
+
readonly from?: readonly PickSource[];
|
|
42
44
|
/** A record's fields. */
|
|
43
45
|
readonly fields?: readonly FieldDecl[];
|
|
44
46
|
/** A map's key. */
|
|
@@ -58,6 +60,17 @@ interface TypeDecl {
|
|
|
58
60
|
/** Optional in source: the type's own default when absent (`defaultOf`). */
|
|
59
61
|
readonly default?: unknown;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Where a pick's choices come from: fixed words, the nodes of a reference kind (by id, as a reference holds
|
|
65
|
+
* them), or a list of text on the extension's own project node, by that field's name.
|
|
66
|
+
*/
|
|
67
|
+
type PickSource = {
|
|
68
|
+
readonly words: readonly string[];
|
|
69
|
+
} | {
|
|
70
|
+
readonly to: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly list: string;
|
|
73
|
+
};
|
|
61
74
|
/** A named field: a target's, or a record's. */
|
|
62
75
|
interface FieldDecl extends TypeDecl {
|
|
63
76
|
readonly name: string;
|
|
@@ -162,16 +175,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
|
|
|
162
175
|
readonly default?: unknown;
|
|
163
176
|
}): C;
|
|
164
177
|
|
|
165
|
-
/** What the host knows about its nodes, for references. */
|
|
178
|
+
/** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
|
|
166
179
|
interface ValueContext {
|
|
167
180
|
/** Does a node of `kind` with `id` exist? Absent, references are not checked
|
|
168
181
|
* (a host checking a manifest's default, say, has no nodes to hand). */
|
|
169
182
|
readonly exists?: (kind: string, id: string) => boolean;
|
|
183
|
+
/** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
|
|
184
|
+
* pick's `list` source. Absent, list sources are not checked. */
|
|
185
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
170
186
|
}
|
|
171
187
|
/** Every problem with `value` as a value of `decl`, paths relative to the value. */
|
|
172
188
|
declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
173
189
|
/** Every problem with one node's entry, against its target. */
|
|
174
190
|
declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
191
|
+
/** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
|
|
192
|
+
interface PickSources {
|
|
193
|
+
/** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
|
|
194
|
+
readonly nodes?: (kind: string) => readonly {
|
|
195
|
+
readonly id: string;
|
|
196
|
+
readonly label?: string;
|
|
197
|
+
}[];
|
|
198
|
+
/** As `ValueContext.list`. */
|
|
199
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
200
|
+
}
|
|
201
|
+
/** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
|
|
202
|
+
interface PickChoice {
|
|
203
|
+
readonly value: string;
|
|
204
|
+
readonly label: string;
|
|
205
|
+
readonly source: PickSource;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
|
|
209
|
+
* in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
|
|
210
|
+
* automatic.
|
|
211
|
+
*/
|
|
212
|
+
declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
|
|
213
|
+
/**
|
|
214
|
+
* A pick's names that more than one of its sources holds (a character and a point of interest both called
|
|
215
|
+
* Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
|
|
216
|
+
*/
|
|
217
|
+
declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
|
|
218
|
+
/**
|
|
219
|
+
* The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
|
|
220
|
+
* the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
|
|
221
|
+
* name that is no such field.
|
|
222
|
+
*/
|
|
223
|
+
declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
|
|
224
|
+
/** A list without ids holding text: what a pick's `list` source names. */
|
|
225
|
+
declare function isTextList(decl: TypeDecl): boolean;
|
|
175
226
|
/**
|
|
176
227
|
* A declaration's default: the one it gives, else its type's. A scalar's is
|
|
177
228
|
* the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
|
|
@@ -345,4 +396,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
345
396
|
/** `base` and then list position `i`. */
|
|
346
397
|
declare const atIndex: (base: string, i: number) => string;
|
|
347
398
|
|
|
348
|
-
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
399
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type PickChoice, type PickSource, type PickSources, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
package/dist/extensions.js
CHANGED
|
@@ -18,7 +18,7 @@ var HOSTS = {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
var SCALAR_TYPES = ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
21
|
-
var VALUE_TYPES = [...SCALAR_TYPES, "reference", "record", "map", "list"];
|
|
21
|
+
var VALUE_TYPES = [...SCALAR_TYPES, "reference", "pick", "record", "map", "list"];
|
|
22
22
|
|
|
23
23
|
// src/extensions/path.ts
|
|
24
24
|
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -102,6 +102,26 @@ function valueProblems(decl, value, ctx = {}, path = "") {
|
|
|
102
102
|
out.push(err("no-such-node", path, `${where(path)} names ${decl.to} "${value}", which does not exist`));
|
|
103
103
|
}
|
|
104
104
|
break;
|
|
105
|
+
case "pick": {
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
wrong("one of its choices, as text");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (value === "") break;
|
|
111
|
+
const sources = decl.from ?? [];
|
|
112
|
+
const holding = sources.filter((s) => sourceHolds(s, value, ctx) === true);
|
|
113
|
+
if (holding.length > 1) {
|
|
114
|
+
out.push({
|
|
115
|
+
severity: "warning",
|
|
116
|
+
code: "pick-ambiguous",
|
|
117
|
+
path,
|
|
118
|
+
message: `${where(path)} is "${value}", which is ${holding.map(sourceWord).join(" and ")}`
|
|
119
|
+
});
|
|
120
|
+
} else if (holding.length === 0 && sources.every((s) => sourceHolds(s, value, ctx) !== void 0)) {
|
|
121
|
+
out.push(err("not-a-choice", path, `${where(path)} is "${value}", which is not one of its choices`));
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
105
125
|
case "record":
|
|
106
126
|
if (!isObject(value)) {
|
|
107
127
|
wrong("an object");
|
|
@@ -181,11 +201,69 @@ function entryProblems(target, entry, ctx = {}, path = "") {
|
|
|
181
201
|
}
|
|
182
202
|
return fieldsProblems(target.fields ?? [], entry, ctx, path);
|
|
183
203
|
}
|
|
204
|
+
function sourceHolds(source, value, ctx) {
|
|
205
|
+
if ("words" in source) return Array.isArray(source.words) && source.words.includes(value);
|
|
206
|
+
if ("to" in source) return ctx.exists === void 0 ? void 0 : ctx.exists(source.to, value);
|
|
207
|
+
if ("list" in source) return ctx.list === void 0 ? void 0 : (ctx.list(source.list) ?? []).includes(value);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
function sourceWord(source) {
|
|
211
|
+
if ("words" in source) return "a fixed word";
|
|
212
|
+
if ("to" in source) return source.to === "cast" ? "a cast member" : `a ${source.to}`;
|
|
213
|
+
return `an entry of the list "${source.list}"`;
|
|
214
|
+
}
|
|
215
|
+
function pickChoices(decl, src) {
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const source of decl.from ?? []) {
|
|
218
|
+
if ("words" in source) {
|
|
219
|
+
for (const w of source.words ?? []) if (typeof w === "string" && w !== "") out.push({ value: w, label: w, source });
|
|
220
|
+
} else if ("to" in source) {
|
|
221
|
+
for (const n of src.nodes?.(source.to) ?? []) if (n.id !== "") out.push({ value: n.id, label: n.label ?? n.id, source });
|
|
222
|
+
} else if ("list" in source) {
|
|
223
|
+
for (const e of src.list?.(source.list) ?? []) if (e !== "") out.push({ value: e, label: e, source });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function pickClashes(decl, src, path = "") {
|
|
229
|
+
const by = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const c of pickChoices(decl, src)) {
|
|
231
|
+
const held = by.get(c.value) ?? [];
|
|
232
|
+
if (!held.includes(c.source)) held.push(c.source);
|
|
233
|
+
by.set(c.value, held);
|
|
234
|
+
}
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const [name, sources] of by) {
|
|
237
|
+
if (sources.length < 2) continue;
|
|
238
|
+
out.push({
|
|
239
|
+
severity: "warning",
|
|
240
|
+
code: "pick-clash",
|
|
241
|
+
path,
|
|
242
|
+
options: [name],
|
|
243
|
+
message: `"${name}" is ${sources.map(sourceWord).join(" and ")}, so a value naming it is ambiguous`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function pickLists(projectTarget, projectEntry) {
|
|
249
|
+
return (name) => {
|
|
250
|
+
const field = (projectTarget?.fields ?? []).find((f) => f.name === name);
|
|
251
|
+
if (field === void 0 || !isTextList(field)) return void 0;
|
|
252
|
+
const held = projectEntry !== void 0 && own(projectEntry, name) ? projectEntry[name] : void 0;
|
|
253
|
+
const value = Array.isArray(held) ? held : defaultOf(field);
|
|
254
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function isTextList(decl) {
|
|
258
|
+
return decl.type === "list" && decl.id === void 0 && decl.of?.type === "text";
|
|
259
|
+
}
|
|
184
260
|
function defaultOf(decl) {
|
|
185
261
|
if (decl.default !== void 0) return decl.default;
|
|
186
262
|
switch (decl.type) {
|
|
187
263
|
case "reference":
|
|
188
264
|
return "";
|
|
265
|
+
case "pick":
|
|
266
|
+
return "";
|
|
189
267
|
case "record":
|
|
190
268
|
return Object.fromEntries((decl.fields ?? []).map((f) => [f.name, defaultOf(f)]));
|
|
191
269
|
case "map":
|
|
@@ -278,7 +356,7 @@ function renameStages(target, entry) {
|
|
|
278
356
|
var ID = /^[a-z][a-z0-9-]*$/;
|
|
279
357
|
var VERSION = /^\d+\.\d+\.\d+$/;
|
|
280
358
|
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
281
|
-
var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
|
|
359
|
+
var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
|
|
282
360
|
var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
|
|
283
361
|
var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
|
|
284
362
|
var BARE_KEYS = new Set(TYPE_KEYS);
|
|
@@ -328,9 +406,80 @@ function manifestProblems(manifest) {
|
|
|
328
406
|
}
|
|
329
407
|
targetProblems(host, kind, target, kindPath, settings, out);
|
|
330
408
|
}
|
|
409
|
+
pickListProblems(kinds, hostPath, out);
|
|
331
410
|
}
|
|
332
411
|
return out;
|
|
333
412
|
}
|
|
413
|
+
function pickSourcesSound(host, from, path, out) {
|
|
414
|
+
if (!Array.isArray(from) || from.length === 0) {
|
|
415
|
+
out.push(err2("pick-from", path, 'a pick lists its sources in "from": { "words": [...] }, { "to": a node kind }, or { "list": a project list }'));
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
let sound = true;
|
|
419
|
+
from.forEach((s, i) => {
|
|
420
|
+
const at = atIndex(path, i);
|
|
421
|
+
const keys = isObject(s) ? Object.keys(s).filter((k) => k === "words" || k === "to" || k === "list") : [];
|
|
422
|
+
if (!isObject(s) || keys.length !== 1) {
|
|
423
|
+
out.push(err2("pick-source", at, 'a source is one of { "words": [...] }, { "to": a node kind }, { "list": a project list }'));
|
|
424
|
+
sound = false;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
unknownKeys(s, new Set(keys), at, out);
|
|
428
|
+
if (keys[0] === "words") {
|
|
429
|
+
const words = s["words"];
|
|
430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
431
|
+
if (!Array.isArray(words) || words.length === 0) {
|
|
432
|
+
out.push(err2("pick-words", atKey(at, "words"), "fixed words are a list of at least one"));
|
|
433
|
+
sound = false;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
words.forEach((w, j) => {
|
|
437
|
+
if (typeof w !== "string" || w.trim() === "") {
|
|
438
|
+
out.push(err2("value-empty", atIndex(atKey(at, "words"), j), "a word has a name"));
|
|
439
|
+
sound = false;
|
|
440
|
+
} else if (seen.has(w)) {
|
|
441
|
+
out.push(err2("value-duplicate", atIndex(atKey(at, "words"), j), `"${w}" is listed twice`));
|
|
442
|
+
sound = false;
|
|
443
|
+
} else seen.add(w);
|
|
444
|
+
});
|
|
445
|
+
} else if (keys[0] === "to") {
|
|
446
|
+
if (typeof s["to"] !== "string" || !HOSTS[host].references.includes(s["to"])) {
|
|
447
|
+
out.push(err2("reference-to", atKey(at, "to"), `a source's nodes are of a kind, one of ${HOSTS[host].references.join(", ")}`));
|
|
448
|
+
sound = false;
|
|
449
|
+
}
|
|
450
|
+
} else if (typeof s["list"] !== "string" || !NAME.test(s["list"])) {
|
|
451
|
+
out.push(err2("pick-list", atKey(at, "list"), "a list source names a field of the extension's project node"));
|
|
452
|
+
sound = false;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return sound;
|
|
456
|
+
}
|
|
457
|
+
function pickListProblems(kinds, hostPath, out) {
|
|
458
|
+
const project = isObject(kinds["project"]) && Array.isArray(kinds["project"]["fields"]) ? kinds["project"]["fields"] : [];
|
|
459
|
+
const lists = new Set(project.filter((f) => isObject(f) && isTextList(f)).map((f) => f.name));
|
|
460
|
+
const walk = (decl, path) => {
|
|
461
|
+
if (!isObject(decl)) return;
|
|
462
|
+
const d = decl;
|
|
463
|
+
if (d.type === "pick" && Array.isArray(d.from)) {
|
|
464
|
+
d.from.forEach((s, i) => {
|
|
465
|
+
if (isObject(s) && typeof s.list === "string" && !lists.has(s.list)) {
|
|
466
|
+
const name = s.list;
|
|
467
|
+
out.push(err2(
|
|
468
|
+
"pick-list",
|
|
469
|
+
atKey(atIndex(atKey(path, "from"), i), "list"),
|
|
470
|
+
`"${name}" is not a list of text on the extension's project node; declare it there, as { "name": "${name}", "type": "list", "of": { "type": "text" } }`
|
|
471
|
+
));
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (Array.isArray(d.fields)) d.fields.forEach((f, i) => walk(f, atIndex(atKey(path, "fields"), i)));
|
|
476
|
+
if (d.of !== void 0) walk(d.of, atKey(path, "of"));
|
|
477
|
+
};
|
|
478
|
+
for (const [kind, target] of Object.entries(kinds)) {
|
|
479
|
+
if (!isObject(target) || !Array.isArray(target["fields"])) continue;
|
|
480
|
+
target["fields"].forEach((f, i) => walk(f, atIndex(atKey(atKey(hostPath, kind), "fields"), i)));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
334
483
|
function unknownKeys(obj, known, path, out) {
|
|
335
484
|
for (const k of Object.keys(obj)) {
|
|
336
485
|
if (!known.has(k)) {
|
|
@@ -491,6 +640,9 @@ function typeProblems(host, decl, path, named, out, claim = false) {
|
|
|
491
640
|
sound = false;
|
|
492
641
|
}
|
|
493
642
|
break;
|
|
643
|
+
case "pick":
|
|
644
|
+
if (!pickSourcesSound(host, decl.from, at("from"), out)) sound = false;
|
|
645
|
+
break;
|
|
494
646
|
case "record":
|
|
495
647
|
if (!Array.isArray(decl.fields) || decl.fields.length === 0) {
|
|
496
648
|
out.push(err2("record-empty", at("fields"), "a record declares its fields"));
|
|
@@ -965,6 +1117,6 @@ function orphanedEntries(data, exists) {
|
|
|
965
1117
|
return out;
|
|
966
1118
|
}
|
|
967
1119
|
|
|
968
|
-
export { ATOMIC, COMMENTS, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
1120
|
+
export { ATOMIC, COMMENTS, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
969
1121
|
//# sourceMappingURL=extensions.js.map
|
|
970
1122
|
//# sourceMappingURL=extensions.js.map
|