@loomcli/core 0.6.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.
Files changed (52) hide show
  1. package/NOTICE +34 -0
  2. package/dist/application.d.ts +11 -6
  3. package/dist/application.js +57 -19
  4. package/dist/capture.d.ts +65 -0
  5. package/dist/capture.js +99 -0
  6. package/dist/chain.d.ts +28 -17
  7. package/dist/chain.js +11 -10
  8. package/dist/command-rules.d.ts +1 -1
  9. package/dist/command-rules.js +2 -2
  10. package/dist/command.d.ts +31 -47
  11. package/dist/command.js +204 -209
  12. package/dist/errors.d.ts +22 -21
  13. package/dist/errors.js +38 -18
  14. package/dist/facts.d.ts +5 -0
  15. package/dist/facts.js +8 -1
  16. package/dist/globals.d.ts +48 -22
  17. package/dist/globals.js +72 -29
  18. package/dist/glyphs.generated.js +1 -1
  19. package/dist/index.d.ts +5 -3
  20. package/dist/index.js +3 -1
  21. package/dist/input-rules.d.ts +20 -2
  22. package/dist/input-rules.js +47 -5
  23. package/dist/inspect.d.ts +33 -17
  24. package/dist/inspect.js +74 -87
  25. package/dist/locate.js +52 -60
  26. package/dist/options.d.ts +101 -83
  27. package/dist/options.js +311 -267
  28. package/dist/parse.d.ts +162 -0
  29. package/dist/parse.js +601 -0
  30. package/dist/plain.d.ts +52 -2
  31. package/dist/plain.js +228 -2
  32. package/dist/plugin-rules.d.ts +8 -4
  33. package/dist/plugin-rules.js +13 -9
  34. package/dist/plugin-settings.d.ts +18 -0
  35. package/dist/plugin-settings.js +38 -0
  36. package/dist/plugin.d.ts +32 -27
  37. package/dist/plugin.js +107 -89
  38. package/dist/sources.d.ts +18 -9
  39. package/dist/sources.js +50 -20
  40. package/dist/style-layout.js +2 -2
  41. package/dist/style-width.d.ts +13 -0
  42. package/dist/style-width.js +170 -0
  43. package/dist/types.d.ts +70 -30
  44. package/dist/unicode.generated.d.ts +27 -0
  45. package/dist/unicode.generated.js +1036 -0
  46. package/dist/validation.d.ts +108 -34
  47. package/dist/validation.js +282 -125
  48. package/dist/view.d.ts +16 -11
  49. package/dist/view.js +13 -4
  50. package/licenses/unicode-LICENSE.txt +41 -0
  51. package/licenses/uucode-LICENSE.md +35 -0
  52. package/package.json +9 -5
package/dist/plain.js CHANGED
@@ -1,12 +1,238 @@
1
+ /**
2
+ * The verdict the declaring call under way reached on each part of a declaration it captured, so
3
+ * every later rule of that call reads that one verdict and never asks the part's prototype again.
4
+ * No call is under way outside `declaring`, so a run and every later call judge afresh.
5
+ */
6
+ let verdicts = undefined;
7
+ /**
8
+ * Runs one declaring call with verdicts of its own, dropped when it returns or throws. A call made
9
+ * inside another, such as a `plugin()` a getter makes, is part of the outer read and shares them.
10
+ */
11
+ function declaring(call) {
12
+ if (verdicts !== undefined) {
13
+ return call();
14
+ }
15
+ verdicts = new WeakMap();
16
+ try {
17
+ return call();
18
+ }
19
+ finally {
20
+ verdicts = undefined;
21
+ }
22
+ }
1
23
  /**
2
24
  * A structural value core reads as plain data: an object literal, and never a declaration that
3
25
  * carries state of its own. The options slots read it to reject a value that is not an options
4
- * object, and inspection reads it to copy a declared value faithfully.
26
+ * object, and `snapshot` reads it to copy a declared value faithfully. A value a declaring call
27
+ * already judged answers with that verdict.
5
28
  */
