@loomcli/core 0.5.0 → 0.6.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 +18 -2
- package/dist/application.js +266 -71
- package/dist/bindings.d.ts +15 -10
- package/dist/bindings.js +34 -15
- package/dist/chain.d.ts +15 -6
- package/dist/chain.js +40 -20
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +65 -23
- package/dist/command.js +695 -226
- package/dist/controls.d.ts +8 -0
- package/dist/controls.js +23 -0
- package/dist/defect.d.ts +18 -0
- package/dist/defect.js +272 -0
- package/dist/developer.d.ts +24 -0
- package/dist/developer.js +52 -0
- package/dist/diagnostic-text.d.ts +81 -0
- package/dist/diagnostic-text.js +283 -0
- package/dist/diagnostic.d.ts +11 -0
- package/dist/diagnostic.js +70 -0
- package/dist/errors.d.ts +108 -24
- package/dist/errors.js +324 -53
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +41 -8
- package/dist/extension.js +142 -57
- package/dist/facts.d.ts +66 -11
- package/dist/facts.js +98 -22
- package/dist/globals.d.ts +29 -21
- package/dist/globals.js +115 -46
- package/dist/hints.d.ts +79 -0
- package/dist/hints.js +247 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +43 -1
- package/dist/identity.d.ts +19 -0
- package/dist/identity.js +72 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +5 -0
- package/dist/input-rules.d.ts +64 -0
- package/dist/input-rules.js +145 -0
- package/dist/inspect.d.ts +12 -4
- package/dist/inspect.js +88 -28
- package/dist/lanes.js +1 -1
- package/dist/locate.js +4 -4
- package/dist/options.d.ts +23 -2
- package/dist/options.js +135 -44
- package/dist/output.d.ts +9 -2
- package/dist/output.js +18 -2
- package/dist/plain.d.ts +6 -0
- package/dist/plain.js +12 -0
- package/dist/plugin-rules.d.ts +62 -0
- package/dist/plugin-rules.js +155 -0
- package/dist/plugin.d.ts +22 -10
- package/dist/plugin.js +348 -116
- package/dist/prototypes.d.ts +7 -0
- package/dist/prototypes.js +29 -0
- package/dist/rendering.d.ts +6 -1
- package/dist/rendering.js +23 -5
- package/dist/rules.d.ts +51 -0
- package/dist/rules.js +115 -0
- package/dist/sequence.js +6 -1
- package/dist/sources.d.ts +6 -4
- package/dist/sources.js +22 -13
- package/dist/style-wire.js +1 -1
- package/dist/style.js +1 -1
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +25 -5
- package/dist/thenable.d.ts +15 -0
- package/dist/thenable.js +29 -0
- package/dist/translators.d.ts +69 -0
- package/dist/translators.js +253 -0
- package/dist/types.d.ts +11 -1
- package/dist/validation.d.ts +44 -3
- package/dist/validation.js +156 -57
- package/dist/view.d.ts +48 -14
- package/dist/view.js +155 -77
- package/package.json +1 -1
package/dist/validation.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { schemaOptions } from './context.js';
|
|
2
|
-
import {
|
|
2
|
+
import { escapeControlCharacters } from './controls.js';
|
|
3
|
+
import { asSentence, DeclarationError, InputError, quoted, reasonOf } from './errors.js';
|
|
4
|
+
import { callSite, factFault, flagFault, partOf, siteFinding } from './facts.js';
|
|
5
|
+
import { booleanOptionValueRule, defaultShape, invalidDefault, notAValidator, omissionAlreadyDecided, omissionWithoutValidator, requiredWithDefault, } from './input-rules.js';
|
|
3
6
|
import { booleanValue } from './options.js';
|
|
7
|
+
import { isPlainObject } from './plain.js';
|
|
8
|
+
import { notAnObject } from './plugin-rules.js';
|
|
9
|
+
import { validatorFailed } from './rules.js';
|
|
4
10
|
/** Every declaration in validation order: the globals first, then the reading Command's own. */
|
|
5
11
|
function scoped(inputs) {
|
|
6
12
|
return [
|
|
@@ -46,6 +52,27 @@ class ValidatedInputs {
|
|
|
46
52
|
return { [input.name]: this.#values.get(input) };
|
|
47
53
|
}
|
|
48
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The one check every `argument()`, `option()`, and `globalOption()` call makes before it reads its
|
|
57
|
+
* config, so a call in JavaScript that supplies none, or a value of another kind, reports a
|
|
58
|
+
* declaration fault and not a TypeError. `place` is where the call sits.
|
|
59
|
+
*/
|
|
60
|
+
export function checkInputConfig(declared, place) {
|
|
61
|
+
const { config, kind, name } = declared;
|
|
62
|
+
if (isPlainObject(config)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const subject = kind === 'argument' ? 'Argument' : 'Option';
|
|
66
|
+
const site = callSite(`${subject} ${quoted(name)}`, {
|
|
67
|
+
arguments: [name, config],
|
|
68
|
+
...place,
|
|
69
|
+
});
|
|
70
|
+
throw new DeclarationError(notAnObject, {
|
|
71
|
+
correction: `Supply ${kind === 'argument' ? 'an argument' : 'an option'} config object, such as ${kind === 'argument' ? '{}' : "{ type: 'string' }"}.`,
|
|
72
|
+
findings: [siteFinding(site, '1')],
|
|
73
|
+
sentence: `${site.subject} declares a config that is not an object.`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
49
76
|
/**
|
|
50
77
|
* Authoring's snapshot of one config. An array default is the one declared value core hands to an
|
|
51
78
|
* action as its own value, so the declaration keeps a copy and the caller keeps its array. Every
|
|
@@ -59,8 +86,25 @@ export function captureConfig(config) {
|
|
|
59
86
|
* A declaration error names the declaration, because the author reads the declaration to fix it.
|
|
60
87
|
* An argument declares and reads under one name, so the two namings differ for options alone.
|
|
61
88
|
*/
|
|
62
|
-
function
|
|
63
|
-
return input.kind === 'argument'
|
|
89
|
+
export function declarationSubject(input) {
|
|
90
|
+
return input.kind === 'argument'
|
|
91
|
+
? `Argument ${quoted(input.name)}`
|
|
92
|
+
: `Option ${quoted(input.name)}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Where one input was declared: a global option at its `globalOption()` call, and every other input
|
|
96
|
+
* at its own `argument()` or `option()` call on the Command at `path`.
|
|
97
|
+
*/
|
|
98
|
+
export function inputPlace(input, scope) {
|
|
99
|
+
return scope.global ? { call: 'globalOption', path: [] } : { call: input.kind, path: scope.path };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The site of the call that declared one input at `place`, rebuilt as `call(name, config)`. Every
|
|
103
|
+
* finding for an input's own call starts from it, so each marks the call the same way. `subject`
|
|
104
|
+
* is the sentence's name for the input.
|
|
105
|
+
*/
|
|
106
|
+
export function declaringSite(input, place, subject = declarationSubject(input)) {
|
|
107
|
+
return callSite(subject, { arguments: [input.name, input.config], ...place });
|
|
64
108
|
}
|
|
65
109
|
/**
|
|
66
110
|
* The token an operator would type for one declaration: `--file` for an option, `-F` when the
|
|
@@ -143,61 +187,88 @@ function holdsRawDefault(input) {
|
|
|
143
187
|
* rule that already decides absence rejects it, and the flag needs a validator to receive the
|
|
144
188
|
* omission.
|
|
145
189
|
*/
|
|
146
|
-
function checkOmissionValidation(input, subject) {
|
|
190
|
+
function checkOmissionValidation(input, site, subject) {
|
|
147
191
|
const { config } = input;
|
|
192
|
+
const decided = (sentence, correction) => factFault(omissionAlreadyDecided, site, { correction, fact: 'validateOmitted', sentence });
|
|
148
193
|
if (config.required) {
|
|
149
|
-
throw
|
|
194
|
+
throw decided(`${subject} is required and declares validateOmitted.`, 'Remove validateOmitted or make the input optional.');
|
|
150
195
|
}
|
|
151
196
|
if (hasDefault(input)) {
|
|
152
|
-
throw
|
|
197
|
+
throw decided(`${subject} declares a default and validateOmitted.`, 'Remove one; the default already fills an omitted value.');
|
|
153
198
|
}
|
|
154
199
|
if (collects(input)) {
|
|
155
|
-
throw
|
|
200
|
+
throw decided(`${subject} takes several values and declares validateOmitted.`, 'Remove validateOmitted; with no values the action receives an empty array and no validator runs.');
|
|
156
201
|
}
|
|
157
202
|
if (config.validate === undefined) {
|
|
158
|
-
throw
|
|
203
|
+
throw factFault(omissionWithoutValidator, site, {
|
|
204
|
+
correction: 'Add validate or remove validateOmitted.',
|
|
205
|
+
fact: 'validateOmitted',
|
|
206
|
+
sentence: `${subject} declares validateOmitted without a validator.`,
|
|
207
|
+
});
|
|
159
208
|
}
|
|
160
209
|
}
|
|
161
|
-
|
|
210
|
+
/** The value rules a Boolean option may not declare, in the order its diagnostic names them. */
|
|
211
|
+
const valueRules = ['validate', 'default', 'required', 'validateOmitted'];
|
|
212
|
+
/** Whether a `validate` value is a Standard Schema v1 object that core can call. */
|
|
213
|
+
function isStandardSchema(validator) {
|
|
214
|
+
if (validator === null || (typeof validator !== 'object' && typeof validator !== 'function')) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const props = Reflect.get(validator, '~standard');
|
|
218
|
+
return (typeof props === 'object' &&
|
|
219
|
+
props !== null &&
|
|
220
|
+
Reflect.get(props, 'version') === 1 &&
|
|
221
|
+
typeof Reflect.get(props, 'vendor') === 'string' &&
|
|
222
|
+
typeof Reflect.get(props, 'validate') === 'function');
|
|
223
|
+
}
|
|
224
|
+
function checkDeclaration(input, site, subject) {
|
|
162
225
|
const { config } = input;
|
|
163
226
|
if (input.kind === 'option' && input.config.type === 'boolean') {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
227
|
+
const declared = valueRules.find((key) => key in config);
|
|
228
|
+
if (declared !== undefined) {
|
|
229
|
+
throw factFault(booleanOptionValueRule, site, {
|
|
230
|
+
correction: `Remove ${declared}; use polarity to control its absent value.`,
|
|
231
|
+
fact: declared,
|
|
232
|
+
sentence: `${subject} is Boolean and declares ${declared}.`,
|
|
233
|
+
});
|
|
169
234
|
}
|
|
170
235
|
return;
|
|
171
236
|
}
|
|
172
237
|
if (config.required !== undefined && typeof config.required !== 'boolean') {
|
|
173
|
-
throw
|
|
238
|
+
throw flagFault(site, 'required');
|
|
174
239
|
}
|
|
175
240
|
if (input.kind === 'argument' &&
|
|
176
241
|
input.config.variadic !== undefined &&
|
|
177
242
|
typeof input.config.variadic !== 'boolean') {
|
|
178
|
-
throw
|
|
243
|
+
throw flagFault(site, 'variadic');
|
|
179
244
|
}
|
|
180
245
|
// The test reads presence, not truth, so a declared `undefined` is a declaration to reject.
|
|
181
246
|
if ('validateOmitted' in config && typeof config.validateOmitted !== 'boolean') {
|
|
182
|
-
throw
|
|
247
|
+
throw flagFault(site, 'validateOmitted');
|
|
183
248
|
}
|
|
184
249
|
if (config.required && Object.hasOwn(config, 'default')) {
|
|
185
|
-
throw
|
|
250
|
+
throw factFault(requiredWithDefault, site, {
|
|
251
|
+
correction: 'Remove the default or make the input optional.',
|
|
252
|
+
fact: 'default',
|
|
253
|
+
sentence: `${subject} is required and declares a default.`,
|
|
254
|
+
});
|
|
186
255
|
}
|
|
187
256
|
if (validatesOmission(input)) {
|
|
188
|
-
checkOmissionValidation(input, subject);
|
|
257
|
+
checkOmissionValidation(input, site, subject);
|
|
189
258
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
typeof validator['~standard'].vendor !== 'string' ||
|
|
197
|
-
typeof validator['~standard'].validate !== 'function')) {
|
|
198
|
-
throw new DeclarationError(`${subject} validate must be a Standard Schema v1 object. Supply a compatible validator.`);
|
|
259
|
+
if (config.validate !== undefined && !isStandardSchema(config.validate)) {
|
|
260
|
+
throw factFault(notAValidator, site, {
|
|
261
|
+
correction: 'Supply a compatible validator.',
|
|
262
|
+
fact: 'validate',
|
|
263
|
+
sentence: `${subject} validate must be a Standard Schema v1 object.`,
|
|
264
|
+
});
|
|
199
265
|
}
|
|
200
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* One issue a validator returned, checked where it enters. Core keeps every own enumerable field
|
|
269
|
+
* the issue carries and rewrites only `path`, reducing each segment to its key, so a field core
|
|
270
|
+
* never reads, such as a validator's issue code, reaches a failure view unchanged.
|
|
271
|
+
*/
|
|
201
272
|
function readIssue(issue) {
|
|
202
273
|
if (issue === null || typeof issue !== 'object' || !('message' in issue)) {
|
|
203
274
|
throw new Error('The validator returned an invalid Standard Schema issue.');
|
|
@@ -208,7 +279,7 @@ function readIssue(issue) {
|
|
|
208
279
|
}
|
|
209
280
|
const suppliedPath = 'path' in issue ? issue.path : undefined;
|
|
210
281
|
if (suppliedPath === undefined) {
|
|
211
|
-
return { message };
|
|
282
|
+
return { ...issue, message };
|
|
212
283
|
}
|
|
213
284
|
if (!Array.isArray(suppliedPath)) {
|
|
214
285
|
throw new Error('The validator returned an invalid Standard Schema issue path.');
|
|
@@ -220,13 +291,15 @@ function readIssue(issue) {
|
|
|
220
291
|
}
|
|
221
292
|
return key;
|
|
222
293
|
});
|
|
223
|
-
return { message, path };
|
|
294
|
+
return { ...issue, message, path };
|
|
224
295
|
}
|
|
225
296
|
/**
|
|
226
297
|
* A broken validator is a fault in the declaration, whichever value reached it, so its diagnostic
|
|
227
|
-
* names the declaration
|
|
298
|
+
* names the declaration and its finding marks the validator on the call at `place`. Returned issues
|
|
299
|
+
* belong to the value, so the caller names those.
|
|
228
300
|
*/
|
|
229
|
-
async function validate(input, raw,
|
|
301
|
+
async function validate(input, raw, call) {
|
|
302
|
+
const { context, place } = call;
|
|
230
303
|
const validator = input.config.validate;
|
|
231
304
|
if (validator === undefined) {
|
|
232
305
|
return { value: raw };
|
|
@@ -246,8 +319,13 @@ async function validate(input, raw, context) {
|
|
|
246
319
|
return { issues: Array.from(issues, readIssue) };
|
|
247
320
|
}
|
|
248
321
|
catch (error) {
|
|
249
|
-
|
|
250
|
-
|
|
322
|
+
// The reason is the author's detail: a distributed build shows the generic defect message.
|
|
323
|
+
const site = place === undefined ? undefined : declaringSite(input, place);
|
|
324
|
+
throw new DeclarationError(validatorFailed, {
|
|
325
|
+
correction: 'Fix the validator.',
|
|
326
|
+
findings: site === undefined ? [] : [siteFinding(site, partOf(site, 'validate'))],
|
|
327
|
+
sentence: `${declarationSubject(input)} validator failed unexpectedly: ${asSentence(reasonOf(error))}`,
|
|
328
|
+
}, { cause: error });
|
|
251
329
|
}
|
|
252
330
|
}
|
|
253
331
|
/**
|
|
@@ -258,13 +336,13 @@ async function validate(input, raw, context) {
|
|
|
258
336
|
* next call. The host is the one captured object that every call and the action share.
|
|
259
337
|
*/
|
|
260
338
|
async function validateDeclared(input, raw, call) {
|
|
261
|
-
const { context, signal } = call;
|
|
339
|
+
const { context, place, signal } = call;
|
|
262
340
|
if (!collects(input) || input.config.validate === undefined) {
|
|
263
|
-
return validate(input, raw, context());
|
|
341
|
+
return validate(input, raw, { context: context(), place });
|
|
264
342
|
}
|
|
265
343
|
if (!Array.isArray(raw)) {
|
|
266
344
|
// The parser, the input sources, and the declaration rules only ever supply an array here.
|
|
267
|
-
throw new TypeError(`${
|
|
345
|
+
throw new TypeError(`${declarationSubject(input)} reached validation without an array of values.`);
|
|
268
346
|
}
|
|
269
347
|
const outputs = [];
|
|
270
348
|
const issues = [];
|
|
@@ -273,15 +351,15 @@ async function validateDeclared(input, raw, call) {
|
|
|
273
351
|
// A cancelled run starts no further call; the run resolves its cancellation code instead.
|
|
274
352
|
break;
|
|
275
353
|
}
|
|
276
|
-
const result = await validate(input, value, context());
|
|
354
|
+
const result = await validate(input, value, { context: context(), place });
|
|
277
355
|
if (result.issues === undefined) {
|
|
278
356
|
outputs.push(result.value);
|
|
279
357
|
}
|
|
280
358
|
else {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
path: [position, ...(issue.path ?? [])]
|
|
284
|
-
}
|
|
359
|
+
// The issue keeps its own fields, and only its path gains the value's position.
|
|
360
|
+
for (const issue of reported(result.issues)) {
|
|
361
|
+
issues.push({ ...issue, path: [position, ...(issue.path ?? [])] });
|
|
362
|
+
}
|
|
285
363
|
}
|
|
286
364
|
}
|
|
287
365
|
return issues.length === 0 ? { value: outputs } : { issues };
|
|
@@ -304,23 +382,33 @@ export function issuePath(issue) {
|
|
|
304
382
|
*/
|
|
305
383
|
function reported(issues) {
|
|
306
384
|
return issues.length === 0
|
|
307
|
-
? [
|
|
385
|
+
? [
|
|
386
|
+
{
|
|
387
|
+
message: 'The validator rejected this value without an explanation. Supply a different value.',
|
|
388
|
+
},
|
|
389
|
+
]
|
|
308
390
|
: issues;
|
|
309
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* One line per issue under the input's subject. A path segment can hold text the operator typed,
|
|
394
|
+
* such as a key of a record, so the line escapes the path; `issuePath` keeps it raw for a view.
|
|
395
|
+
*/
|
|
310
396
|
function messages(subject, issues) {
|
|
311
397
|
return issues.map((issue) => {
|
|
312
398
|
const path = issuePath(issue);
|
|
313
|
-
|
|
399
|
+
const position = path === undefined ? '' : ` at ${escapeControlCharacters(path)}`;
|
|
400
|
+
return `${subject}${position}: ${issue.message}`;
|
|
314
401
|
});
|
|
315
402
|
}
|
|
316
403
|
/** The fault a default of the wrong raw shape reports, by the shape its declaration expects. */
|
|
317
|
-
function defaultShapeFault(input, subject) {
|
|
404
|
+
function defaultShapeFault(input, site, subject) {
|
|
405
|
+
const fault = (sentence, correction) => factFault(defaultShape, site, { correction, fact: 'default', sentence });
|
|
318
406
|
if (!collects(input)) {
|
|
319
|
-
return `${subject} default must be a string without a validator
|
|
407
|
+
return fault(`${subject} default must be a string without a validator.`, 'Supply a string default.');
|
|
320
408
|
}
|
|
321
409
|
return input.config.validate === undefined
|
|
322
|
-
? `${subject} default must be an array of strings without a validator
|
|
323
|
-
: `${subject} default must be an array
|
|
410
|
+
? fault(`${subject} default must be an array of strings without a validator.`, 'Supply a string array default.')
|
|
411
|
+
: fault(`${subject} default must be an array.`, 'Supply an array of values.');
|
|
324
412
|
}
|
|
325
413
|
/** A declared `default: undefined` is a default, so presence is the key, never the value. */
|
|
326
414
|
function hasDefault(input) {
|
|
@@ -334,31 +422,41 @@ function hasDefault(input) {
|
|
|
334
422
|
* diagnostics read with; every other caller is named by the declaration itself.
|
|
335
423
|
*/
|
|
336
424
|
export function checkDeclarations(inputs, named) {
|
|
337
|
-
for (const input of inputs) {
|
|
338
|
-
checkDeclaration(input, named ??
|
|
425
|
+
for (const { input, site } of inputs) {
|
|
426
|
+
checkDeclaration(input, site, named ?? declarationSubject(input));
|
|
339
427
|
}
|
|
340
|
-
for (const input of inputs.filter((entry) => hasDefault(entry))) {
|
|
428
|
+
for (const { input, site } of inputs.filter((entry) => hasDefault(entry.input))) {
|
|
341
429
|
if (!holdsRawDefault(input)) {
|
|
342
|
-
|
|
343
|
-
throw new DeclarationError(defaultShapeFault(input, subject));
|
|
430
|
+
throw defaultShapeFault(input, site, named ?? declarationSubject(input));
|
|
344
431
|
}
|
|
345
432
|
}
|
|
346
433
|
}
|
|
347
434
|
/**
|
|
348
435
|
* Every declared default, validated before any token is read. The host is captured by then, so a
|
|
349
|
-
* default's validator reads the same Host its action will, under the `default` phase.
|
|
436
|
+
* default's validator reads the same Host its action will, under the `default` phase. `places`
|
|
437
|
+
* says where each declaration sits, which a rejected default's finding rebuilds.
|
|
350
438
|
*/
|
|
351
|
-
export async function prepareInputs(inputs, host) {
|
|
439
|
+
export async function prepareInputs(inputs, host, places) {
|
|
352
440
|
const declarations = scoped(inputs);
|
|
353
441
|
const defaults = new Map();
|
|
354
442
|
for (const entry of declarations.filter(({ input }) => hasDefault(input))) {
|
|
355
443
|
const { input } = entry;
|
|
356
|
-
const subject =
|
|
444
|
+
const subject = declarationSubject(input);
|
|
445
|
+
const place = places.get(input);
|
|
357
446
|
const result = await validateDeclared(input, input.config.default, {
|
|
358
447
|
context: () => ({ host, input: identityOf(entry), phase: 'default' }),
|
|
448
|
+
place,
|
|
359
449
|
});
|
|
360
450
|
if (result.issues !== undefined) {
|
|
361
|
-
|
|
451
|
+
const site = place === undefined ? undefined : declaringSite(input, place);
|
|
452
|
+
throw new DeclarationError(invalidDefault, {
|
|
453
|
+
correction: 'Fix the default or its validator.',
|
|
454
|
+
findings: site === undefined ? [] : [siteFinding(site, partOf(site, 'default'))],
|
|
455
|
+
sentence: [
|
|
456
|
+
`${subject} has an invalid default.`,
|
|
457
|
+
...messages(subject, reported(result.issues)),
|
|
458
|
+
].join('\n'),
|
|
459
|
+
});
|
|
362
460
|
}
|
|
363
461
|
defaults.set(input, result.value);
|
|
364
462
|
}
|
|
@@ -461,6 +559,7 @@ export async function validateValues(invocation) {
|
|
|
461
559
|
const accept = async (entry, raw, spelling) => {
|
|
462
560
|
const result = await validateDeclared(entry.input, raw, {
|
|
463
561
|
context: () => contextOf(entry),
|
|
562
|
+
place: inputPlace(entry.input, { global: entry.global, path: invocation.command }),
|
|
464
563
|
signal: invocation.signal,
|
|
465
564
|
});
|
|
466
565
|
if (result.issues === undefined) {
|
package/dist/view.d.ts
CHANGED
|
@@ -39,11 +39,32 @@ type AnyDeclaredView = (View<never> | RowView<never>) & DeclaredViewBrand & {
|
|
|
39
39
|
/** A failure class as an override key, so `UsageError` and an application's subclass both fit. */
|
|
40
40
|
type FailureClass<Failure extends LoomError> = abstract new (...args: never[]) => Failure;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* What every failure view reads: the stderr view context, where the run was, and the hints the
|
|
43
|
+
* installed plugins' `onFailure` hooks returned. `run()` fills it where it catches the failure, so
|
|
44
|
+
* no failure class carries these facts. `path` holds the canonical names routing walked, `[]`
|
|
45
|
+
* before routing, and `hints` is `[]` when no hook contributed.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
interface FailureViewContext extends ViewContext {
|
|
48
|
+
readonly application: string;
|
|
49
|
+
readonly path: readonly string[];
|
|
50
|
+
readonly hints: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A failure class's view. A `View<Failure>` written against `ViewContext` is assignable to it,
|
|
54
|
+
* because its function reads less of the context.
|
|
55
|
+
*/
|
|
56
|
+
interface FailureView<Failure extends LoomError> {
|
|
57
|
+
render: (failure: Readonly<Failure>, context: FailureViewContext) => string;
|
|
58
|
+
/** A failure view has one shape, as a view does. */
|
|
59
|
+
row?: never;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One stored view function, with the data type and the context erased. A registry holds one entry
|
|
63
|
+
* per key and cannot carry a type parameter per entry, so `override(key, replacement)` is where the
|
|
64
|
+
* replacement is typed against the key it answers: a failure class's view reads the failure view
|
|
65
|
+
* context, and every other view reads the view context.
|
|
66
|
+
*/
|
|
67
|
+
type ViewFunction = (data: never, context: never) => unknown;
|
|
47
68
|
/** One stored row function, with the row type erased for the same reason. */
|
|
48
69
|
type RowFunction = (row: never, index: number, context: ViewContext) => unknown;
|
|
49
70
|
/** One stored function that opens a sequence and reads the context alone. */
|
|
@@ -114,7 +135,7 @@ declare function override<Data>(key: DeclaredView<Data>, replacement: NoInfer<Vi
|
|
|
114
135
|
declare function override<Row>(key: DeclaredRowView<Row>, replacement: NoInfer<RowView<Row>>): ViewOverride;
|
|
115
136
|
declare function override<Failure extends LoomError>(key: FailureClass<Failure> & {
|
|
116
137
|
readonly [declaredView]?: never;
|
|
117
|
-
}, replacement: NoInfer<
|
|
138
|
+
}, replacement: NoInfer<FailureView<Failure>>): ViewOverride;
|
|
118
139
|
/** One contributor's overrides, read once per build and consulted in contributor order. */
|
|
119
140
|
interface ViewContributions {
|
|
120
141
|
failures: Map<unknown, StoredView>;
|
|
@@ -127,13 +148,20 @@ interface ViewContributions {
|
|
|
127
148
|
type ViewRegistry = readonly ViewContributions[];
|
|
128
149
|
/** The identities one build has met, so a second object under one identity is visible. */
|
|
129
150
|
type ViewIdentities = Map<string, AnyDeclaredView>;
|
|
130
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* How one contributor's diagnostics name it, whether its list may declare a view, and the call
|
|
153
|
+
* that declared the list, `plugin(identity, …)` or `new Application(name, …)`, which a fault marks.
|
|
154
|
+
*/
|
|
131
155
|
interface ViewSubject {
|
|
132
156
|
/** A plugin declares views beside its overrides; an application overrides alone. */
|
|
133
157
|
declares: boolean;
|
|
134
158
|
sentence: string;
|
|
159
|
+
owner: {
|
|
160
|
+
call: string;
|
|
161
|
+
named: unknown;
|
|
162
|
+
};
|
|
135
163
|
}
|
|
136
|
-
/** The identity
|
|
164
|
+
/** The identity one build starts from, holding the views core itself declares. */
|
|
137
165
|
declare function viewIdentities(declared: readonly AnyDeclaredView[]): ViewIdentities;
|
|
138
166
|
/**
|
|
139
167
|
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
@@ -158,23 +186,29 @@ interface ResolvedRowView<Row> {
|
|
|
158
186
|
*/
|
|
159
187
|
declare function resolveRowView<Row>(registry: ViewRegistry, value: RowView<Row>): ResolvedRowView<Row>;
|
|
160
188
|
/**
|
|
161
|
-
* The report of one failure: the text core writes, and whether a view produced it.
|
|
162
|
-
* report
|
|
163
|
-
*
|
|
189
|
+
* The report of one failure: the text core writes, and whether a view produced it. A rendered
|
|
190
|
+
* report says whether core's own default text answered, which for a defect is the generic defect
|
|
191
|
+
* message. An unrendered report carries core's own text, which the plain fallback path writes
|
|
192
|
+
* beside the diagnostic naming the view that could not answer, and what the view threw.
|
|
164
193
|
*/
|
|
165
194
|
type FailureReport = {
|
|
166
195
|
kind: 'rendered';
|
|
167
196
|
text: string;
|
|
197
|
+
core: boolean;
|
|
168
198
|
} | {
|
|
169
199
|
kind: 'unrendered';
|
|
170
200
|
text: string;
|
|
171
201
|
reason: string;
|
|
202
|
+
cause: unknown;
|
|
172
203
|
};
|
|
173
204
|
/**
|
|
174
205
|
* The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
|
|
175
|
-
* it and falls to core's own default text, which
|
|
176
|
-
* `FatalError` keeps the authored
|
|
206
|
+
* it and falls to core's own default text, which opens a usage failure with the context's
|
|
207
|
+
* application name and escapes the raw facts it interpolates. A `FatalError` keeps the authored
|
|
208
|
+
* marked message it was given. The default text writes each hint on its own line under the
|
|
209
|
+
* sentence, as marked text it does not escape; an override decides for itself. An unrendered report
|
|
210
|
+
* carries the same default text without hints, which the plain fallback path writes.
|
|
177
211
|
*/
|
|
178
|
-
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context
|
|
179
|
-
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, ResolvedRowView, ViewContribution, ViewContributions, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
212
|
+
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context: FailureViewContext): FailureReport;
|
|
213
|
+
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, FailureView, FailureViewContext, ResolvedRowView, ViewContribution, ViewContributions, ViewSubject, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
180
214
|
export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
|