@loomcli/core 0.6.0 → 0.7.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/NOTICE +34 -0
- package/dist/application.js +47 -15
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/command.js +73 -44
- package/dist/facts.d.ts +5 -0
- package/dist/facts.js +8 -1
- package/dist/globals.js +9 -7
- package/dist/glyphs.generated.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/input-rules.d.ts +5 -1
- package/dist/input-rules.js +8 -1
- package/dist/inspect.d.ts +0 -8
- package/dist/inspect.js +5 -21
- package/dist/options.d.ts +7 -0
- package/dist/options.js +13 -7
- package/dist/plain.d.ts +52 -2
- package/dist/plain.js +228 -2
- package/dist/plugin-rules.d.ts +7 -1
- package/dist/plugin-rules.js +11 -2
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +6 -6
- package/dist/plugin.js +98 -45
- package/dist/sources.js +4 -4
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -0
- package/dist/types.d.ts +2 -2
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +33 -12
- package/dist/validation.js +73 -28
- package/dist/view.d.ts +16 -11
- package/dist/view.js +13 -4
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
package/dist/globals.js
CHANGED
|
@@ -4,7 +4,7 @@ import { buildExtensions } from './extension.js';
|
|
|
4
4
|
import { callSite, checkDeprecated, checkDescription, checkHidden, factFault, pluginOptionSite, siteFinding, } from './facts.js';
|
|
5
5
|
import { globalPresenceRule, optionDeclaredTwice, spellingTaken } from './input-rules.js';
|
|
6
6
|
import { checkOptionName, compileOptions, spellingMark } from './options.js';
|
|
7
|
-
import {
|
|
7
|
+
import { captureInputConfig, configUnread } from './validation.js';
|
|
8
8
|
const globalSubject = 'the global options';
|
|
9
9
|
/**
|
|
10
10
|
* The presence keys a global option may not declare, in the order its diagnostic names them. A
|
|
@@ -99,7 +99,7 @@ function globalSite(input) {
|
|
|
99
99
|
*/
|
|
100
100
|
function pluginSites(identity, inputs) {
|
|
101
101
|
const options = Object.fromEntries(inputs.map(({ config, name }) => [name, config]));
|
|
102
|
-
return (input) => pluginOptionSite({ identity, options }, input.name, `Plugin ${quoted(identity)} option
|
|
102
|
+
return (input) => pluginOptionSite({ identity, options }, input.name, `Plugin ${quoted(identity)} option ${quoted(input.name)}`);
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* One global option's own facts, binding, and extension values, checked at its `globalOption()`
|
|
@@ -108,9 +108,11 @@ function pluginSites(identity, inputs) {
|
|
|
108
108
|
*/
|
|
109
109
|
function declareGlobalOption(state, declared, descriptors) {
|
|
110
110
|
// The name is judged before the config, as every other declaration judges its own name first.
|
|
111
|
-
checkOptionName(declared.name, globalSite(declared));
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
checkOptionName(declared.name, configUnread(globalSite(declared)));
|
|
112
|
+
const input = {
|
|
113
|
+
...declared,
|
|
114
|
+
config: captureInputConfig(declared, { call: 'globalOption', path: [] }),
|
|
115
|
+
};
|
|
114
116
|
const site = globalSite(input);
|
|
115
117
|
const sentence = site.subject;
|
|
116
118
|
const rejected = omissionRules.find((key) => key in input.config);
|
|
@@ -168,7 +170,7 @@ function globalTable(inputs, plugins) {
|
|
|
168
170
|
...plugins.flatMap((installed) => {
|
|
169
171
|
const siteOf = pluginSites(installed.identity, installed.inputs);
|
|
170
172
|
return boundOptions(installed.inputs, (input) => ({
|
|
171
|
-
phrase: `plugin ${quoted(installed.identity)} option
|
|
173
|
+
phrase: `plugin ${quoted(installed.identity)} option ${quoted(input.name)}`,
|
|
172
174
|
site: siteOf(input),
|
|
173
175
|
}));
|
|
174
176
|
}),
|
|
@@ -206,7 +208,7 @@ function checkLocalOptions(declarations, table, scope) {
|
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
claimVariables(boundOptions(declarations, (input) => ({
|
|
209
|
-
phrase: `${subject} option
|
|
211
|
+
phrase: `${subject} option ${quoted(input.name)}`,
|
|
210
212
|
site: siteOf(input),
|
|
211
213
|
})), table.variables);
|
|
212
214
|
return options;
|
package/dist/glyphs.generated.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { locate } from './locate.js';
|
|
|
13
13
|
export { incompleteResult, lanes } from './lanes.js';
|
|
14
14
|
export { override, view } from './view.js';
|
|
15
15
|
export { plugin } from './plugin.js';
|
|
16
|
+
export { checkShortSetting } from './plugin-settings.js';
|
|
16
17
|
export { translate } from './translators.js';
|
|
17
18
|
export { glyph } from './glyphs.generated.js';
|
|
18
19
|
export type { RenderingPolicy } from './rendering.js';
|
|
@@ -30,7 +31,7 @@ export type { CancellationReason } from './signals.js';
|
|
|
30
31
|
export type { ErrorClass, Translation, Translator } from './translators.js';
|
|
31
32
|
export type { IncompleteResult } from './lanes.js';
|
|
32
33
|
export type { WordPosition } from './locate.js';
|
|
33
|
-
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureView, FailureViewContext, ViewContribution, ViewOverride, } from './view.js';
|
|
34
|
+
export type { AnyDeclaredView, AnyOverrideKey, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureView, FailureViewContext, ReplacementView, ViewContribution, ViewOverride, } from './view.js';
|
|
34
35
|
export type { ArgumentNode, CommandGraph, CommandNode, OptionNode, ResultNode } from './inspect.js';
|
|
35
36
|
export type { Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, SourceAnswer, SourceContext, SourceResolver, } from './plugin.js';
|
|
36
37
|
export type { Action, ActionArgs, ActionContext, ActionHandler, ActionOptions, ArgumentConfig, AttachedCommand, BooleanOption, CommandAttachHook, ExitCode, Host, InputIdentity, InputTerminal, Out, OptionConfig, OutputTerminal, Request, ResultInput, ResultView, ResultViews, RunOptions, ScalarArgument, StringOption, SuppliedInputs, RowView, RowViews, ValidationContext, VariadicArgument, View, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { locate } from './locate.js';
|
|
|
11
11
|
export { incompleteResult, lanes } from './lanes.js';
|
|
12
12
|
export { override, view } from './view.js';
|
|
13
13
|
export { plugin } from './plugin.js';
|
|
14
|
+
export { checkShortSetting } from './plugin-settings.js';
|
|
14
15
|
export { translate } from './translators.js';
|
|
15
16
|
export { glyph } from './glyphs.generated.js';
|
|
16
17
|
export { pad, style } from './style.js';
|
package/dist/input-rules.d.ts
CHANGED
|
@@ -57,8 +57,12 @@ declare const requiredWithDefault: import("./diagnostic-text.js").DiagnosticRule
|
|
|
57
57
|
declare const notAValidator: import("./diagnostic-text.js").DiagnosticRule;
|
|
58
58
|
/** A default of the wrong raw shape for its declaration. */
|
|
59
59
|
declare const defaultShape: import("./diagnostic-text.js").DiagnosticRule;
|
|
60
|
+
/** The most arrays and plain objects any path through a declared default may hold. */
|
|
61
|
+
declare const defaultLevels = 10;
|
|
62
|
+
/** A declared default nested deeper than `defaultLevels`. */
|
|
63
|
+
declare const defaultDepth: import("./diagnostic-text.js").DiagnosticRule;
|
|
60
64
|
/** A declared default its validator rejected. */
|
|
61
65
|
declare const invalidDefault: import("./diagnostic-text.js").DiagnosticRule;
|
|
62
66
|
/** A validator's JSON Schema converter that threw or returned a value that is not a plain object. */
|
|
63
67
|
declare const schemaConverterFailed: import("./diagnostic-text.js").DiagnosticRule;
|
|
64
|
-
export { booleanOptionMultiple, booleanOptionValueRule, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
|
68
|
+
export { booleanOptionMultiple, booleanOptionValueRule, defaultDepth, defaultLevels, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
package/dist/input-rules.js
CHANGED
|
@@ -132,6 +132,13 @@ const defaultShape = registerRule('@loomcli/core/default-shape', {
|
|
|
132
132
|
explanation: "A default stands in for the value an operator would supply. Without a validator it is that raw value, a string or, for an input that takes several values, an array of strings; with one, it is the validator's input, and an input that takes several values still takes an array of them.",
|
|
133
133
|
headline: 'Default of the wrong shape',
|
|
134
134
|
});
|
|
135
|
+
/** The most arrays and plain objects any path through a declared default may hold. */
|
|
136
|
+
const defaultLevels = 10;
|
|
137
|
+
/** A declared default nested deeper than `defaultLevels`. */
|
|
138
|
+
const defaultDepth = registerRule('@loomcli/core/default-depth', {
|
|
139
|
+
explanation: `A default stands in for the value an operator would supply, and every reader of the graph, help and the manifest included, walks it. Core keeps every path through a default within ${String(defaultLevels)} levels of arrays and plain objects, and a default that holds itself nests without end, so every reader stays far inside the call stack on every runtime.`,
|
|
140
|
+
headline: 'Default nested too deep',
|
|
141
|
+
});
|
|
135
142
|
/** A declared default its validator rejected. */
|
|
136
143
|
const invalidDefault = registerRule('@loomcli/core/invalid-default', {
|
|
137
144
|
explanation: 'Each run passes every declared default through its validator before it reads a token, because a default reaches the action as a validated value. A default the validator rejects would reach no action, whatever the operator supplies.',
|
|
@@ -142,4 +149,4 @@ const schemaConverterFailed = registerRule('@loomcli/core/schema-converter-faile
|
|
|
142
149
|
explanation: "Help, the manifest, and completion read what an input accepts from the JSON Schema its validator publishes. A converter that throws or returns anything but a plain object publishes no shape, so a distributed build reads the input's schema as null.",
|
|
143
150
|
headline: 'Schema converter failed',
|
|
144
151
|
});
|
|
145
|
-
export { booleanOptionMultiple, booleanOptionValueRule, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
|
152
|
+
export { booleanOptionMultiple, booleanOptionValueRule, defaultDepth, defaultLevels, defaultShape, envName, envOnArgument, envOnMultiple, flagNotBoolean, globalOptionAfterCommand, globalPresenceRule, invalidDefault, notAValidator, omissionAlreadyDecided, nameSharedAcrossKinds, omissionWithoutValidator, optionDeclaredTwice, optionPolarity, optionType, polarityOnString, requiredWithDefault, schemaConverterFailed, shortAlias, shortOnlyBothPolarities, shortOnlyWithoutShort, spellingTaken, variableBoundTwice, };
|
package/dist/inspect.d.ts
CHANGED
|
@@ -110,14 +110,6 @@ interface CommandGraph {
|
|
|
110
110
|
readonly globals: readonly OptionNode[];
|
|
111
111
|
readonly root: CommandNode;
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
114
|
-
* A snapshot of one value. Arrays and plain objects are copied and frozen to any depth, so a
|
|
115
|
-
* consumer cannot reach the source through the copy, and a later call reports the value again.
|
|
116
|
-
* Primitives and library objects, such as a class instance or a `Date` a schema produced, are
|
|
117
|
-
* reported as they are, because core cannot copy them meaningfully. The graph reads it for a
|
|
118
|
-
* declared value and the chain reads it for the request one middleware holds.
|
|
119
|
-
*/
|
|
120
|
-
export declare function snapshot(value: unknown): unknown;
|
|
121
113
|
/** The declared result as plain data, or `null` on a Command that declares none. */
|
|
122
114
|
declare function resultNode(result: DeclaredResult | undefined): ResultNode | null;
|
|
123
115
|
/**
|
package/dist/inspect.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { asSentence, DeclarationError, InternalError, reasonOf } from './errors.js';
|
|
2
2
|
import { partOf, siteFinding } from './facts.js';
|
|
3
3
|
import { schemaConverterFailed } from './input-rules.js';
|
|
4
|
-
import { isPlainObject } from './plain.js';
|
|
4
|
+
import { isPlainObject, snapshotRecord } from './plain.js';
|
|
5
5
|
import { foreignGraph, foreignGraphCorrection } from './rules.js';
|
|
6
6
|
import { declaringSite, inputPlace, validatesOmission } from './validation.js';
|
|
7
7
|
/** A declaration that carries no extension value publishes one shared, empty frozen record. */
|
|
@@ -21,28 +21,12 @@ function spellingsOf(table, name) {
|
|
|
21
21
|
return spellings;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* A
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* reported as they are, because core cannot copy them meaningfully. The graph reads it for a
|
|
28
|
-
* declared value and the chain reads it for the request one middleware holds.
|
|
24
|
+
* A declared default is wrapped, so `default: undefined` reads apart from no default at all. The
|
|
25
|
+
* value is the frozen snapshot the declaring call took, the one a run validates, so inspection
|
|
26
|
+
* copies nothing and every reader sees one value.
|
|
29
27
|
*/
|
|
30
|
-
export function snapshot(value) {
|
|
31
|
-
if (Array.isArray(value)) {
|
|
32
|
-
return Object.freeze(value.map((entry) => snapshot(entry)));
|
|
33
|
-
}
|
|
34
|
-
if (isPlainObject(value)) {
|
|
35
|
-
return snapshotRecord(value);
|
|
36
|
-
}
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
/** The snapshot of one plain object, under the record type the caller already established. */
|
|
40
|
-
function snapshotRecord(value) {
|
|
41
|
-
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, snapshot(entry)])));
|
|
42
|
-
}
|
|
43
|
-
/** A declared default is wrapped, so `default: undefined` reads apart from no default at all. */
|
|
44
28
|
function declaredDefault(config) {
|
|
45
|
-
return 'default' in config ? Object.freeze({ value:
|
|
29
|
+
return 'default' in config ? Object.freeze({ value: config.default }) : undefined;
|
|
46
30
|
}
|
|
47
31
|
/**
|
|
48
32
|
* The JSON Schema draft build asks every converter for, with no library options. Every converter
|
package/dist/options.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ type OptionSpelling = OptionForm & {
|
|
|
35
35
|
export declare function spellingMark(site: InputSite, role: SpellingRole): string;
|
|
36
36
|
/** The declared name answers the declared-name rule an argument's name answers. */
|
|
37
37
|
export declare function checkOptionName(name: unknown, site: InputSite): void;
|
|
38
|
+
/** Whether a declared short alias is one ASCII letter, the rule every short spelling answers. */
|
|
39
|
+
export declare function isShortAlias(short: unknown): boolean;
|
|
40
|
+
/** The sentence and correction of the short-alias fault for the option `subject` names. */
|
|
41
|
+
export declare function shortAliasText(subject: string): {
|
|
42
|
+
sentence: string;
|
|
43
|
+
correction: string;
|
|
44
|
+
};
|
|
38
45
|
/**
|
|
39
46
|
* The scope one table compiles: the phrase a repeated name names it by, and where each of its
|
|
40
47
|
* options was declared, which every fault's findings rebuild.
|
package/dist/options.js
CHANGED
|
@@ -36,15 +36,21 @@ export function checkOptionName(name, site) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/** Whether a declared short alias is one ASCII letter, the rule every short spelling answers. */
|
|
40
|
+
export function isShortAlias(short) {
|
|
41
|
+
return typeof short === 'string' && /^[A-Za-z]$/u.test(short);
|
|
42
|
+
}
|
|
43
|
+
/** The sentence and correction of the short-alias fault for the option `subject` names. */
|
|
44
|
+
export function shortAliasText(subject) {
|
|
45
|
+
return {
|
|
46
|
+
correction: 'Supply one ASCII letter.',
|
|
47
|
+
sentence: `${subject} declares a short alias that is not one ASCII letter.`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
39
50
|
/** The short alias, and `shortOnly`, which leaves the option that alias alone. */
|
|
40
51
|
function checkShortForms(config, site, subject) {
|
|
41
|
-
if (config.short !== undefined &&
|
|
42
|
-
(
|
|
43
|
-
throw factFault(shortAlias, site, {
|
|
44
|
-
correction: 'Supply one ASCII letter.',
|
|
45
|
-
fact: 'short',
|
|
46
|
-
sentence: `${subject} declares a short alias that is not one ASCII letter.`,
|
|
47
|
-
});
|
|
52
|
+
if (config.short !== undefined && !isShortAlias(config.short)) {
|
|
53
|
+
throw factFault(shortAlias, site, { ...shortAliasText(subject), fact: 'short' });
|
|
48
54
|
}
|
|
49
55
|
if (config.shortOnly !== undefined && typeof config.shortOnly !== 'boolean') {
|
|
50
56
|
throw flagFault(site, 'shortOnly');
|
package/dist/plain.d.ts
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one declaring call with verdicts of its own, dropped when it returns or throws. A call made
|
|
3
|
+
* inside another, such as a `plugin()` a getter makes, is part of the outer read and shares them.
|
|
4
|
+
*/
|
|
5
|
+
declare function declaring<Result>(call: () => Result): Result;
|
|
1
6
|
/**
|
|
2
7
|
* A structural value core reads as plain data: an object literal, and never a declaration that
|
|
3
8
|
* carries state of its own. The options slots read it to reject a value that is not an options
|
|
4
|
-
* object, and
|
|
9
|
+
* object, and `snapshot` reads it to copy a declared value faithfully. A value a declaring call
|
|
10
|
+
* already judged answers with that verdict.
|
|
11
|
+
*/
|
|
12
|
+
declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether a declaring call reads one part of a declaration as plain data, decided once: the first
|
|
15
|
+
* verdict is recorded, and `isPlainObject` answers with it for the same value from then on.
|
|
16
|
+
*/
|
|
17
|
+
declare function decidePlain(value: unknown): value is Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* A snapshot of one value. Arrays and plain objects are copied and frozen to any depth, so a
|
|
20
|
+
* consumer cannot reach the source through the copy. A value that holds itself is copied with the
|
|
21
|
+
* same cycle, because each source object is judged and copied once and every path to it reaches
|
|
22
|
+
* that copy. Primitives and library objects, such as a class instance or a `Date` a schema
|
|
23
|
+
* produced, are reported as they are, because core cannot copy them meaningfully. Build reads it
|
|
24
|
+
* for a converter's schema, and the chain for the request one middleware holds.
|
|
25
|
+
*/
|
|
26
|
+
declare function snapshot(value: unknown): unknown;
|
|
27
|
+
/** The snapshot of one plain object, under the record type the caller already established. */
|
|
28
|
+
declare function snapshotRecord(value: Record<string, unknown>): Readonly<Record<string, unknown>>;
|
|
29
|
+
/**
|
|
30
|
+
* The snapshot `snapshot` takes, of a value whose paths may hold at most `levels` arrays and plain
|
|
31
|
+
* objects, the value itself included. Every path through a container the value holds twice counts
|
|
32
|
+
* it, and a value that holds itself has a path without end. Either kind of path past `levels`
|
|
33
|
+
* throws `NestedTooDeepError` before the walk goes deeper than `levels`, so neither the walk nor
|
|
34
|
+
* any later reader of the copy goes deeper either. A declaring call reads it for a declared default.
|
|
35
|
+
*/
|
|
36
|
+
declare function boundedSnapshot(value: unknown, levels: number): unknown;
|
|
37
|
+
/** What `boundedSnapshot` throws for a value with a path longer than its limit. */
|
|
38
|
+
declare class NestedTooDeepError extends Error {
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A copy of every own string key of one object, enumerable or not, each described once and read
|
|
43
|
+
* once, in the order the object lists its keys. `entering` hears each key before it is read, so a
|
|
44
|
+
* read that throws can name it. A symbol key is not copied, because no declaration declares one.
|
|
45
|
+
*/
|
|
46
|
+
declare function copyOwnKeys(declared: object, entering?: (key: string) => void): Record<string, unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* The copy of one part of a declaration that `decidePlain` judges plain, by `copyOwnKeys`. Any other
|
|
49
|
+
* value is answered as it is, so the rule for its slot reports it.
|
|
5
50
|
*/
|
|
6
|
-
|
|
51
|
+
declare function shallowRecord(value: unknown): unknown;
|
|
52
|
+
/** A copy of one list's entries by `fillList`. Each entry is what its factory built, so it is kept. */
|
|
53
|
+
declare function copyList<Entry>(list: readonly Entry[]): Entry[];
|
|
54
|
+
/** The copy of a value that is a list, by `copyList`. Any other value is answered as it is. */
|
|
55
|
+
declare function shallowList(value: unknown): unknown;
|
|
56
|
+
export { boundedSnapshot, copyList, copyOwnKeys, decidePlain, declaring, isPlainObject, NestedTooDeepError, shallowList, shallowRecord, snapshot, snapshotRecord, };
|
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
|
|
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
|
-
|
|
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, };
|
package/dist/plugin-rules.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -59,4 +65,4 @@ declare const renderingPolicyRule: import("./diagnostic-text.js").DiagnosticRule
|
|
|
59
65
|
declare const themeMapping: import("./diagnostic-text.js").DiagnosticRule;
|
|
60
66
|
/** A theme name that a built-in style member already holds. */
|
|
61
67
|
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, };
|
|
68
|
+
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, unreadableDeclaration, };
|
package/dist/plugin-rules.js
CHANGED
|
@@ -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,
|
|
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.',
|
|
@@ -152,4 +161,4 @@ const themeNameTaken = registerRule('@loomcli/core/theme-name-taken', {
|
|
|
152
161
|
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
162
|
headline: 'Theme name taken',
|
|
154
163
|
});
|
|
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, };
|
|
164
|
+
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, 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
|
+
}
|