6
- export function isPlainObject(value) {
29
+ function isPlainObject(value) {
7
30
  if (value === null || typeof value !== 'object') {
8
31
  return false;
9
32
  }
33
+ return verdicts?.get(value) ?? hasPlainPrototype(value);
34
+ }
35
+ /** Whether one object's prototype, read once, is `Object.prototype` or `null`. */
36
+ function hasPlainPrototype(value) {
10
37
  const prototype = Object.getPrototypeOf(value);
11
38
  return prototype === Object.prototype || prototype === null;
12
39
  }
40
+ /**
41
+ * Whether a declaring call reads one part of a declaration as plain data, decided once: the first
42
+ * verdict is recorded, and `isPlainObject` answers with it for the same value from then on.
43
+ */
44
+ function decidePlain(value) {
45
+ if (value === null || typeof value !== 'object') {
46
+ return false;
47
+ }
48
+ const known = verdicts?.get(value);
49
+ if (known !== undefined) {
50
+ return known;
51
+ }
52
+ const verdict = hasPlainPrototype(value);
53
+ verdicts?.set(value, verdict);
54
+ return verdict;
55
+ }
56
+ /**
57
+ * A snapshot of one value. Arrays and plain objects are copied and frozen to any depth, so a
58
+ * consumer cannot reach the source through the copy. A value that holds itself is copied with the
59
+ * same cycle, because each source object is judged and copied once and every path to it reaches
60
+ * that copy. Primitives and library objects, such as a class instance or a `Date` a schema
61
+ * produced, are reported as they are, because core cannot copy them meaningfully. Build reads it
62
+ * for a converter's schema, and the chain for the request one middleware holds.
63
+ */
64
+ function snapshot(value) {
65
+ return copied(value, copying(Number.POSITIVE_INFINITY), 1);
66
+ }
67
+ /** The snapshot of one plain object, under the record type the caller already established. */
68
+ function snapshotRecord(value) {
69
+ return copiedRecord(value, copying(Number.POSITIVE_INFINITY), 1);
70
+ }
71
+ /**
72
+ * The snapshot `snapshot` takes, of a value whose paths may hold at most `levels` arrays and plain
73
+ * objects, the value itself included. Every path through a container the value holds twice counts
74
+ * it, and a value that holds itself has a path without end. Either kind of path past `levels`
75
+ * throws `NestedTooDeepError` before the walk goes deeper than `levels`, so neither the walk nor
76
+ * any later reader of the copy goes deeper either. A declaring call reads it for a declared default.
77
+ */
78
+ function boundedSnapshot(value, levels) {
79
+ return copied(value, copying(levels), 1);
80
+ }
81
+ /** What `boundedSnapshot` throws for a value with a path longer than its limit. */
82
+ class NestedTooDeepError extends Error {
83
+ name = 'NestedTooDeepError';
84
+ }
85
+ /** A snapshot about to start, whose paths may hold at most `levels` containers. */
86
+ function copying(levels) {
87
+ return { copies: new Map(), heights: new Map(), levels };
88
+ }
89
+ /**
90
+ * One value of a snapshot in progress, reached at `level`, where the value itself is level 1. An
91
+ * object the walk reached already answers as it did then, so its prototype is read once.
92
+ */
93
+ function copied(value, walk, level) {
94
+ if (typeof value !== 'object' || value === null) {
95
+ return value;
96
+ }
97
+ if (walk.copies.has(value)) {
98
+ return walk.copies.get(value);
99
+ }
100
+ if (Array.isArray(value)) {
101
+ return copiedList(value, walk, level);
102
+ }
103
+ if (isPlainObject(value)) {
104
+ return copiedRecord(value, walk, level);
105
+ }
106
+ return kept(value, walk);
107
+ }
108
+ /** An object core cannot copy, kept as it is, which every later reach answers with unread. */
109
+ function kept(value, walk) {
110
+ walk.copies.set(value, value);
111
+ walk.heights.set(value, 0);
112
+ return value;
113
+ }
114
+ /**
115
+ * How many containers the longest path from one entry holds, once the walk has copied the entry of
116
+ * a container reached at `level`. A container the walk filled counts every container below it,
117
+ * even one it reached first by a shorter path. A container still being filled is one this entry
118
+ * leads back into, so the path through it has no end, and any other value holds none.
119
+ */
120
+ function checkedHeight(entry, walk, level) {
121
+ const height = typeof entry === 'object' && entry !== null
122
+ ? (walk.heights.get(entry) ?? Number.POSITIVE_INFINITY)
123
+ : 0;
124
+ if (level + height > walk.levels) {
125
+ throw new NestedTooDeepError();
126
+ }
127
+ return height;
128
+ }
129
+ /**
130
+ * The copier of each entry of one container reached at `level`, which counts how many containers
131
+ * the longest path from that container holds, itself included.
132
+ */
133
+ function entryCopier(walk, level) {
134
+ let height = 1;
135
+ return {
136
+ copy: (entry) => {
137
+ const copy = copied(entry, walk, level + 1);
138
+ height = Math.max(height, checkedHeight(entry, walk, level) + 1);
139
+ return copy;
140
+ },
141
+ height: () => height,
142
+ };
143
+ }
144
+ /** Throws `NestedTooDeepError` for a container the walk first reaches past its limit. */
145
+ function checkLevel(walk, level) {
146
+ if (level > walk.levels) {
147
+ throw new NestedTooDeepError();
148
+ }
149
+ }
150
+ /**
151
+ * One array's copy. It joins `copies` before its entries are read, so an entry that leads back to
152
+ * it reaches the copy, and it is frozen once it is filled. A hole stays a hole.
153
+ */
154
+ function copiedList(list, walk, level) {
155
+ checkLevel(walk, level);
156
+ const copy = [];
157
+ walk.copies.set(list, copy);
158
+ const entries = entryCopier(walk, level);
159
+ fillList(copy, list, entries.copy);
160
+ walk.heights.set(list, entries.height());
161
+ return Object.freeze(copy);
162
+ }
163
+ /**
164
+ * One plain object's copy on `Object.prototype`, filled and frozen as an array's copy is. Each key
165
+ * is defined as an own data property, so a key of `__proto__` is stored under that name.
166
+ */
167
+ function copiedRecord(record, walk, level) {
168
+ checkLevel(walk, level);
169
+ const copy = {};
170
+ walk.copies.set(record, copy);
171
+ const entries = entryCopier(walk, level);
172
+ for (const [key, entry] of Object.entries(record)) {
173
+ defineEntry(copy, key, entries.copy(entry));
174
+ }
175
+ walk.heights.set(record, entries.height());
176
+ return Object.freeze(copy);
177
+ }
178
+ /** Defines one key as an own data property, so a key of `__proto__` is stored under that name. */
179
+ function defineEntry(target, key, value) {
180
+ Object.defineProperty(target, key, {
181
+ configurable: true,
182
+ enumerable: true,
183
+ value,
184
+ writable: true,
185
+ });
186
+ }
187
+ /**
188
+ * A copy of every own string key of one object, enumerable or not, each described once and read
189
+ * once, in the order the object lists its keys. `entering` hears each key before it is read, so a
190
+ * read that throws can name it. A symbol key is not copied, because no declaration declares one.
191
+ */
192
+ function copyOwnKeys(declared, entering = () => undefined) {
193
+ const copy = {};
194
+ for (const key of Reflect.ownKeys(declared)) {
195
+ if (typeof key === 'string') {
196
+ entering(key);
197
+ if (Reflect.getOwnPropertyDescriptor(declared, key) !== undefined) {
198
+ defineEntry(copy, key, Reflect.get(declared, key));
199
+ }
200
+ }
201
+ }
202
+ return copy;
203
+ }
204
+ /**
205
+ * The copy of one part of a declaration that `decidePlain` judges plain, by `copyOwnKeys`. Any other
206
+ * value is answered as it is, so the rule for its slot reports it.
207
+ */
208
+ function shallowRecord(value) {
209
+ return decidePlain(value) ? copyOwnKeys(value) : value;
210
+ }
211
+ /**
212
+ * Fills `copy` with what `convert` makes of each entry of `list`, read by its length once and then
213
+ * index by index, so none of the list's own methods runs. A hole stays a hole.
214
+ */
215
+ function fillList(copy, list, convert) {
216
+ const { length } = list;
217
+ for (let index = 0; index < length; index += 1) {
218
+ if (Object.hasOwn(list, index)) {
219
+ defineEntry(copy, index, convert(list[index]));
220
+ }
221
+ }
222
+ copy.length = length;
223
+ }
224
+ /** A copy of one list's entries by `fillList`. Each entry is what its factory built, so it is kept. */
225
+ function copyList(list) {
226
+ const copy = [];
227
+ fillList(copy, list, (entry) => entry);
228
+ return copy;
229
+ }
230
+ /** The copy of a value that is a list, by `copyList`. Any other value is answered as it is. */
231
+ function shallowList(value) {
232
+ if (!Array.isArray(value)) {
233
+ return value;
234
+ }
235
+ const list = value;
236
+ return copyList(list);
237
+ }
238
+ export { boundedSnapshot, copyList, copyOwnKeys, decidePlain, declaring, isPlainObject, NestedTooDeepError, shallowList, shallowRecord, snapshot, snapshotRecord, };
@@ -5,6 +5,12 @@ declare const notAList: import("./diagnostic-text.js").DiagnosticRule;
5
5
  * that is not an object.
6
6
  */
7
7
  declare const notAnObject: import("./diagnostic-text.js").DiagnosticRule;
8
+ /**
9
+ * A declaration whose read throws while core takes its one copy, such as a getter that throws or a
10
+ * proxy whose trap throws: the config of an argument or option, a plugin's definition and each of
11
+ * its option declarations, and the options of a Command or of the Application.
12
+ */
13
+ declare const unreadableDeclaration: import("./diagnostic-text.js").DiagnosticRule;
8
14
  /** A list entry that its factory did not build, such as a hand-made plugin or translation. */
9
15
  declare const foreignValue: import("./diagnostic-text.js").DiagnosticRule;
10
16
  /** One plugin identity installed twice. */
@@ -13,8 +19,6 @@ declare const pluginInstalledTwice: import("./diagnostic-text.js").DiagnosticRul
13
19
  declare const slotTaken: import("./diagnostic-text.js").DiagnosticRule;
14
20
  /** A plugin, extension, or view identity outside the identity grammar. */
15
21
  declare const invalidIdentity: import("./diagnostic-text.js").DiagnosticRule;
16
- /** A validator or a presence rule on a plugin option. */
17
- declare const pluginOptionRule: import("./diagnostic-text.js").DiagnosticRule;
18
22
  /** A middleware activation that is missing, empty, or names an option the plugin lacks. */
19
23
  declare const middlewareActivation: import("./diagnostic-text.js").DiagnosticRule;
20
24
  /** A loader, a hook, or a translator that core cannot call. */
@@ -25,7 +29,7 @@ declare const unknownSignal: import("./diagnostic-text.js").DiagnosticRule;
25
29
  declare const signalClaimedTwice: import("./diagnostic-text.js").DiagnosticRule;
26
30
  /** A configuration source binding that is not one of the plugin's option extensions. */
27
31
  declare const sourceBinding: import("./diagnostic-text.js").DiagnosticRule;
28
- /** A plugin option that carries its own plugin's source binding. */
32
+ /** A plugin's option that carries its own plugin's source binding. */
29
33
  declare const sourceBoundOwnOption: import("./diagnostic-text.js").DiagnosticRule;
30
34
  /** Two distinct objects under one extension or declared-view identity. */
31
35
  declare const twoPackageCopies: import("./diagnostic-text.js").DiagnosticRule;
@@ -59,4 +63,4 @@ declare const renderingPolicyRule: import("./diagnostic-text.js").DiagnosticRule
59
63
  declare const themeMapping: import("./diagnostic-text.js").DiagnosticRule;
60
64
  /** A theme name that a built-in style member already holds. */
61
65
  declare const themeNameTaken: import("./diagnostic-text.js").DiagnosticRule;
62
- export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, pluginOptionRule, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, };
66
+ export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, unreadableDeclaration, };
@@ -6,7 +6,7 @@ import { registerRule } from './diagnostic-text.js';
6
6
  */
