@loomcli/core 0.7.0 → 0.8.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/dist/application.d.ts +11 -6
- package/dist/application.js +11 -5
- package/dist/chain.d.ts +28 -17
- package/dist/chain.js +11 -10
- package/dist/command-rules.d.ts +1 -1
- package/dist/command-rules.js +2 -2
- package/dist/command.d.ts +31 -47
- package/dist/command.js +136 -170
- package/dist/errors.d.ts +22 -21
- package/dist/errors.js +38 -18
- package/dist/globals.d.ts +48 -22
- package/dist/globals.js +63 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/input-rules.d.ts +16 -2
- package/dist/input-rules.js +40 -5
- package/dist/inspect.d.ts +35 -11
- package/dist/inspect.js +69 -66
- package/dist/locate.js +52 -60
- package/dist/options.d.ts +94 -83
- package/dist/options.js +298 -260
- package/dist/parse.d.ts +162 -0
- package/dist/parse.js +601 -0
- package/dist/plugin-rules.d.ts +2 -4
- package/dist/plugin-rules.js +3 -8
- package/dist/plugin.d.ts +26 -21
- package/dist/plugin.js +10 -45
- package/dist/sources.d.ts +18 -9
- package/dist/sources.js +46 -16
- package/dist/types.d.ts +68 -28
- package/dist/validation.d.ts +84 -31
- package/dist/validation.js +222 -110
- package/package.json +1 -1
package/dist/validation.js
CHANGED
|
@@ -4,8 +4,8 @@ import { escapeControlCharacters } from './controls.js';
|
|
|
4
4
|
import { elided, spelled } from './diagnostic-text.js';
|
|
5
5
|
import { asSentence, DeclarationError, InputError, quoted, reasonOf } from './errors.js';
|
|
6
6
|
import { callSite, factFault, flagFault, partOf, siteFinding } from './facts.js';
|
|
7
|
-
import { booleanOptionValueRule, defaultDepth, defaultLevels, defaultShape, invalidDefault, notAValidator, omissionAlreadyDecided, omissionWithoutValidator, requiredWithDefault, } from './input-rules.js';
|
|
8
|
-
import { booleanValue } from './options.js';
|
|
7
|
+
import { booleanOptionValueRule, countOptionValueRule, defaultDepth, defaultLevels, defaultShape, invalidDefault, invalidImplied, notAValidator, omissionAlreadyDecided, omissionWithoutValidator, requiredWithDefault, } from './input-rules.js';
|
|
8
|
+
import { booleanValue, reportedOf, spellingsOf } from './options.js';
|
|
9
9
|
import { boundedSnapshot, NestedTooDeepError, shallowList } from './plain.js';
|
|
10
10
|
import { notAnObject } from './plugin-rules.js';
|
|
11
11
|
import { validatorFailed } from './rules.js';
|
|
@@ -44,6 +44,10 @@ class ValidatedInputs {
|
|
|
44
44
|
read(input) {
|
|
45
45
|
return this.#values.get(input);
|
|
46
46
|
}
|
|
47
|
+
/** Whether this pass validated the declaration's value. */
|
|
48
|
+
has(input) {
|
|
49
|
+
return this.#values.has(input);
|
|
50
|
+
}
|
|
47
51
|
#field(input) {
|
|
48
52
|
// Last resort: no typed path exists. The map stores every validated value as `unknown`.
|
|
49
53
|
// An object literal with a generic computed key does not type as `Record<Name, _>` either.
|
|
@@ -56,14 +60,14 @@ class ValidatedInputs {
|
|
|
56
60
|
}
|
|
57
61
|
/**
|
|
58
62
|
* Authoring's one read of a config, through `captureDeclaration`: the prototype verdict, the copy of
|
|
59
|
-
* every own string key, the
|
|
60
|
-
* one try. Every later check, the registry entry, and every sentence read the copy
|
|
61
|
-
* author's object again, so a getter runs once and the caller's later changes reach
|
|
62
|
-
* default is copied and frozen to `defaultLevels` levels, cycles included, and that
|
|
63
|
-
* value the graph publishes and a run validates; every other property is captured as
|
|
64
|
-
* because core clones no library object. A read that throws, from a getter or a proxy
|
|
65
|
-
* unreadable fault, named by the key it threw in, a default nested deeper is the
|
|
66
|
-
* and a value that is not a plain object is the not-an-object fault.
|
|
63
|
+
* every own string key, the copies of its `extensions` and `aliases` lists, and the snapshot of its
|
|
64
|
+
* default, inside one try. Every later check, the registry entry, and every sentence read the copy
|
|
65
|
+
* and never the author's object again, so a getter runs once and the caller's later changes reach
|
|
66
|
+
* nothing. The default is copied and frozen to `defaultLevels` levels, cycles included, and that
|
|
67
|
+
* copy is the value the graph publishes and a run validates; every other property is captured as
|
|
68
|
+
* declared, because core clones no library object. A read that throws, from a getter or a proxy
|
|
69
|
+
* trap, is the unreadable fault, named by the key it threw in, a default nested deeper is the
|
|
70
|
+
* too-deep fault, and a value that is not a plain object is the not-an-object fault.
|
|
67
71
|
*/
|
|
68
72
|
export function captureConfig(config, faults) {
|
|
69
73
|
const { notAnObject: notAnObjectFault, tooDeep, unreadable } = faults;
|
|
@@ -71,6 +75,7 @@ export function captureConfig(config, faults) {
|
|
|
71
75
|
// Presence is the key, so a declared `default: undefined` stays a default.
|
|
72
76
|
read.nested(copy, 'default', (value) => boundedSnapshot(value, defaultLevels));
|
|
73
77
|
read.nested(copy, 'extensions', shallowList);
|
|
78
|
+
read.nested(copy, 'aliases', shallowList);
|
|
74
79
|
}, {
|
|
75
80
|
notAnObject: notAnObjectFault,
|
|
76
81
|
unreadable: (thrown, slot) => thrown instanceof NestedTooDeepError ? tooDeep() : unreadable(thrown, slot),
|
|
@@ -125,11 +130,11 @@ export function declarationSubject(input) {
|
|
|
125
130
|
: `Option ${quoted(input.name)}`;
|
|
126
131
|
}
|
|
127
132
|
/**
|
|
128
|
-
* Where one input was declared:
|
|
129
|
-
*
|
|
133
|
+
* Where one Command's own input was declared: its `argument()` or `option()` call on the Command at
|
|
134
|
+
* `path`. A global option's site is the built table's, under `BuiltGlobals.sites`.
|
|
130
135
|
*/
|
|
131
|
-
export function inputPlace(input,
|
|
132
|
-
return
|
|
136
|
+
export function inputPlace(input, path) {
|
|
137
|
+
return { call: input.kind, path };
|
|
133
138
|
}
|
|
134
139
|
/**
|
|
135
140
|
* The site of the call that declared one input at `place`, rebuilt as `call(name, config)`. Every
|
|
@@ -149,23 +154,15 @@ export function configUnread(site) {
|
|
|
149
154
|
return { ...site, declaration: { ...site.declaration, arguments: shown } };
|
|
150
155
|
}
|
|
151
156
|
/**
|
|
152
|
-
* The token an operator would type for one declaration:
|
|
153
|
-
*
|
|
157
|
+
* The token an operator would type for one declaration: an option's reported spelling, read from
|
|
158
|
+
* the routed Command's table, and the declared name for an argument. Every input diagnostic and
|
|
154
159
|
* every reported problem names the declaration this way, so an omission and a rejected value read
|
|
155
160
|
* alike and a `shortOnly` option is never named by a long form it does not accept.
|
|
156
161
|
*/
|
|
157
|
-
function spellingOf(input) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const { config } = input;
|
|
162
|
-
if (config.shortOnly === true && config.short !== undefined) {
|
|
163
|
-
return `-${config.short}`;
|
|
164
|
-
}
|
|
165
|
-
// A negative-only Boolean option accepts its negative form alone.
|
|
166
|
-
return config.type === 'boolean' && config.polarity === 'negative'
|
|
167
|
-
? `--no-${input.name}`
|
|
168
|
-
: `--${input.name}`;
|
|
162
|
+
function spellingOf(input, table) {
|
|
163
|
+
return input.kind === 'argument'
|
|
164
|
+
? input.name
|
|
165
|
+
: reportedOf(spellingsOf(table, input.name), input.name);
|
|
169
166
|
}
|
|
170
167
|
/**
|
|
171
168
|
* An input diagnostic names the declaration by kind and by the spelling that reaches it. A value an
|
|
@@ -249,7 +246,7 @@ function checkOmissionValidation(input, site, subject) {
|
|
|
249
246
|
});
|
|
250
247
|
}
|
|
251
248
|
}
|
|
252
|
-
/** The value rules a Boolean option may not declare, in the order
|
|
249
|
+
/** The value rules a Boolean or counted option may not declare, in the order a diagnostic names them. */
|
|
253
250
|
const valueRules = ['validate', 'default', 'required', 'validateOmitted'];
|
|
254
251
|
/** Whether a `validate` value is a Standard Schema v1 object that core can call. */
|
|
255
252
|
function isStandardSchema(validator) {
|
|
@@ -263,17 +260,32 @@ function isStandardSchema(validator) {
|
|
|
263
260
|
typeof Reflect.get(props, 'vendor') === 'string' &&
|
|
264
261
|
typeof Reflect.get(props, 'validate') === 'function');
|
|
265
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The value rules a Boolean or counted option declares, which take a value it never consumes. Each
|
|
265
|
+
* kind names its own absent value in the correction.
|
|
266
|
+
*/
|
|
267
|
+
function checkValueless(config, site, subject) {
|
|
268
|
+
const declared = valueRules.find((key) => key in config);
|
|
269
|
+
if (declared === undefined) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (config.type === 'count') {
|
|
273
|
+
throw factFault(countOptionValueRule, site, {
|
|
274
|
+
correction: `Remove ${declared}; a counted option reads 0 when no occurrence supplies it.`,
|
|
275
|
+
fact: declared,
|
|
276
|
+
sentence: `${subject} is a counted option and declares ${declared}.`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
throw factFault(booleanOptionValueRule, site, {
|
|
280
|
+
correction: `Remove ${declared}; use polarity to control its absent value.`,
|
|
281
|
+
fact: declared,
|
|
282
|
+
sentence: `${subject} is Boolean and declares ${declared}.`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
266
285
|
function checkDeclaration(input, site, subject) {
|
|
267
286
|
const { config } = input;
|
|
268
|
-
if (input.kind === 'option' && input.config.type
|
|
269
|
-
|
|
270
|
-
if (declared !== undefined) {
|
|
271
|
-
throw factFault(booleanOptionValueRule, site, {
|
|
272
|
-
correction: `Remove ${declared}; use polarity to control its absent value.`,
|
|
273
|
-
fact: declared,
|
|
274
|
-
sentence: `${subject} is Boolean and declares ${declared}.`,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
287
|
+
if (input.kind === 'option' && input.config.type !== 'string') {
|
|
288
|
+
checkValueless(input.config, site, subject);
|
|
277
289
|
return;
|
|
278
290
|
}
|
|
279
291
|
if (config.required !== undefined && typeof config.required !== 'boolean') {
|
|
@@ -341,7 +353,7 @@ function readIssue(issue) {
|
|
|
341
353
|
* belong to the value, so the caller names those.
|
|
342
354
|
*/
|
|
343
355
|
async function validate(input, raw, call) {
|
|
344
|
-
const { context,
|
|
356
|
+
const { context, site } = call;
|
|
345
357
|
const validator = input.config.validate;
|
|
346
358
|
if (validator === undefined) {
|
|
347
359
|
return { value: raw };
|
|
@@ -362,7 +374,6 @@ async function validate(input, raw, call) {
|
|
|
362
374
|
}
|
|
363
375
|
catch (error) {
|
|
364
376
|
// The reason is the author's detail: a distributed build shows the generic defect message.
|
|
365
|
-
const site = place === undefined ? undefined : declaringSite(input, place);
|
|
366
377
|
throw new DeclarationError(validatorFailed, {
|
|
367
378
|
correction: 'Fix the validator.',
|
|
368
379
|
findings: site === undefined ? [] : [siteFinding(site, partOf(site, 'validate'))],
|
|
@@ -370,17 +381,34 @@ async function validate(input, raw, call) {
|
|
|
370
381
|
}, { cause: error });
|
|
371
382
|
}
|
|
372
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Where a bare spelling supplied one option's implied value, with that value's prepared output, or
|
|
386
|
+
* `undefined` when no bare spelling supplied it.
|
|
387
|
+
*/
|
|
388
|
+
function impliedPositions(input, read) {
|
|
389
|
+
const positions = input.kind === 'option' ? read.options.implied.get(input.name) : undefined;
|
|
390
|
+
return positions === undefined
|
|
391
|
+
? undefined
|
|
392
|
+
: { output: read.declaredValues.implied.get(input), positions };
|
|
393
|
+
}
|
|
373
394
|
/**
|
|
374
395
|
* One validation of a declared value. A multiple option or a variadic argument passes each of its
|
|
375
396
|
* values through the validator in order, and each issue reads at its value's position before its
|
|
376
397
|
* own path, so the action receives the array of outputs. Every other input passes its value once.
|
|
377
|
-
*
|
|
378
|
-
*
|
|
398
|
+
* A bare spelling's implied value never meets the validator here: its output was prepared before
|
|
399
|
+
* any token, so a scalar that a bare spelling supplied, and each bare occurrence in a multiple
|
|
400
|
+
* option's list, reuse a copy of that output in place. Each call reads a fresh context whose
|
|
401
|
+
* arrays are copies, so a write to them never reaches the next call. The host is the one captured
|
|
402
|
+
* object that every call and the action share.
|
|
379
403
|
*/
|
|
380
404
|
async function validateDeclared(input, raw, call) {
|
|
381
|
-
const { context,
|
|
405
|
+
const { context, implied, signal, site } = call;
|
|
406
|
+
if (!collects(input) && implied?.positions.includes(0) === true) {
|
|
407
|
+
// A bare spelling supplied the implied value, whose output was prepared before any token.
|
|
408
|
+
return { value: freshDeclaredValue(implied.output) };
|
|
409
|
+
}
|
|
382
410
|
if (!collects(input) || input.config.validate === undefined) {
|
|
383
|
-
return validate(input, raw, { context: context(),
|
|
411
|
+
return validate(input, raw, { context: context(), site });
|
|
384
412
|
}
|
|
385
413
|
if (!Array.isArray(raw)) {
|
|
386
414
|
// The parser, the input sources, and the declaration rules only ever supply an array here.
|
|
@@ -393,7 +421,10 @@ async function validateDeclared(input, raw, call) {
|
|
|
393
421
|
// A cancelled run starts no further call; the run resolves its cancellation code instead.
|
|
394
422
|
break;
|
|
395
423
|
}
|
|
396
|
-
|
|
424
|
+
// A bare occurrence holds the implied value, whose output was prepared before any token.
|
|
425
|
+
const result = implied?.positions.includes(position) === true
|
|
426
|
+
? { value: freshDeclaredValue(implied.output) }
|
|
427
|
+
: await validate(input, value, { context: context(), site });
|
|
397
428
|
if (result.issues === undefined) {
|
|
398
429
|
outputs.push(result.value);
|
|
399
430
|
}
|
|
@@ -474,44 +505,81 @@ export function checkDeclarations(inputs, named) {
|
|
|
474
505
|
}
|
|
475
506
|
}
|
|
476
507
|
/**
|
|
477
|
-
* Every declared default, validated before any token is read
|
|
478
|
-
*
|
|
479
|
-
*
|
|
508
|
+
* Every declared default and implied value, validated before any token is read, in declaration
|
|
509
|
+
* order, a declaration's default before its implied value. The host is captured by then, so each
|
|
510
|
+
* validator reads the same Host its action will, under the `default` phase. `places` says where each
|
|
511
|
+
* declaration sits, which a rejected value's finding rebuilds. An implied value is judged whether or
|
|
512
|
+
* not an invocation holds a bare spelling, because the author declared it.
|
|
480
513
|
*/
|
|
481
|
-
export async function
|
|
482
|
-
const declarations = scoped(inputs);
|
|
514
|
+
export async function prepareDeclaredValues(inputs, host, places) {
|
|
483
515
|
const defaults = new Map();
|
|
484
|
-
|
|
516
|
+
const implied = new Map();
|
|
517
|
+
for (const entry of scoped(inputs)) {
|
|
485
518
|
const { input } = entry;
|
|
486
|
-
const
|
|
487
|
-
const place = places.get(input);
|
|
488
|
-
const result = await validateDeclared(input, input.config.default, {
|
|
519
|
+
const call = {
|
|
489
520
|
context: () => ({ host, input: identityOf(entry), phase: 'default' }),
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
if (
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
});
|
|
521
|
+
site: places.get(input),
|
|
522
|
+
};
|
|
523
|
+
if (hasDefault(input)) {
|
|
524
|
+
const result = await validateDeclared(input, input.config.default, call);
|
|
525
|
+
defaults.set(input, declaredOutput(input, result, { fault: 'default', site: call.site }));
|
|
526
|
+
}
|
|
527
|
+
const value = impliedOf(input);
|
|
528
|
+
if (value !== undefined) {
|
|
529
|
+
// A bare spelling supplies one value, so even a multiple option's validator reads it alone.
|
|
530
|
+
const result = await validate(input, value, { context: call.context(), site: call.site });
|
|
531
|
+
implied.set(input, declaredOutput(input, result, { fault: 'implied', site: call.site }));
|
|
502
532
|
}
|
|
503
|
-
defaults.set(input, result.value);
|
|
504
533
|
}
|
|
505
|
-
return defaults;
|
|
534
|
+
return { defaults, implied };
|
|
535
|
+
}
|
|
536
|
+
/** How each declared value names itself when its validator rejects it, and the fix. */
|
|
537
|
+
const declaredFaults = {
|
|
538
|
+
default: {
|
|
539
|
+
correction: 'Fix the default or its validator.',
|
|
540
|
+
noun: 'default',
|
|
541
|
+
rule: invalidDefault,
|
|
542
|
+
},
|
|
543
|
+
implied: {
|
|
544
|
+
correction: 'Fix the implied value or its validator.',
|
|
545
|
+
noun: 'implied value',
|
|
546
|
+
rule: invalidImplied,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* The output of a declared value its validator accepted, or the declaration fault it is when the
|
|
551
|
+
* validator rejected it, one line per issue under the sentence, marking the key that declares it.
|
|
552
|
+
*/
|
|
553
|
+
function declaredOutput(input, result, declared) {
|
|
554
|
+
if (result.issues === undefined) {
|
|
555
|
+
return result.value;
|
|
556
|
+
}
|
|
557
|
+
const { fault, site } = declared;
|
|
558
|
+
const { correction, noun, rule } = declaredFaults[fault];
|
|
559
|
+
const subject = declarationSubject(input);
|
|
560
|
+
throw new DeclarationError(rule, {
|
|
561
|
+
correction,
|
|
562
|
+
findings: site === undefined ? [] : [siteFinding(site, partOf(site, fault))],
|
|
563
|
+
sentence: [
|
|
564
|
+
`${subject} has an invalid ${noun}.`,
|
|
565
|
+
...messages(subject, reported(result.issues)),
|
|
566
|
+
].join('\n'),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
/** The implied value a string option declares, which a bare spelling supplies, or `undefined`. */
|
|
570
|
+
function impliedOf(input) {
|
|
571
|
+
return input.kind === 'option' && input.config.type === 'string'
|
|
572
|
+
? input.config.implied
|
|
573
|
+
: undefined;
|
|
506
574
|
}
|
|
507
575
|
/**
|
|
508
|
-
* An array default reaches the action as its own mutable copy, so an action that
|
|
509
|
-
* rewrites neither the declaration nor the next invocation. One prepared
|
|
510
|
-
* invocation of a run, and an unvalidated default is the frozen snapshot the
|
|
511
|
-
* so each read copies it. The copy keeps a hole where the
|
|
512
|
-
* Every other output passes through unchanged.
|
|
576
|
+
* An array default or implied output reaches the action as its own mutable copy, so an action that
|
|
577
|
+
* mutates its array rewrites neither the declaration nor the next invocation. One prepared declared
|
|
578
|
+
* value serves every invocation of a run, and an unvalidated default is the frozen snapshot the
|
|
579
|
+
* declaring call took, so each read copies it. The copy keeps a hole where the value has one, as
|
|
580
|
+
* the graph's does. Every other output passes through unchanged.
|
|
513
581
|
*/
|
|
514
|
-
function
|
|
582
|
+
function freshDeclaredValue(value) {
|
|
515
583
|
// A spread reads a hole as `undefined`, and `slice` keeps it.
|
|
516
584
|
// oxlint-disable-next-line unicorn/prefer-spread
|
|
517
585
|
return Array.isArray(value) ? value.slice() : value;
|
|
@@ -532,6 +600,9 @@ function suppliedInputs(declarations, supplied) {
|
|
|
532
600
|
else if (input.config.type === 'boolean') {
|
|
533
601
|
options[input.name] = supplied.options.booleans.get(input.name);
|
|
534
602
|
}
|
|
603
|
+
else if (input.config.type === 'count') {
|
|
604
|
+
options[input.name] = supplied.options.counts.get(input.name);
|
|
605
|
+
}
|
|
535
606
|
else {
|
|
536
607
|
options[input.name] =
|
|
537
608
|
copied(suppliedOption(supplied.options, input.name, collected)) ??
|
|
@@ -540,22 +611,29 @@ function suppliedInputs(declarations, supplied) {
|
|
|
540
611
|
}
|
|
541
612
|
return { args, options };
|
|
542
613
|
}
|
|
543
|
-
/** The Boolean grammar's one issue, which a variable outside it reports. */
|
|
544
|
-
const grammarIssues = [{ message: 'Use true, false, 1, or 0.' }];
|
|
545
614
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
615
|
+
* The one issue a variable outside its option's grammar reports: the Boolean grammar, or the count
|
|
616
|
+
* grammar of ASCII decimal digits.
|
|
548
617
|
*/
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
618
|
+
function grammarIssues(input) {
|
|
619
|
+
return input.config.type === 'count'
|
|
620
|
+
? [{ message: 'Use a whole number of 0 or more.' }]
|
|
621
|
+
: [{ message: 'Use true, false, 1, or 0.' }];
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Whether a pass rejected a global option, which leaves no global value a middleware may read. A
|
|
625
|
+
* global option declares no presence rule, so every problem it reports is a rejected value.
|
|
626
|
+
*/
|
|
627
|
+
export function rejectsGlobal(validation) {
|
|
628
|
+
return validation.failure?.problems.some((problem) => problem.input.global) ?? false;
|
|
556
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Validates one invocation in reporting order: the global options, the application's and then each
|
|
632
|
+
* plugin's, and then the routed Command's own declarations, each in authoring order.
|
|
633
|
+
*/
|
|
557
634
|
export async function validateValues(invocation) {
|
|
558
|
-
const {
|
|
635
|
+
const { declaredValues, prior, sources, supplied } = invocation;
|
|
636
|
+
const { defaults } = declaredValues;
|
|
559
637
|
const declarations = scoped(invocation.inputs);
|
|
560
638
|
/** Where a filled option's value came from, which its diagnostic names; argv names none. */
|
|
561
639
|
const originOf = (input) => input.kind === 'option' ? sources.labels.get(input.name) : undefined;
|
|
@@ -588,24 +666,40 @@ export async function validateValues(invocation) {
|
|
|
588
666
|
};
|
|
589
667
|
};
|
|
590
668
|
const values = new Map();
|
|
591
|
-
|
|
592
|
-
const
|
|
669
|
+
// Each input reports at most once, so insertion order is the order this phase reaches them.
|
|
670
|
+
const reports = new Map();
|
|
671
|
+
/**
|
|
672
|
+
* One missing input, in the order this phase reaches it, unless a skipped configuration source
|
|
673
|
+
* would have been asked to fill it.
|
|
674
|
+
*/
|
|
675
|
+
const omit = (entry, spelling, line) => {
|
|
676
|
+
if (sources.unanswered?.has(entry.input) === true) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
reports.set(entry.input, {
|
|
680
|
+
lines: [line],
|
|
681
|
+
problem: { input: identityOf(entry), reason: 'missing', spelling },
|
|
682
|
+
});
|
|
683
|
+
};
|
|
593
684
|
/** One rejected input, whatever rejected it, in the order this phase reaches it. */
|
|
594
685
|
const reject = (entry, issues, subject) => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
686
|
+
reports.set(entry.input, {
|
|
687
|
+
lines: messages(subject, issues),
|
|
688
|
+
problem: {
|
|
689
|
+
input: identityOf(entry),
|
|
690
|
+
issues,
|
|
691
|
+
reason: 'invalid',
|
|
692
|
+
spelling: spellingOf(entry.input, invocation.table),
|
|
693
|
+
},
|
|
600
694
|
});
|
|
601
|
-
lines.push(...messages(subject, issues));
|
|
602
695
|
};
|
|
603
696
|
/** One path for every value a validator reads, so a raw shape and its issues meet it once. */
|
|
604
697
|
const accept = async (entry, raw, spelling) => {
|
|
605
698
|
const result = await validateDeclared(entry.input, raw, {
|
|
606
699
|
context: () => contextOf(entry),
|
|
607
|
-
|
|
700
|
+
implied: impliedPositions(entry.input, { declaredValues, options: supplied.options }),
|
|
608
701
|
signal: invocation.signal,
|
|
702
|
+
site: invocation.places.get(entry.input),
|
|
609
703
|
});
|
|
610
704
|
if (result.issues === undefined) {
|
|
611
705
|
values.set(entry.input, result.value);
|
|
@@ -613,7 +707,9 @@ export async function validateValues(invocation) {
|
|
|
613
707
|
}
|
|
614
708
|
reject(entry, reported(result.issues), suppliedName(entry.input, spelling, originOf(entry.input)));
|
|
615
709
|
};
|
|
616
|
-
|
|
710
|
+
const { only } = invocation;
|
|
711
|
+
const validated = only ? declarations.filter(({ input }) => only.has(input)) : declarations;
|
|
712
|
+
for (const entry of validated) {
|
|
617
713
|
if (invocation.signal.aborted) {
|
|
618
714
|
/**
|
|
619
715
|
* A cancelled run starts no further validator call. The one already in flight was awaited
|
|
@@ -624,16 +720,29 @@ export async function validateValues(invocation) {
|
|
|
624
720
|
}
|
|
625
721
|
const { input } = entry;
|
|
626
722
|
const variable = sources.rejected.get(input.name);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
723
|
+
const earlier = prior?.reports.get(input);
|
|
724
|
+
if (prior?.values.has(input) === true) {
|
|
725
|
+
// An earlier pass validated this value once, and one value meets its validator once.
|
|
726
|
+
values.set(input, prior.values.read(input));
|
|
727
|
+
}
|
|
728
|
+
else if (earlier !== undefined) {
|
|
729
|
+
// The earlier pass's problem reports here, in this pass's order.
|
|
730
|
+
reports.set(input, earlier);
|
|
731
|
+
}
|
|
732
|
+
else if (input.kind === 'option' && variable !== undefined) {
|
|
733
|
+
// A variable outside its option's grammar filled nothing, so it is the option's problem.
|
|
734
|
+
reject(entry, grammarIssues(input), suppliedName(input, spellingOf(input, invocation.table), variable));
|
|
630
735
|
}
|
|
631
736
|
else if (input.kind === 'option' && input.config.type === 'boolean') {
|
|
632
737
|
values.set(input, booleanValue(supplied.options, input.name, input.config));
|
|
633
738
|
}
|
|
739
|
+
else if (input.kind === 'option' && input.config.type === 'count') {
|
|
740
|
+
// A count passes no validator, and no occurrence and no source reads 0.
|
|
741
|
+
values.set(input, supplied.options.counts.get(input.name) ?? 0);
|
|
742
|
+
}
|
|
634
743
|
else {
|
|
635
744
|
const collected = collects(input);
|
|
636
|
-
const spelling = spellingOf(input);
|
|
745
|
+
const spelling = spellingOf(input, invocation.table);
|
|
637
746
|
const raw = input.kind === 'argument'
|
|
638
747
|
? supplied.args.get(input)
|
|
639
748
|
: suppliedOption(supplied.options, input.name, collected);
|
|
@@ -641,34 +750,37 @@ export async function validateValues(invocation) {
|
|
|
641
750
|
if (input.config.required) {
|
|
642
751
|
// An omitted required argument arrives here too, so omission has one class.
|
|
643
752
|
// One aggregated diagnostic covers an omitted argument and an omitted option alike.
|
|
644
|
-
|
|
645
|
-
lines.push(missingMessage(input, spelling, { collected }));
|
|
753
|
+
omit(entry, spelling, missingMessage(input, spelling, { collected }));
|
|
646
754
|
}
|
|
647
755
|
else if (collected && !defaults.has(input)) {
|
|
648
756
|
// No occurrence has no value to validate, so the action receives an empty array.
|
|
649
757
|
values.set(input, []);
|
|
650
758
|
}
|
|
651
|
-
else if (validatesOmission(input)) {
|
|
759
|
+
else if (validatesOmission(input) && sources.unanswered?.has(input) !== true) {
|
|
652
760
|
// The flag sends the omission itself to the validator.
|
|
761
|
+
// An option a skipped configuration source would have filled is not judged absent.
|
|
653
762
|
// An absence rule reads the context a supplied value reads, and reports input issues.
|
|
654
763
|
await accept(entry, undefined, spelling);
|
|
655
764
|
}
|
|
656
765
|
else {
|
|
657
|
-
values.set(input,
|
|
766
|
+
values.set(input, freshDeclaredValue(defaults.get(input)));
|
|
658
767
|
}
|
|
659
768
|
}
|
|
660
769
|
else if (input.config.required && Array.isArray(raw) && raw.length === 0) {
|
|
661
770
|
// A filled list satisfies the at-least-one rule by its length, so an empty one is missing.
|
|
662
|
-
|
|
663
|
-
lines.push(missingMessage(input, spelling, { collected, origin: originOf(input) }));
|
|
771
|
+
omit(entry, spelling, missingMessage(input, spelling, { collected, origin: originOf(input) }));
|
|
664
772
|
}
|
|
665
773
|
else {
|
|
666
774
|
await accept(entry, raw, spelling);
|
|
667
775
|
}
|
|
668
776
|
}
|
|
669
777
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
778
|
+
const found = [...reports.values()];
|
|
779
|
+
return {
|
|
780
|
+
failure: found.length > 0
|
|
781
|
+
? new InputError(found.flatMap((report) => report.lines).join('\n'), found.map((report) => report.problem))
|
|
782
|
+
: undefined,
|
|
783
|
+
reports,
|
|
784
|
+
values: new ValidatedInputs(values),
|
|
785
|
+
};
|
|
674
786
|
}
|