@loomcli/core 0.4.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/LICENSE +21 -0
- package/dist/application.d.ts +60 -31
- package/dist/application.js +356 -149
- package/dist/bindings.d.ts +31 -0
- package/dist/bindings.js +64 -0
- package/dist/chain.d.ts +26 -12
- package/dist/chain.js +59 -92
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +212 -93
- package/dist/command.js +1224 -454
- 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 +115 -26
- package/dist/errors.js +333 -54
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +44 -9
- package/dist/extension.js +147 -65
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +108 -25
- package/dist/globals.d.ts +63 -22
- package/dist/globals.js +164 -39
- 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 +15 -4
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +64 -0
- package/dist/input-rules.js +145 -0
- package/dist/inspect.d.ts +36 -5
- package/dist/inspect.js +125 -27
- package/dist/lanes.js +1 -1
- package/dist/locate.d.ts +41 -0
- package/dist/locate.js +121 -0
- package/dist/options.d.ts +96 -2
- package/dist/options.js +259 -71
- package/dist/output.d.ts +11 -2
- package/dist/output.js +23 -3
- 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 +121 -55
- package/dist/plugin.js +496 -126
- 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 +58 -0
- package/dist/sources.js +258 -0
- 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 +76 -21
- package/dist/validation.d.ts +65 -10
- package/dist/validation.js +314 -108
- package/dist/view.d.ts +49 -15
- package/dist/view.js +157 -79
- package/package.json +3 -2
package/dist/extension.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { Finding } from './diagnostic-text.js';
|
|
3
|
+
import type { FactSite } from './facts.js';
|
|
2
4
|
import type { ArgumentNode, CommandNode, OptionNode } from './inspect.js';
|
|
3
5
|
import type { AttachedCommand } from './types.js';
|
|
4
6
|
/** The three declaration kinds an extension can name, each with its own node in the graph. */
|
|
@@ -15,8 +17,14 @@ interface AnyExtension {
|
|
|
15
17
|
readonly target: ExtensionTarget;
|
|
16
18
|
readonly collect: boolean;
|
|
17
19
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* What a value must show to be taken for a descriptor before admission checks its identity and its
|
|
22
|
+
* `collect` flag. The identity stays unread here, so admission reads it once.
|
|
23
|
+
*/
|
|
24
|
+
interface DescriptorShape {
|
|
25
|
+
readonly identity: unknown;
|
|
26
|
+
readonly target: ExtensionTarget;
|
|
27
|
+
}
|
|
20
28
|
/** One carried value: the input its author supplied and the descriptor that produced it. */
|
|
21
29
|
interface CarriedValue {
|
|
22
30
|
descriptor: AnyExtension;
|
|
@@ -83,6 +91,8 @@ type ExtensionRead<Schema extends StandardSchemaV1, Collect extends boolean> = C
|
|
|
83
91
|
* collection order, and an empty list where the node carries none.
|
|
84
92
|
*/
|
|
85
93
|
declare function readExtension<Target extends ExtensionTarget, Schema extends StandardSchemaV1, Collect extends boolean = false>(node: NodeFor<Target>, descriptor: Extension<Target, Schema, Collect>): ExtensionRead<Schema, Collect>;
|
|
94
|
+
/** How a diagnostic names the declarations one target covers, such as "Commands". */
|
|
95
|
+
declare function appliesTo(target: ExtensionTarget): string;
|
|
86
96
|
/** The declaration one `extensions` slot belongs to, as its own diagnostics name it. */
|
|
87
97
|
interface ExtensionSubject {
|
|
88
98
|
/** The subject after a preposition, such as `on Command "get"`. */
|
|
@@ -90,8 +100,15 @@ interface ExtensionSubject {
|
|
|
90
100
|
/** The subject at the start of a sentence, such as `Command "get"`. */
|
|
91
101
|
sentence: string;
|
|
92
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* A descriptor admission accepted: its shape and a Boolean `collect` flag. Its identity is the key
|
|
105
|
+
* a registry holds it under, so nothing reads it from the descriptor again.
|
|
106
|
+
*/
|
|
107
|
+
type AdmittedDescriptor = DescriptorShape & {
|
|
108
|
+
readonly collect: boolean;
|
|
109
|
+
};
|
|
93
110
|
/** Every descriptor one build has met, so a second descriptor under one identity is visible. */
|
|
94
|
-
type DescriptorRegistry = Map<string,
|
|
111
|
+
type DescriptorRegistry = Map<string, AdmittedDescriptor>;
|
|
95
112
|
/**
|
|
96
113
|
* The record each declaration published during one build, keyed by the declaration itself. The
|
|
97
114
|
* records live here rather than on the declaration, because one build's outputs belong to that
|
|
@@ -100,15 +117,33 @@ type DescriptorRegistry = Map<string, AnyExtension>;
|
|
|
100
117
|
type ExtensionRecords = Map<object, Readonly<Record<string, unknown>>>;
|
|
101
118
|
/**
|
|
102
119
|
* Whether a value has a descriptor's shape: a callable object carrying an identity and a declared
|
|
103
|
-
* target.
|
|
120
|
+
* target. Admission checks its identity and its `collect` flag where a build registers it.
|
|
104
121
|
*/
|
|
105
122
|
declare function isDescriptor(value: unknown): value is DescriptorShape;
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
/**
|
|
124
|
+
* How one descriptor is admitted: the declaration that carries it, the plugin that holds it in a
|
|
125
|
+
* sentence, and its identity when a registry already read it once.
|
|
126
|
+
*/
|
|
127
|
+
interface Admission {
|
|
128
|
+
place?: Finding | undefined;
|
|
129
|
+
holder?: string | undefined;
|
|
130
|
+
identity?: string | undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Admits one descriptor and records it under the identity admission read, so a later descriptor of
|
|
134
|
+
* that identity is compared to it.
|
|
135
|
+
*/
|
|
136
|
+
declare function registerDescriptor(descriptors: DescriptorRegistry, descriptor: DescriptorShape, admission?: Admission): void;
|
|
137
|
+
/**
|
|
138
|
+
* Where one `extensions` list sits: the call that declared it, and the dotted path among that
|
|
139
|
+
* call's arguments to the list, which is empty for `extend()`, whose arguments are the list.
|
|
140
|
+
*/
|
|
141
|
+
type ExtensionSite = Pick<FactSite, 'at' | 'declaration'>;
|
|
142
|
+
/** Everything one `extensions` slot needs to answer: whose it is, where, and what it may carry. */
|
|
109
143
|
interface ExtensionSlot {
|
|
110
144
|
declared: unknown;
|
|
111
145
|
descriptors: DescriptorRegistry;
|
|
146
|
+
site: ExtensionSite;
|
|
112
147
|
subject: ExtensionSubject;
|
|
113
148
|
target: ExtensionTarget;
|
|
114
149
|
}
|
|
@@ -151,5 +186,5 @@ declare function buildExtensions(slot: ExtensionSlot): Readonly<Record<string, u
|
|
|
151
186
|
declare function storeCommandLayers(slot: Omit<ExtensionSlot, 'declared' | 'target'> & {
|
|
152
187
|
layers: readonly unknown[];
|
|
153
188
|
}): ExtensionStore;
|
|
154
|
-
export type { AnyExtension, DeepReadonly, DescriptorRegistry, Extension, ExtensionRecords, ExtensionStore, ExtensionSubject, ExtensionTarget, ExtensionValue, };
|
|
155
|
-
export { buildExtensions, extendStore, extension, isDescriptor, publishStore, readExtension, registerDescriptor, storeCommandLayers, validateLayer, };
|
|
189
|
+
export type { AdmittedDescriptor, AnyExtension, DeepReadonly, DescriptorRegistry, Extension, ExtensionRecords, ExtensionSite, ExtensionStore, ExtensionSubject, ExtensionTarget, ExtensionValue, };
|
|
190
|
+
export { appliesTo, buildExtensions, extendStore, extension, isDescriptor, publishStore, readExtension, registerDescriptor, storeCommandLayers, validateLayer, };
|
package/dist/extension.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { escapeControlCharacters } from './controls.js';
|
|
2
|
+
import { asSentence, DeclarationError, quoted, reasonOf } from './errors.js';
|
|
3
|
+
import { partOf } from './facts.js';
|
|
4
|
+
import { checkIdentity, identityFault, isIdentity } from './identity.js';
|
|
5
|
+
import { flagNotBoolean } from './input-rules.js';
|
|
6
|
+
import { isPlainObject } from './plain.js';
|
|
7
|
+
import { asyncExtensionSchema, extensionOutput, extensionTarget as extensionTargetRule, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, notAList, twoPackageCopies, } from './plugin-rules.js';
|
|
8
|
+
import { isThenable } from './thenable.js';
|
|
9
|
+
/** The fix every fault from two copies of one package shares. */
|
|
10
|
+
const copiesCorrection = 'Install one copy of the package that defines it.';
|
|
3
11
|
/** Authored values register here, so the public brand publishes no state to reach or replace. */
|
|
4
12
|
const values = new WeakMap();
|
|
5
13
|
/**
|
|
@@ -20,6 +28,7 @@ class ExtensionCarrier {
|
|
|
20
28
|
}
|
|
21
29
|
}
|
|
22
30
|
function extension(identity, config) {
|
|
31
|
+
checkIdentity('extension', identity);
|
|
23
32
|
// `Object.assign` returns the same function object, so the value carries the descriptor itself.
|
|
24
33
|
function create(input) {
|
|
25
34
|
return new ExtensionCarrier({ descriptor, input });
|
|
@@ -44,14 +53,19 @@ const noValues = Object.freeze([]);
|
|
|
44
53
|
* collection order, and an empty list where the node carries none.
|
|
45
54
|
*/
|
|
46
55
|
function readExtension(node, descriptor) {
|
|
56
|
+
const { identity } = descriptor;
|
|
47
57
|
const record = node.extensions;
|
|
48
|
-
const owner = owners.get(record)?.get(
|
|
58
|
+
const owner = owners.get(record)?.get(identity);
|
|
49
59
|
if (owner !== undefined && owner !== descriptor) {
|
|
50
|
-
|
|
60
|
+
// A read happens inside a plugin's own code, so no declaration call stands for it.
|
|
61
|
+
throw new DeclarationError(twoPackageCopies, {
|
|
62
|
+
correction: copiesCorrection,
|
|
63
|
+
sentence: `Extension ${quoted(identity)} was read through a descriptor that did not define the stored value.`,
|
|
64
|
+
});
|
|
51
65
|
}
|
|
52
66
|
// A collecting extension a declaration carries no value of reads as an empty list.
|
|
53
67
|
const absent = descriptor.collect ? noValues : undefined;
|
|
54
|
-
const stored = owner === undefined ? absent : record[
|
|
68
|
+
const stored = owner === undefined ? absent : record[identity];
|
|
55
69
|
// Last resort: no typed path exists.
|
|
56
70
|
// The graph stores every output under a string identity, so the record reads back as `unknown`.
|
|
57
71
|
// No key relates one entry to a descriptor's schema or to its runtime `collect` flag.
|
|
@@ -67,6 +81,10 @@ const applies = {
|
|
|
67
81
|
command: 'Commands',
|
|
68
82
|
option: 'options',
|
|
69
83
|
};
|
|
84
|
+
/** How a diagnostic names the declarations one target covers, such as "Commands". */
|
|
85
|
+
function appliesTo(target) {
|
|
86
|
+
return applies[target];
|
|
87
|
+
}
|
|
70
88
|
/**
|
|
71
89
|
* The children one container contributes, or nothing when its own shape is not plain data. An
|
|
72
90
|
* accessor, a symbol key, and a non-enumerable own property are not plain data, and an `undefined`
|
|
@@ -178,13 +196,12 @@ function plainData(value) {
|
|
|
178
196
|
}
|
|
179
197
|
/**
|
|
180
198
|
* Whether a value has a descriptor's shape: a callable object carrying an identity and a declared
|
|
181
|
-
* target.
|
|
199
|
+
* target. Admission checks its identity and its `collect` flag where a build registers it.
|
|
182
200
|
*/
|
|
183
201
|
function isDescriptor(value) {
|
|
184
202
|
return (value !== null &&
|
|
185
203
|
(typeof value === 'object' || typeof value === 'function') &&
|
|
186
204
|
'identity' in value &&
|
|
187
|
-
typeof value.identity === 'string' &&
|
|
188
205
|
'target' in value &&
|
|
189
206
|
(value.target === 'argument' || value.target === 'command' || value.target === 'option'));
|
|
190
207
|
}
|
|
@@ -196,29 +213,53 @@ function hasCollectFlag(descriptor) {
|
|
|
196
213
|
function collects(descriptor) {
|
|
197
214
|
return descriptor.collect;
|
|
198
215
|
}
|
|
216
|
+
/** The findings for the declaration one fault marks, when a declaration stands for it. */
|
|
217
|
+
function marking(place, note) {
|
|
218
|
+
return place === undefined ? [] : [{ ...place, note }];
|
|
219
|
+
}
|
|
199
220
|
/**
|
|
200
|
-
* The descriptor a registry holds for one identity once this one is admitted
|
|
201
|
-
* one descriptor, wherever on the graph that descriptor appears, and a
|
|
202
|
-
* a build first meets it: its
|
|
203
|
-
*
|
|
221
|
+
* The descriptor a registry holds for one identity once this one is admitted, and that identity.
|
|
222
|
+
* One identity means one descriptor, wherever on the graph that descriptor appears, and a
|
|
223
|
+
* descriptor is checked when a build first meets it: its identity follows the identity grammar and
|
|
224
|
+
* its `collect` flag is `true` or `false`, which the factory always ensures and a hand-built
|
|
225
|
+
* descriptor may not. The identity is read once, so a getter cannot answer the check with one value
|
|
226
|
+
* and the registry with another. It reads the registry and changes nothing.
|
|
204
227
|
*/
|
|
205
|
-
function admitDescriptor(descriptors, descriptor) {
|
|
206
|
-
const
|
|
228
|
+
function admitDescriptor(descriptors, descriptor, { holder, place, ...admission }) {
|
|
229
|
+
const identity = admission.identity ?? descriptor.identity;
|
|
230
|
+
if (!isIdentity(identity)) {
|
|
231
|
+
const subject = holder === undefined
|
|
232
|
+
? 'An extension declares the identity'
|
|
233
|
+
: `${holder} holds the extension identity`;
|
|
234
|
+
throw identityFault(subject, identity, place === undefined ? [] : [place]);
|
|
235
|
+
}
|
|
236
|
+
const known = descriptors.get(identity);
|
|
207
237
|
if (known === undefined) {
|
|
208
238
|
if (!hasCollectFlag(descriptor)) {
|
|
209
|
-
throw new DeclarationError(
|
|
239
|
+
throw new DeclarationError(flagNotBoolean, {
|
|
240
|
+
correction: 'Use true or false.',
|
|
241
|
+
findings: marking(place, `extension ${quoted(identity)}`),
|
|
242
|
+
sentence: `Extension ${quoted(identity)} declares collect that is not a Boolean.`,
|
|
243
|
+
});
|
|
210
244
|
}
|
|
211
|
-
return descriptor;
|
|
245
|
+
return { descriptor, identity };
|
|
212
246
|
}
|
|
213
247
|
if (known !== descriptor) {
|
|
214
|
-
throw new DeclarationError(
|
|
248
|
+
throw new DeclarationError(twoPackageCopies, {
|
|
249
|
+
correction: copiesCorrection,
|
|
250
|
+
findings: marking(place, `another ${quoted(identity)}`),
|
|
251
|
+
sentence: `Extension ${quoted(identity)} is defined twice.`,
|
|
252
|
+
});
|
|
215
253
|
}
|
|
216
|
-
return known;
|
|
254
|
+
return { descriptor: known, identity };
|
|
217
255
|
}
|
|
218
|
-
/**
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Admits one descriptor and records it under the identity admission read, so a later descriptor of
|
|
258
|
+
* that identity is compared to it.
|
|
259
|
+
*/
|
|
260
|
+
function registerDescriptor(descriptors, descriptor, admission = {}) {
|
|
261
|
+
const admitted = admitDescriptor(descriptors, descriptor, admission);
|
|
262
|
+
descriptors.set(admitted.identity, admitted.descriptor);
|
|
222
263
|
}
|
|
223
264
|
/** Whether a value answers the Standard Schema v1 contract this build calls synchronously. */
|
|
224
265
|
function isSchema(value) {
|
|
@@ -231,108 +272,149 @@ function isSchema(value) {
|
|
|
231
272
|
'validate' in standard &&
|
|
232
273
|
typeof standard.validate === 'function');
|
|
233
274
|
}
|
|
234
|
-
/**
|
|
235
|
-
* One schema message as a sentence of its own. A schema author writes the message with or without a
|
|
236
|
-
* full stop, so the diagnostic supplies one only where the message carries none.
|
|
237
|
-
*/
|
|
238
|
-
function sentence(text) {
|
|
239
|
-
return text.endsWith('.') ? text : `${text}.`;
|
|
240
|
-
}
|
|
241
|
-
/** The message one rejected value reports, with the placeholder a silent schema earns. */
|
|
275
|
+
/** The message one rejected value reports, escaped, with the placeholder a silent schema earns. */
|
|
242
276
|
function issueText(issues) {
|
|
243
277
|
const first = Array.isArray(issues) ? issues[0] : undefined;
|
|
244
278
|
if (first !== null && typeof first === 'object' && 'message' in first) {
|
|
245
279
|
const { message } = first;
|
|
246
280
|
if (typeof message === 'string') {
|
|
247
|
-
return message;
|
|
281
|
+
return escapeControlCharacters(message);
|
|
248
282
|
}
|
|
249
283
|
}
|
|
250
284
|
// The sentence the caller composes ends the diagnostic, so this text carries no full stop.
|
|
251
285
|
return 'The schema rejected this value without an explanation';
|
|
252
286
|
}
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
function isThenable(value) {
|
|
258
|
-
return 'then' in value && typeof value.then === 'function';
|
|
287
|
+
/** One entry of a list a fault marks: the site's call, with the mark on the entry. */
|
|
288
|
+
function entryFinding(site, index, note) {
|
|
289
|
+
const finding = { ...site.declaration, mark: partOf(site, index) };
|
|
290
|
+
return note === undefined ? finding : { ...finding, note };
|
|
259
291
|
}
|
|
260
292
|
/** The schema one descriptor answers with, which a JavaScript author can leave out. */
|
|
261
|
-
function schemaOf(subject,
|
|
293
|
+
function schemaOf(subject, { carried, place }) {
|
|
294
|
+
const { descriptor } = carried;
|
|
262
295
|
const schema = 'schema' in descriptor ? descriptor.schema : undefined;
|
|
263
296
|
if (!isSchema(schema)) {
|
|
264
|
-
throw new DeclarationError(
|
|
297
|
+
throw new DeclarationError(extensionWithoutSchema, {
|
|
298
|
+
correction: 'Supply a Standard Schema v1 object that answers synchronously.',
|
|
299
|
+
findings: [place],
|
|
300
|
+
sentence: `${subject.sentence} holds extension ${quoted(descriptor.identity)}, which declares no schema.`,
|
|
301
|
+
});
|
|
265
302
|
}
|
|
266
303
|
return schema;
|
|
267
304
|
}
|
|
305
|
+
/** The fix every rejected extension value shares. */
|
|
306
|
+
const correctValue = 'Correct the value.';
|
|
268
307
|
/** The result one schema answered with, or the rejection its own throw is. */
|
|
269
|
-
function validated(subject,
|
|
308
|
+
function validated(subject, entry, schema) {
|
|
309
|
+
const { carried, place } = entry;
|
|
270
310
|
try {
|
|
271
311
|
return schema['~standard'].validate(carried.input);
|
|
272
312
|
}
|
|
273
313
|
catch (error) {
|
|
274
314
|
// A schema that throws rejected the value the only way it could, so it reads as a rejection.
|
|
275
|
-
throw new DeclarationError(
|
|
315
|
+
throw new DeclarationError(invalidExtensionValue, {
|
|
316
|
+
correction: correctValue,
|
|
317
|
+
findings: [place],
|
|
318
|
+
sentence: `${subject.sentence} holds an invalid ${quoted(carried.descriptor.identity)} value: ${asSentence(reasonOf(error))}`,
|
|
319
|
+
}, { cause: error });
|
|
276
320
|
}
|
|
277
321
|
}
|
|
278
322
|
/** Validates one carried value and answers the plain-data output the node stores under it. */
|
|
279
|
-
function validateValue(subject,
|
|
323
|
+
function validateValue(subject, entry) {
|
|
324
|
+
const { carried, place } = entry;
|
|
280
325
|
const { identity } = carried.descriptor;
|
|
281
|
-
const result = validated(subject,
|
|
326
|
+
const result = validated(subject, entry, schemaOf(subject, entry));
|
|
282
327
|
if (result === null || typeof result !== 'object' || isThenable(result)) {
|
|
283
|
-
throw new DeclarationError(
|
|
328
|
+
throw new DeclarationError(asyncExtensionSchema, {
|
|
329
|
+
correction: 'Supply a schema that answers synchronously.',
|
|
330
|
+
findings: [place],
|
|
331
|
+
sentence: `Extension ${quoted(identity)} validates asynchronously.`,
|
|
332
|
+
});
|
|
284
333
|
}
|
|
285
334
|
const issues = 'issues' in result ? result.issues : undefined;
|
|
286
335
|
if (issues !== undefined) {
|
|
287
|
-
throw new DeclarationError(
|
|
336
|
+
throw new DeclarationError(invalidExtensionValue, {
|
|
337
|
+
correction: correctValue,
|
|
338
|
+
findings: [place],
|
|
339
|
+
sentence: `${subject.sentence} holds an invalid ${quoted(identity)} value: ${asSentence(issueText(issues))}`,
|
|
340
|
+
});
|
|
288
341
|
}
|
|
289
342
|
const output = plainData('value' in result ? result.value : undefined);
|
|
290
343
|
if (!output) {
|
|
291
|
-
throw new DeclarationError(
|
|
344
|
+
throw new DeclarationError(extensionOutput, {
|
|
345
|
+
correction: 'Return strings, numbers, booleans, null, arrays, and plain objects.',
|
|
346
|
+
findings: [place],
|
|
347
|
+
sentence: `Extension ${quoted(identity)} produced a value that is not plain data ${subject.phrase}.`,
|
|
348
|
+
});
|
|
292
349
|
}
|
|
293
350
|
return output.data;
|
|
294
351
|
}
|
|
295
352
|
/** An `extensions` slot holds a list of values, so anything else is the same declaration fault. */
|
|
296
|
-
function readList(
|
|
353
|
+
function readList(slot) {
|
|
354
|
+
const { declared, site, subject } = slot;
|
|
297
355
|
if (declared === undefined) {
|
|
298
356
|
return [];
|
|
299
357
|
}
|
|
300
358
|
if (!Array.isArray(declared)) {
|
|
301
|
-
throw new DeclarationError(
|
|
359
|
+
throw new DeclarationError(notAList, {
|
|
360
|
+
correction: 'Supply a list of values returned by calling an extension.',
|
|
361
|
+
findings: [{ ...site.declaration, mark: site.at }],
|
|
362
|
+
sentence: `${subject.sentence} declares extensions that are not an array.`,
|
|
363
|
+
});
|
|
302
364
|
}
|
|
303
365
|
return declared;
|
|
304
366
|
}
|
|
305
367
|
/** The value one entry carries, under the rules its own slot's target sets. */
|
|
306
|
-
function carriedValue(
|
|
368
|
+
function carriedValue(slot, entry, index) {
|
|
369
|
+
const { site, subject, target } = slot;
|
|
307
370
|
const carried = typeof entry === 'object' && entry !== null ? values.get(entry) : undefined;
|
|
308
371
|
if (!carried) {
|
|
309
|
-
throw new DeclarationError(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
372
|
+
throw new DeclarationError(foreignValue, {
|
|
373
|
+
correction: 'Supply the value returned by calling an extension.',
|
|
374
|
+
findings: [entryFinding(site, index)],
|
|
375
|
+
sentence: `${subject.sentence} holds a value that is not an extension value.`,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const { descriptor } = carried;
|
|
379
|
+
const place = entryFinding(site, index, `extension ${quoted(descriptor.identity)}`);
|
|
380
|
+
if (descriptor.target !== target) {
|
|
381
|
+
throw new DeclarationError(extensionTargetRule, {
|
|
382
|
+
correction: `Supply an extension that applies to ${applies[target]}.`,
|
|
383
|
+
findings: [place],
|
|
384
|
+
sentence: `${subject.sentence} holds extension ${quoted(descriptor.identity)}, which applies to ${applies[descriptor.target]}.`,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return { carried, place };
|
|
315
388
|
}
|
|
316
389
|
/**
|
|
317
390
|
* One layer's values, each validated once, synchronously, in authoring order. A layer holds at
|
|
318
391
|
* most one value of an extension, collecting or not, so a second one is a declaration fault.
|
|
319
392
|
*/
|
|
320
393
|
function validateLayer(slot) {
|
|
321
|
-
const { subject } = slot;
|
|
394
|
+
const { site, subject } = slot;
|
|
322
395
|
const layer = [];
|
|
323
|
-
|
|
396
|
+
// Each identity's position in the list, so a repeat marks both values.
|
|
397
|
+
const seen = new Map();
|
|
324
398
|
// The layer's descriptors register together once every value is valid.
|
|
325
399
|
// A rejected layer, such as a hook's `extend()` call the hook catches, leaves the registry as it was.
|
|
326
400
|
const staged = new Map(slot.descriptors);
|
|
327
|
-
for (const
|
|
328
|
-
const
|
|
329
|
-
const { descriptor } = carried;
|
|
330
|
-
registerDescriptor(staged, descriptor);
|
|
331
|
-
|
|
332
|
-
|
|
401
|
+
for (const [index, value] of readList(slot).entries()) {
|
|
402
|
+
const entry = carriedValue(slot, value, index);
|
|
403
|
+
const { descriptor } = entry.carried;
|
|
404
|
+
registerDescriptor(staged, descriptor, { place: entryFinding(site, index) });
|
|
405
|
+
const first = seen.get(descriptor.identity);
|
|
406
|
+
if (first !== undefined) {
|
|
407
|
+
throw new DeclarationError(extensionValueTwice, {
|
|
408
|
+
correction: 'Supply one value.',
|
|
409
|
+
findings: [
|
|
410
|
+
entryFinding(site, first, 'the first value'),
|
|
411
|
+
entryFinding(site, index, 'the second value'),
|
|
412
|
+
],
|
|
413
|
+
sentence: `${subject.sentence} holds extension ${quoted(descriptor.identity)} twice.`,
|
|
414
|
+
});
|
|
333
415
|
}
|
|
334
|
-
seen.
|
|
335
|
-
layer.push({ descriptor, output: validateValue(subject,
|
|
416
|
+
seen.set(descriptor.identity, index);
|
|
417
|
+
layer.push({ descriptor, output: validateValue(subject, entry) });
|
|
336
418
|
}
|
|
337
419
|
for (const [identity, descriptor] of staged) {
|
|
338
420
|
slot.descriptors.set(identity, descriptor);
|
|
@@ -395,4 +477,4 @@ function storeCommandLayers(slot) {
|
|
|
395
477
|
}
|
|
396
478
|
return store;
|
|
397
479
|
}
|
|
398
|
-
export { buildExtensions, extendStore, extension, isDescriptor, publishStore, readExtension, registerDescriptor, storeCommandLayers, validateLayer, };
|
|
480
|
+
export { appliesTo, buildExtensions, extendStore, extension, isDescriptor, publishStore, readExtension, registerDescriptor, storeCommandLayers, validateLayer, };
|
package/dist/facts.d.ts
CHANGED
|
@@ -1,39 +1,99 @@
|
|
|
1
|
+
import type { DiagnosticRule, Finding } from './diagnostic-text.js';
|
|
2
|
+
import { DeclarationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Where one fact was declared: the subject its sentence names, the call that declared it, and the
|
|
5
|
+
* dotted path among that call's arguments to the object that holds the fact, which a finding marks.
|
|
6
|
+
*/
|
|
7
|
+
export interface FactSite {
|
|
8
|
+
readonly subject: string;
|
|
9
|
+
readonly declaration: Omit<Finding, 'mark' | 'note'>;
|
|
10
|
+
readonly at: string;
|
|
11
|
+
}
|
|
12
|
+
/** The finding for the call one site holds, marking one part of it, with a note when given. */
|
|
13
|
+
export declare function siteFinding(site: FactSite, mark: string, note?: string): Finding;
|
|
14
|
+
/**
|
|
15
|
+
* Where one key of a declaration's options object sits, rebuilt with that key alone, as
|
|
16
|
+
* `plugin(identity, { middleware })` or `new Application(name, { plugins })`, at `1.<key>`. A fault
|
|
17
|
+
* about one slot shows the slot, not every other one the author declared beside it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function slotSite(declaration: {
|
|
20
|
+
call: string;
|
|
21
|
+
named: unknown;
|
|
22
|
+
subject: string;
|
|
23
|
+
}, key: string, value: unknown): FactSite;
|
|
24
|
+
/**
|
|
25
|
+
* The dotted path to one part inside the value a site holds, such as `1.middleware.activate` for
|
|
26
|
+
* `activate` under a site at `1.middleware`. A site at the call's own arguments has an empty path.
|
|
27
|
+
*/
|
|
28
|
+
export declare function partOf(site: Pick<FactSite, 'at'>, ...keys: readonly (number | string)[]): string;
|
|
29
|
+
/** The finding that marks one part inside the value a site holds, with a note when given. */
|
|
30
|
+
export declare function partFinding(site: FactSite, keys: readonly (number | string)[], note?: string): Finding;
|
|
31
|
+
/**
|
|
32
|
+
* One fact's fault, which marks the fact inside the call that declared it. Every rule about one key
|
|
33
|
+
* of a declaration's config object reports through it, so each marks the key the same way.
|
|
34
|
+
*/
|
|
35
|
+
export declare function factFault(rule: DiagnosticRule, site: FactSite, parts: {
|
|
36
|
+
fact: string;
|
|
37
|
+
sentence: string;
|
|
38
|
+
correction: string;
|
|
39
|
+
}): DeclarationError;
|
|
40
|
+
/**
|
|
41
|
+
* One yes-or-no declaration key that holds a value other than a Boolean, such as `required` or
|
|
42
|
+
* `hidden`. Every such key reports under one rule, in one sentence and with one correction.
|
|
43
|
+
*/
|
|
44
|
+
export declare function flagFault(site: FactSite, flag: string): DeclarationError;
|
|
45
|
+
/**
|
|
46
|
+
* Where one input was declared: the site of its config object, and the dotted path to its declared
|
|
47
|
+
* name, which a fault about the name or about the whole input marks.
|
|
48
|
+
*/
|
|
49
|
+
export interface InputSite extends FactSite {
|
|
50
|
+
readonly named: string;
|
|
51
|
+
}
|
|
52
|
+
/** The site of an input a call declared as `call(name, config)`, such as `option()`. */
|
|
53
|
+
export declare function callSite(subject: string, declaration: Omit<Finding, 'mark' | 'note'>): InputSite;
|
|
54
|
+
/**
|
|
55
|
+
* The site of one option a plugin declared, rebuilt as `plugin(identity, { options })` from the
|
|
56
|
+
* options the plugin holds. The option's entry in the record stands for its name.
|
|
57
|
+
*/
|
|
58
|
+
export declare function pluginOptionSite(plugin: {
|
|
59
|
+
identity: string;
|
|
60
|
+
options: unknown;
|
|
61
|
+
}, name: string, subject: string): InputSite;
|
|
62
|
+
/**
|
|
63
|
+
* One line of prose: a string that holds a character other than whitespace and no line terminator.
|
|
64
|
+
* Every one-line fact reads this rule, and so does the label a configuration source answers with.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isProseLine(value: unknown): value is string;
|
|
1
67
|
/**
|
|
2
68
|
* The `description` core fact: one line of prose every projection reads. A value that is not a
|
|
3
69
|
* string fails the same way a blank one does, because the author reads one rule for one fact.
|
|
4
70
|
* An omitted description is absent, not a fault, so it passes through as `undefined`.
|
|
5
71
|
*/
|
|
6
|
-
export declare function checkDescription(
|
|
72
|
+
export declare function checkDescription(site: FactSite, value: unknown): string | undefined;
|
|
7
73
|
/**
|
|
8
74
|
* The `hidden` core fact: whether a listing omits this member. It is a Boolean, because a listing
|
|
9
75
|
* asks one question of it, and an omitted declaration reads `false`. Routing selects and parsing
|
|
10
76
|
* binds without reading it, so a hidden member behaves as any other.
|
|
11
77
|
*/
|
|
12
|
-
export declare function checkHidden(
|
|
78
|
+
export declare function checkHidden(site: FactSite, value: unknown): boolean;
|
|
13
79
|
/**
|
|
14
80
|
* The `deprecated` core fact: the one-line migration message a listing shows beside the member.
|
|
15
81
|
* It answers the rule a description answers, because both are one line of prose a projection
|
|
16
82
|
* prints. A bare `true` is rejected with every other value that is not prose: a deprecation with
|
|
17
83
|
* no migration path leaves an operator or an agent with nothing to do.
|
|
18
84
|
*/
|
|
19
|
-
export declare function checkDeprecated(
|
|
85
|
+
export declare function checkDeprecated(site: FactSite, value: unknown): string | undefined;
|
|
20
86
|
/**
|
|
21
87
|
* The declarations that carry neither listing fact: an argument, which cannot leave the grammar it
|
|
22
88
|
* sits in, and the root, which is every page's entry point. The types remove both keys there, and
|
|
23
89
|
* a JavaScript author, or a TypeScript author whose argument config is inferred from a value,
|
|
24
90
|
* reaches this rule instead.
|
|
25
91
|
*/
|
|
26
|
-
export declare function checkNoListingFacts(
|
|
92
|
+
export declare function checkNoListingFacts(site: FactSite, declared: object): void;
|
|
27
93
|
/**
|
|
28
94
|
* The `version` core fact, which the Application alone carries. Core reads it as an opaque string,
|
|
29
95
|
* because the convention is the package manifest's own field and no scheme is imposed on it. An
|
|
30
96
|
* omitted version is `0.0.0`, which means unversioned, and core keeps no record of which one the
|
|
31
97
|
* author wrote.
|
|
32
98
|
*/
|
|
33
|
-
export declare function checkVersion(value: unknown): string;
|
|
34
|
-
/**
|
|
35
|
-
* A structural value core reads as plain data: an object literal, and never a declaration that
|
|
36
|
-
* carries state of its own. The options slots read it to reject a value that is not an options
|
|
37
|
-
* object, and inspection reads it to copy a declared value faithfully.
|
|
38
|
-
*/
|
|
39
|
-
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
99
|
+
export declare function checkVersion(site: FactSite, value: unknown): string;
|