7
7
  /** A slot that holds a list, such as `plugins` or `commands`, holding a value of another kind. */
8
8
  const notAList = registerRule('@loomcli/core/not-a-list', {
9
- explanation: 'Core reads plugins, commands, extensions, views, translators, and signals each as a list, in order. A value of any other kind has no entries to read.',
9
+ explanation: 'Core reads plugins, commands, extensions, views, translators, signals, and aliases each as a list, in order. A value of any other kind has no entries to read.',
10
10
  headline: 'Not a list',
11
11
  });
12
12
  /**
@@ -14,9 +14,18 @@ const notAList = registerRule('@loomcli/core/not-a-list', {
14
14
  * that is not an object.
15
15
  */
16
16
  const notAnObject = registerRule('@loomcli/core/not-an-object', {
17
- explanation: "Core reads the options of a Command and of the Application, a plugin's definition, its options record, each of its option declarations, its middleware, its source, and the config of an argument or option by their keys. A value of any other kind has no keys to read.",
17
+ explanation: "Core reads the options of a Command and of the Application, a plugin's definition, its options record, each of its option declarations, its middleware, its source, the config of an argument or option, and the settings a plugin factory takes by their keys. A value of any other kind has no keys to read.",
18
18
  headline: 'Not an object',
19
19
  });
20
+ /**
21
+ * A declaration whose read throws while core takes its one copy, such as a getter that throws or a
22
+ * proxy whose trap throws: the config of an argument or option, a plugin's definition and each of
23
+ * its option declarations, and the options of a Command or of the Application.
24
+ */
25
+ const unreadableDeclaration = registerRule('@loomcli/core/unreadable-declaration', {
26
+ explanation: 'Core reads a declaration by its keys, and each list in it by index, once, at the call that declares it, and checks and records the copy it takes. A read that throws, such as a throwing getter or proxy trap, leaves core nothing to check or record.',
27
+ headline: 'Declaration could not be read',
28
+ });
20
29
  /** A list entry that its factory did not build, such as a hand-made plugin or translation. */
21
30
  const foreignValue = registerRule('@loomcli/core/foreign-value', {
22
31
  explanation: 'Core reads a plugin, an extension, an extension value, a declared view, a view override, and a translation through facts its factory recorded when it built the value. Any other value carries none, even one of the same shape.',
@@ -37,11 +46,6 @@ const invalidIdentity = registerRule('@loomcli/core/invalid-identity', {
37
46
  explanation: 'An identity keys what a plugin, an extension, or a view contributes, names it in every diagnostic, and prefixes the identities of the rules its package declares, so it is a package name as npm spells one, scoped or not, then any subpath segments, each after a / and each of lowercase letters and digits in words joined by single hyphens.',
38
47
  headline: 'Invalid identity',
39
48
  });
40
- /** A validator or a presence rule on a plugin option. */
41
- const pluginOptionRule = registerRule('@loomcli/core/plugin-option-rule', {
42
- explanation: "A plugin's middleware interprets its own options' values, so a plugin option declares how it parses and nothing more: no validator and no presence rule.",
43
- headline: 'Rule on a plugin option',
44
- });
45
49
  /** A middleware activation that is missing, empty, or names an option the plugin lacks. */
46
50
  const middlewareActivation = registerRule('@loomcli/core/middleware-activation', {
47
51
  explanation: "Activation decides when core loads a plugin's middleware: on every run with 'always', or only when an invocation supplies one of the plugin's own options the list names, so a middleware no invocation needs costs it nothing.",
@@ -67,7 +71,7 @@ const sourceBinding = registerRule('@loomcli/core/source-binding', {
67
71
  explanation: 'A configuration source answers the options that carry its binding, an extension the plugin lists under extensions that applies to options. Core asks the source about those options without knowing what the binding means.',
68
72
  headline: 'Invalid source binding',
69
73
  });
70
- /** A plugin option that carries its own plugin's source binding. */
74
+ /** A plugin's option that carries its own plugin's source binding. */
71
75
  const sourceBoundOwnOption = registerRule('@loomcli/core/source-bound-own-option', {
72
76
  explanation: "A plugin's own options resolve before its configuration source loads, because the source reads them, so none of them can take a value from that source.",
73
77
  headline: 'Source bound to its own option',
@@ -152,4 +156,4 @@ const themeNameTaken = registerRule('@loomcli/core/theme-name-taken', {
152
156
  explanation: 'Each theme name becomes a member of the style object beside the built-in members, so a name a built-in already holds would hide it.',
153
157
  headline: 'Theme name taken',
154
158
  });
155
- export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, pluginOptionRule, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, };
159
+ export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, unreadableDeclaration, };
@@ -0,0 +1,18 @@
1
+ /** The plugin factory whose settings `checkShortSetting` judges, and the option `short` spells. */
2
+ interface ShortSettingDeclarer {
3
+ /** The plugin's identity, which the sentence and each finding's note name. */
4
+ readonly plugin: string;
5
+ /** The factory's name, the call each finding quotes, such as `'format'`. */
6
+ readonly call: string;
7
+ /** The name of the option the setting gives a short spelling, such as `'format'`. */
8
+ readonly option: string;
9
+ }
10
+ /**
11
+ * Judges a plugin factory's settings at its call, under core's own rules: settings that are neither
12
+ * undefined nor a plain object are the not-an-object fault, and a `short` that is not one ASCII
13
+ * letter is the short-alias fault every option's short spelling answers, in the same sentence. Each
14
+ * finding quotes the factory's call and names the plugin, so a first-party plugin that takes a short
15
+ * spelling as `{ short }` reports it where the author wrote it.
16
+ */
17
+ export declare function checkShortSetting(settings: unknown, declarer: ShortSettingDeclarer): void;
18
+ export {};
@@ -0,0 +1,38 @@
1
+ import { DeclarationError, quoted } from './errors.js';
2
+ import { declarerNote } from './facts.js';
3
+ import { shortAlias } from './input-rules.js';
4
+ import { shortAliasText, isShortAlias } from './options.js';
5
+ import { isPlainObject } from './plain.js';
6
+ import { notAnObject } from './plugin-rules.js';
7
+ /**
8
+ * Judges a plugin factory's settings at its call, under core's own rules: settings that are neither
9
+ * undefined nor a plain object are the not-an-object fault, and a `short` that is not one ASCII
10
+ * letter is the short-alias fault every option's short spelling answers, in the same sentence. Each
11
+ * finding quotes the factory's call and names the plugin, so a first-party plugin that takes a short
12
+ * spelling as `{ short }` reports it where the author wrote it.
13
+ */
14
+ export function checkShortSetting(settings, declarer) {
15
+ if (settings === undefined) {
16
+ return;
17
+ }
18
+ const { call, option, plugin } = declarer;
19
+ const finding = (mark) => ({
20
+ arguments: [settings],
21
+ call,
22
+ mark,
23
+ note: declarerNote(plugin),
24
+ });
25
+ if (!isPlainObject(settings)) {
26
+ throw new DeclarationError(notAnObject, {
27
+ correction: 'Supply a settings object, or omit the settings.',
28
+ findings: [finding('0')],
29
+ sentence: `Plugin ${quoted(plugin)} declares settings that are not an object.`,
30
+ });
31
+ }
32
+ if (settings.short !== undefined && !isShortAlias(settings.short)) {
33
+ throw new DeclarationError(shortAlias, {
34
+ ...shortAliasText(`Option ${quoted(option)}`),
35
+ findings: [finding('0.short')],
36
+ });
37
+ }
38
+ }
package/dist/plugin.d.ts CHANGED
@@ -9,18 +9,21 @@ import type { ProcessSignal } from './signals.js';
9
9
  import type { Palette } from './style-state.js';
10
10
  import type { ContextualStyle, ThemeConstraint, ThemeMapping } from './style.js';
11
11
  import type { Translation, TranslationContributor } from './translators.js';
12
- import type { CommandAttachHook, Host, OptionValue, Out, PluginOptionConfig } from './types.js';
12
+ import type { CommandAttachHook, GlobalOptionConfig, Host, OptionValue, Out } from './types.js';
13
13
  import type { OptionInput } from './validation.js';
14
14
  import type { ViewContribution, ViewSubject } from './view.js';
15
15
  /**
16
- * The declaration record a plugin contributes its options under: the parsing part of an option
17
- * config, keyed by option name. A plugin option carries no schema and no presence rule, so the
18
- * config type publishes neither, and `plugin()` repeats the rule for a JavaScript author.
16
+ * The declaration record a plugin contributes its global options under, keyed by option name. Each
17
+ * entry takes the configuration `globalOption()` takes, so the config type publishes no presence
18
+ * rule, and `plugin()` repeats the rule for a JavaScript author.
19
+ */
20
+ type PluginOptions = Readonly<Record<string, GlobalOptionConfig>>;
21
+ /**
22
+ * The values one plugin's options take, read through the same `OptionValue` an action's options
23
+ * are, so a validated option reads as its validator's output.
19
24
  */
20
- type PluginOptions = Readonly<Record<string, PluginOptionConfig>>;
21
- /** The values one plugin's own options take, read through the same rules an action's options are. */
22
25
  type PluginOptionValues<Options extends PluginOptions> = {
23
- readonly [Name in keyof Options]: OptionValue<Options[Name]>;
26
+ readonly [Name in keyof Options & string]: OptionValue<Options[Name]>;
24
27
  };
25
28
  /**
26
29
  * The spelling that supplied each of one plugin's own options given as a token, such as `-h`,
@@ -48,6 +51,16 @@ declare class PluginDeclaration<Options extends PluginOptions, Theme extends The
48
51
  type Plugin<Options extends PluginOptions = PluginOptions, Theme extends ThemeMapping = {}> = Pick<PluginDeclaration<Options, Theme>, typeof pluginOptions | typeof pluginTheme>;
49
52
  /** The declared options of a plugin, or of the factory that returns one. */
50
53
  type OptionsOf<Contributor> = Contributor extends Plugin<infer Options> ? Options : Contributor extends (...args: never[]) => Plugin<infer Options> ? Options : PluginOptions;
54
+ /**
55
+ * The option values an installed plugin tuple contributes to every action's options, each plugin's
56
+ * read through `PluginOptionValues`. The Application computes it once from the constructor's
57
+ * `plugins`. A plugin typed as the wide `Plugin` states no option names, and a list widened to
58
+ * `Plugin[]` states no plugins, so neither names an option.
59
+ */
60
+ type InstalledOptionValues<Plugins extends readonly Plugin[]> = Plugins extends readonly [
61
+ infer First extends Plugin,
62
+ ...infer Rest extends readonly Plugin[]
63
+ ] ? (string extends keyof OptionsOf<First> ? {} : PluginOptionValues<OptionsOf<First>>) & InstalledOptionValues<Rest> : {};
51
64
  /** A middleware reads its own plugin's options and either takes over or continues the chain. */
52
65
  type Middleware<Contributor extends Plugin | ((...args: never[]) => Plugin)> = (context: MiddlewareContext<OptionsOf<Contributor>>) => Promise<void> | void;
53
66
  /**
@@ -66,11 +79,12 @@ interface SourceContext<Options extends PluginOptions = PluginOptions> {
66
79
  readonly style: ContextualStyle;
67
80
  }
68
81
  /**
69
- * One answer: a value of the option's raw type, a string, a Boolean, or a list of strings for a
70
- * multiple option, and the one-line label core prints in a diagnostic about the value.
82
+ * One answer: a value of the option's raw type, a string, a Boolean, a whole number of 0 or more
83
+ * for a counted option, or a list of strings for a multiple option, and the one-line label core
84
+ * prints in a diagnostic about the value.
71
85
  */
72
86
  interface SourceAnswer {
73
- readonly value: string | boolean | readonly string[];
87
+ readonly value: string | boolean | number | readonly string[];
74
88
  readonly label: string;
75
89
  }
76
90
  /**
@@ -114,8 +128,8 @@ interface PluginDefinition<Options extends PluginOptions = PluginOptions, Theme
114
128
  * One plugin: an identity and the contributions it carries. Creating and installing the value runs
115
129
  * none of its code: `onCommandAttach` runs at graph build, `onFailure` runs when `run()` renders a
116
130
  * failure, and the middleware runs inside an invocation, so an installed plugin an invocation never
117
- * reaches costs that invocation its hooks alone. Every rule
118
- * that one definition carries on its own throws here, before the value exists.
131
+ * reaches costs that invocation its hooks alone. The definition is read once, after the identity,
132
+ * and every rule that one definition carries on its own throws here, before the value exists.
119
133
  */
120
134
  declare function plugin<Options extends PluginOptions = {}, const Theme extends ThemeMapping = {}>(identity: string, definition: PluginDefinition<Options, Theme>): Plugin<NoInfer<Options>, NoInfer<Theme>>;
121
135
  /** How every plugin diagnostic names one plugin at the start of a sentence. */
@@ -130,9 +144,9 @@ interface InstalledPlugins {
130
144
  /**
131
145
  * The installed list in composition order, with every rule that reads two plugins together: an
132
146
  * identity installed twice, a second claim on the theme slot, the signals slot, or the
133
- * configuration source, and two distinct descriptors under one identity. The slot is read
134
- * defensively, because a JavaScript author reaches it with any value. Each plugin's own rules
135
- * already ran at its `plugin()` call.
147
+ * configuration source, and two distinct descriptors under one identity. The slot is the copy the
148
+ * Application's constructor took, which a JavaScript author fills with any value. Each plugin's own
149
+ * rules already ran at its `plugin()` call.
136
150
  */
137
151
  declare function installPlugins(application: string, plugins: unknown): InstalledPlugins;
138
152
  /**
@@ -164,7 +178,7 @@ interface BuiltPlugin {
164
178
  onCommandAttach: CommandAttachHook | undefined;
165
179
  /** The hook core calls for each failure `run()` renders after graph build, or nothing. */
166
180
  onFailure: FailureHook | undefined;
167
- /** The plugin's own `views` slot, read once the validated theme is in place. */
181
+ /** The copy of the plugin's own `views` list, which the Application reads again. */
168
182
  views: unknown;
169
183
  /** The translations the plugin registers, which resolve after the application's. */
170
184
  translators: TranslationContributor;
@@ -182,17 +196,8 @@ interface BuiltPlugin {
182
196
  }
183
197
  /** The signals the one slot owner claimed, or none when no installed plugin claims the slot. */
184
198
  declare function ownedSignals(plugins: readonly BuiltPlugin[]): readonly ProcessSignal[];
185
- /** The value shape a plugin option takes, which is what `OptionValue` gives its declaration. */
186
- type PluginValues = Record<string, string | string[] | boolean | undefined>;
187
- /**
188
- * One plugin's own option values for one run: what argv or an input source supplied, or the
189
- * declared default, filled without validation. A collected value and an array default are copied,
190
- * so a plugin that writes to what it received changes neither the declaration nor the next run.
191
- * Entries become own keys even for a name such as `__proto__`, which assignment would not.
192
- */
193
- declare function pluginValues(inputs: readonly OptionInput[], values: OptionValues): PluginValues;
194
199
  /** One plugin's own spellings for one run, frozen, so no plugin writes what another reads. */
195
200
  declare function pluginSpellings(inputs: readonly OptionInput[], values: OptionValues): Readonly<Record<string, string>>;
196
201
  type ThemeOf<Contributor> = [Contributor] extends [never] ? {} : Contributor extends Plugin<PluginOptions, infer Theme> ? Theme : {};
197
- export type { ThemeOf, BuiltPlugin, BuiltSource, PluginValues, SourceAnswer, SourceContext, SourceResolver, Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, };
198
- export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews, pluginValues, };
202
+ export type { ThemeOf, BuiltPlugin, BuiltSource, InstalledOptionValues, SourceAnswer, SourceContext, SourceResolver, Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, };
203
+ export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews, };