@loomcli/core 0.5.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.d.ts +18 -2
- package/dist/application.js +302 -75
- package/dist/bindings.d.ts +15 -10
- package/dist/bindings.js +34 -15
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/chain.d.ts +15 -6
- package/dist/chain.js +40 -20
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +65 -23
- package/dist/command.js +734 -236
- package/dist/controls.d.ts +8 -0
- package/dist/controls.js +23 -0
- package/dist/defect.d.ts +18 -0
- package/dist/defect.js +272 -0
- package/dist/developer.d.ts +24 -0
- package/dist/developer.js +52 -0
- package/dist/diagnostic-text.d.ts +81 -0
- package/dist/diagnostic-text.js +283 -0
- package/dist/diagnostic.d.ts +11 -0
- package/dist/diagnostic.js +70 -0
- package/dist/errors.d.ts +108 -24
- package/dist/errors.js +324 -53
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +41 -8
- package/dist/extension.js +142 -57
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +106 -23
- package/dist/globals.d.ts +29 -21
- package/dist/globals.js +117 -46
- package/dist/glyphs.generated.js +1 -1
- 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 +12 -2
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +68 -0
- package/dist/input-rules.js +152 -0
- package/dist/inspect.d.ts +12 -12
- package/dist/inspect.js +92 -48
- package/dist/lanes.js +1 -1
- package/dist/locate.js +4 -4
- package/dist/options.d.ts +30 -2
- package/dist/options.js +143 -46
- package/dist/output.d.ts +9 -2
- package/dist/output.js +18 -2
- package/dist/plain.d.ts +56 -0
- package/dist/plain.js +238 -0
- package/dist/plugin-rules.d.ts +68 -0
- package/dist/plugin-rules.js +164 -0
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +27 -15
- package/dist/plugin.js +429 -144
- package/dist/prototypes.d.ts +7 -0
- package/dist/prototypes.js +29 -0
- package/dist/rendering.d.ts +6 -1
- package/dist/rendering.js +23 -5
- package/dist/rules.d.ts +51 -0
- package/dist/rules.js +115 -0
- package/dist/sequence.js +6 -1
- package/dist/sources.d.ts +6 -4
- package/dist/sources.js +25 -16
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -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 +13 -3
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +69 -7
- package/dist/validation.js +211 -67
- package/dist/view.d.ts +61 -22
- package/dist/view.js +168 -81
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
package/dist/theme.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { Palette } from './style-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* One plugin's theme, read as the palette core resolves semantic styles through. Each fault marks
|
|
4
|
+
* the theme, or the one entry at fault, in `plugin(identity, { theme })`.
|
|
5
|
+
*/
|
|
2
6
|
declare function buildTheme(value: unknown, identity: string): Palette;
|
|
3
7
|
export { buildTheme };
|
package/dist/theme.js
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
|
-
import { DeclarationError } from './errors.js';
|
|
2
|
-
import {
|
|
1
|
+
import { DeclarationError, quoted } from './errors.js';
|
|
2
|
+
import { partFinding, slotSite } from './facts.js';
|
|
3
|
+
import { isPlainObject } from './plain.js';
|
|
4
|
+
import { themeMapping, themeNameTaken } from './plugin-rules.js';
|
|
3
5
|
import { chains, reservedStyleNames } from './style.js';
|
|
6
|
+
/**
|
|
7
|
+
* One plugin's theme, read as the palette core resolves semantic styles through. Each fault marks
|
|
8
|
+
* the theme, or the one entry at fault, in `plugin(identity, { theme })`.
|
|
9
|
+
*/
|
|
4
10
|
function buildTheme(value, identity) {
|
|
11
|
+
const subject = `Plugin ${quoted(identity)}`;
|
|
12
|
+
const site = slotSite({ call: 'plugin', named: identity, subject }, 'theme', value);
|
|
5
13
|
if (!isPlainObject(value)) {
|
|
6
|
-
throw new DeclarationError(
|
|
14
|
+
throw new DeclarationError(themeMapping, {
|
|
15
|
+
correction: 'Supply a mapping of names to concrete style chains.',
|
|
16
|
+
findings: [partFinding(site, [])],
|
|
17
|
+
sentence: `${subject} declares a theme that is not a mapping.`,
|
|
18
|
+
});
|
|
7
19
|
}
|
|
8
20
|
const palette = new Map();
|
|
9
21
|
for (const [name, chain] of Object.entries(value)) {
|
|
10
22
|
if (reservedStyleNames.has(name)) {
|
|
11
|
-
throw new DeclarationError(
|
|
23
|
+
throw new DeclarationError(themeNameTaken, {
|
|
24
|
+
correction: 'Rename the theme entry.',
|
|
25
|
+
findings: [partFinding(site, [name])],
|
|
26
|
+
sentence: `${subject} theme name ${quoted(name)} shadows a built-in style member.`,
|
|
27
|
+
});
|
|
12
28
|
}
|
|
13
29
|
const operations = typeof chain === 'function' ? chains.get(chain) : undefined;
|
|
14
30
|
if (chain !== undefined &&
|
|
15
31
|
(operations === undefined || operations.some((entry) => entry[0] === 'token'))) {
|
|
16
|
-
throw new DeclarationError(
|
|
32
|
+
throw new DeclarationError(themeMapping, {
|
|
33
|
+
correction: 'Map the name to a concrete chain such as style.cyan.bold, without calling it or naming a semantic style.',
|
|
34
|
+
findings: [partFinding(site, [name])],
|
|
35
|
+
sentence: `${subject} theme mapping ${quoted(name)} is not an unapplied concrete style chain without semantic tokens.`,
|
|
36
|
+
});
|
|
17
37
|
}
|
|
18
38
|
palette.set(name, operations ?? []);
|
|
19
39
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether one value is a promise or another thenable. A thenable object and a promise from another
|
|
3
|
+
* realm are as unwaitable to a synchronous caller as a native one, so the test is the contract and
|
|
4
|
+
* not the class, and a function with a callable `then` counts, as Promises/A+ says. A value whose
|
|
5
|
+
* `then` cannot be read, such as a proxy whose trap throws, is not a thenable, so the test itself
|
|
6
|
+
* never throws.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Attaches a rejection handler to a thenable a synchronous function returned, and otherwise
|
|
11
|
+
* ignores it. An unobserved rejection would end the process before the run could report anything.
|
|
12
|
+
* The thenable is adopted inside a fresh promise, so a `then` that throws rejects that promise
|
|
13
|
+
* instead of throwing here.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ignoreRejection(value: PromiseLike<unknown>): void;
|
package/dist/thenable.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether one value is a promise or another thenable. A thenable object and a promise from another
|
|
3
|
+
* realm are as unwaitable to a synchronous caller as a native one, so the test is the contract and
|
|
4
|
+
* not the class, and a function with a callable `then` counts, as Promises/A+ says. A value whose
|
|
5
|
+
* `then` cannot be read, such as a proxy whose trap throws, is not a thenable, so the test itself
|
|
6
|
+
* never throws.
|
|
7
|
+
*/
|
|
8
|
+
export function isThenable(value) {
|
|
9
|
+
try {
|
|
10
|
+
return ((typeof value === 'object' || typeof value === 'function') &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
'then' in value &&
|
|
13
|
+
typeof value.then === 'function');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attaches a rejection handler to a thenable a synchronous function returned, and otherwise
|
|
21
|
+
* ignores it. An unobserved rejection would end the process before the run could report anything.
|
|
22
|
+
* The thenable is adopted inside a fresh promise, so a `then` that throws rejects that promise
|
|
23
|
+
* instead of throwing here.
|
|
24
|
+
*/
|
|
25
|
+
export function ignoreRejection(value) {
|
|
26
|
+
void new Promise((resolve) => {
|
|
27
|
+
resolve(value);
|
|
28
|
+
}).catch(() => undefined);
|
|
29
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { LoomError } from './errors.js';
|
|
2
|
+
import type { FactSite } from './facts.js';
|
|
3
|
+
/** Phantom key. It brands a translation and holds no runtime value. */
|
|
4
|
+
declare const translation: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* A class a translation is keyed on, such as `SyntaxError`. Its instance type is the type the
|
|
7
|
+
* translator receives, so the function needs no narrowing of its own.
|
|
8
|
+
*/
|
|
9
|
+
type ErrorClass<Thrown extends object> = abstract new (...args: never[]) => Thrown;
|
|
10
|
+
/**
|
|
11
|
+
* A function that turns one foreign throw into one of the author's failures, or answers
|
|
12
|
+
* `undefined` to pass the throw on to the next translator.
|
|
13
|
+
*/
|
|
14
|
+
type Translator<Thrown extends object> = (error: Thrown) => LoomError | undefined;
|
|
15
|
+
/** One translation as core reads it back: the key's prototype, its name, and the typed call. */
|
|
16
|
+
interface TranslationRecord {
|
|
17
|
+
/** The object a thrown value's prototype chain holds when the key's class constructed it. */
|
|
18
|
+
prototype: object;
|
|
19
|
+
/** The key's name, which a broken translator's diagnostic reports. */
|
|
20
|
+
name: string;
|
|
21
|
+
/**
|
|
22
|
+
* The translator, typed at `translate()` against its key. It answers `undefined` without calling
|
|
23
|
+
* the translator for a value its key does not claim as an instance.
|
|
24
|
+
*/
|
|
25
|
+
offer: (thrown: object) => unknown;
|
|
26
|
+
}
|
|
27
|
+
/** The runtime value `translate()` returns. Its record lives in the registry above. */
|
|
28
|
+
declare class TranslationDeclaration {
|
|
29
|
+
readonly [translation]: true;
|
|
30
|
+
constructor(record: TranslationRecord);
|
|
31
|
+
}
|
|
32
|
+
/** An opaque translation pairing one error class with the translator that answers it. */
|
|
33
|
+
type Translation = Pick<TranslationDeclaration, typeof translation>;
|
|
34
|
+
/**
|
|
35
|
+
* Pairs an error class with the translator that turns its instances into a failure. The
|
|
36
|
+
* translator receives the thrown instance typed from the class, and a `translators` list on the
|
|
37
|
+
* Application or a plugin registers the pair.
|
|
38
|
+
*/
|
|
39
|
+
declare function translate<Thrown extends object>(key: ErrorClass<Thrown>, translator: Translator<Thrown>): Translation;
|
|
40
|
+
/**
|
|
41
|
+
* One contributor's translations: how a diagnostic names it, and its records by key prototype,
|
|
42
|
+
* each list in the order the contributor listed it.
|
|
43
|
+
*/
|
|
44
|
+
interface TranslationContributor {
|
|
45
|
+
/** The contributor as a diagnostic's subject, such as `the Application` or `plugin "@acme/http"`. */
|
|
46
|
+
subject: string;
|
|
47
|
+
byPrototype: ReadonlyMap<object, readonly TranslationRecord[]>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Every contributor in resolution order: the application's translations, then each installed
|
|
51
|
+
* plugin's in installation order.
|
|
52
|
+
*/
|
|
53
|
+
type TranslatorRegistry = readonly TranslationContributor[];
|
|
54
|
+
/**
|
|
55
|
+
* One contributor's `translators` list. `site` names the contributor at the start of a sentence,
|
|
56
|
+
* `The Application` or `Plugin "@acme/http"`, and holds the call that declared the list, which a
|
|
57
|
+
* fault marks. The slot is read defensively, because a JavaScript author reaches it with any value.
|
|
58
|
+
*/
|
|
59
|
+
declare function readTranslations(site: FactSite, declared: unknown): TranslationContributor;
|
|
60
|
+
/**
|
|
61
|
+
* The failure the translators answer for one foreign throw, or `undefined` when the throw is never
|
|
62
|
+
* offered or every translator passed. Resolution follows the override walk: each contributor in
|
|
63
|
+
* order, the thrown value's chain walked in full at each, most derived first, and within one
|
|
64
|
+
* class the list order. A broken translator ends the walk with its defect, so no later translator
|
|
65
|
+
* hides it.
|
|
66
|
+
*/
|
|
67
|
+
declare function translateThrow(registry: TranslatorRegistry, thrown: unknown): LoomError | undefined;
|
|
68
|
+
export type { ErrorClass, TranslationContributor, Translation, Translator, TranslatorRegistry };
|
|
69
|
+
export { readTranslations, translate, translateThrow };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { spelled } from './diagnostic-text.js';
|
|
2
|
+
import { asSentence, DeclarationError, InternalError, LoomError, quoted, reasonOf, } from './errors.js';
|
|
3
|
+
import { partFinding } from './facts.js';
|
|
4
|
+
import { foreignValue, notAFunction, notAList, translationKey } from './plugin-rules.js';
|
|
5
|
+
import { prototypeChain } from './prototypes.js';
|
|
6
|
+
import { brokenTranslator, brokenTranslatorCorrection } from './rules.js';
|
|
7
|
+
import { ignoreRejection, isThenable } from './thenable.js';
|
|
8
|
+
/** Authored translations register here, so the public type publishes nothing to reach. */
|
|
9
|
+
const records = new WeakMap();
|
|
10
|
+
/** The runtime value `translate()` returns. Its record lives in the registry above. */
|
|
11
|
+
class TranslationDeclaration {
|
|
12
|
+
constructor(record) {
|
|
13
|
+
records.set(this, record);
|
|
14
|
+
Object.freeze(this);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The prototype a class key carries, or `undefined` for a value that is not a class. A class is a
|
|
19
|
+
* function whose `prototype` is the object its instances' chains hold, so an arrow function, a
|
|
20
|
+
* bound function, and every non-function are no key at all. A key whose `prototype` cannot be
|
|
21
|
+
* read, such as a proxy whose trap throws, is no key either.
|
|
22
|
+
*/
|
|
23
|
+
function keyPrototype(key) {
|
|
24
|
+
let prototype = undefined;
|
|
25
|
+
try {
|
|
26
|
+
if (typeof key !== 'function' || !('prototype' in key)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
prototype = key.prototype;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return typeof prototype === 'object' && prototype !== null ? prototype : undefined;
|
|
35
|
+
}
|
|
36
|
+
/** A key's own name, or `undefined` when it has none that can be read. */
|
|
37
|
+
function nameOf(key) {
|
|
38
|
+
let name = undefined;
|
|
39
|
+
try {
|
|
40
|
+
name = 'name' in key ? key.name : undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
name = undefined;
|
|
44
|
+
}
|
|
45
|
+
return typeof name === 'string' && name !== '' ? name : undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How a diagnostic names one key class: its name, quoted and escaped, or what it is when it has no
|
|
49
|
+
* name that can be read.
|
|
50
|
+
*/
|
|
51
|
+
function keyName(key) {
|
|
52
|
+
const name = nameOf(key);
|
|
53
|
+
return name === undefined ? 'an anonymous class' : quoted(name);
|
|
54
|
+
}
|
|
55
|
+
/** A name JavaScript source can spell as an identifier, which a finding prints a class key as. */
|
|
56
|
+
const identifier = /^[A-Za-z_$][\w$]*$/u;
|
|
57
|
+
/**
|
|
58
|
+
* The finding for one `translate()` call, marking the argument at `mark`. A class key prints as
|
|
59
|
+
* its name, as its author wrote it, where any other function prints as an ellipsis.
|
|
60
|
+
*/
|
|
61
|
+
function translateFinding(key, translator, mark) {
|
|
62
|
+
const name = typeof key === 'function' ? nameOf(key) : undefined;
|
|
63
|
+
const code = name !== undefined && identifier.test(name) ? spelled(name) : key;
|
|
64
|
+
return { arguments: [code, translator], call: 'translate', mark };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether a key claims a thrown value as its instance. A value whose chain cannot be read is not
|
|
68
|
+
* claimed, so its translator is never called and is not blamed for the failed read.
|
|
69
|
+
*/
|
|
70
|
+
function claims(thrown, key) {
|
|
71
|
+
try {
|
|
72
|
+
return thrown instanceof key;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Pairs an error class with the translator that turns its instances into a failure. The
|
|
80
|
+
* translator receives the thrown instance typed from the class, and a `translators` list on the
|
|
81
|
+
* Application or a plugin registers the pair.
|
|
82
|
+
*/
|
|
83
|
+
function translate(key, translator) {
|
|
84
|
+
const prototype = keyPrototype(key);
|
|
85
|
+
// A key whose prototype's chain cannot be read, such as through a proxy whose trap throws, is no
|
|
86
|
+
// Key either, so it is reported as a non-class before any failure-class check.
|
|
87
|
+
const chain = prototype === undefined ? undefined : prototypeChain(prototype);
|
|
88
|
+
if (prototype === undefined || chain === undefined) {
|
|
89
|
+
throw new DeclarationError(translationKey, {
|
|
90
|
+
correction: 'Supply an error class, such as SyntaxError.',
|
|
91
|
+
findings: [translateFinding(key, translator, '0')],
|
|
92
|
+
sentence: 'translate() received a key that is not a class.',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Core never offers a failure to translators, so a translation keyed on a failure class never runs.
|
|
96
|
+
if (prototype === LoomError.prototype || chain.includes(LoomError.prototype)) {
|
|
97
|
+
throw new DeclarationError(translationKey, {
|
|
98
|
+
correction: 'Key the translation on the foreign class it replaces.',
|
|
99
|
+
findings: [translateFinding(key, translator, '0')],
|
|
100
|
+
sentence: 'translate() received a failure class as its key.',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (typeof translator !== 'function') {
|
|
104
|
+
throw new DeclarationError(notAFunction, {
|
|
105
|
+
correction: 'Supply a function that returns a failure or undefined.',
|
|
106
|
+
findings: [translateFinding(key, translator, '1')],
|
|
107
|
+
sentence: 'translate() received a translator that is not a function.',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return new TranslationDeclaration({
|
|
111
|
+
name: keyName(key),
|
|
112
|
+
offer: (thrown) => (claims(thrown, key) ? translator(thrown) : undefined),
|
|
113
|
+
prototype,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* One contributor's `translators` list. `site` names the contributor at the start of a sentence,
|
|
118
|
+
* `The Application` or `Plugin "@acme/http"`, and holds the call that declared the list, which a
|
|
119
|
+
* fault marks. The slot is read defensively, because a JavaScript author reaches it with any value.
|
|
120
|
+
*/
|
|
121
|
+
function readTranslations(site, declared) {
|
|
122
|
+
const sentence = site.subject;
|
|
123
|
+
const byPrototype = new Map();
|
|
124
|
+
for (const record of translationRecords(site, declared)) {
|
|
125
|
+
const listed = byPrototype.get(record.prototype) ?? [];
|
|
126
|
+
listed.push(record);
|
|
127
|
+
byPrototype.set(record.prototype, listed);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
byPrototype,
|
|
131
|
+
subject: `${sentence.slice(0, 1).toLowerCase()}${sentence.slice(1)}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** The fix a `translators` slot's list fault and entry fault share. */
|
|
135
|
+
const translationSupply = 'translate(ErrorClass, translator)';
|
|
136
|
+
/** The record behind each entry of one `translators` slot, whose every other value is its fault. */
|
|
137
|
+
function translationRecords(site, declared) {
|
|
138
|
+
if (declared !== undefined && !Array.isArray(declared)) {
|
|
139
|
+
throw new DeclarationError(notAList, {
|
|
140
|
+
correction: `Supply a list of values returned by ${translationSupply}.`,
|
|
141
|
+
findings: [partFinding(site, [])],
|
|
142
|
+
sentence: `${site.subject} declares translators that are not an array.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const list = declared ?? [];
|
|
146
|
+
// Array.from visits a hole as undefined, where map would skip it and leave the hole in place.
|
|
147
|
+
return Array.from(list, (entry, index) => {
|
|
148
|
+
const record = typeof entry === 'object' && entry !== null ? records.get(entry) : undefined;
|
|
149
|
+
if (!record) {
|
|
150
|
+
throw new DeclarationError(foreignValue, {
|
|
151
|
+
correction: `Supply the value returned by ${translationSupply}.`,
|
|
152
|
+
findings: [partFinding(site, [index])],
|
|
153
|
+
sentence: `${site.subject} holds a translator entry that is not a translation.`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return record;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Every prototype in one thrown value's chain, most derived first, or `undefined` when the value is
|
|
161
|
+
* never offered: a value whose chain holds a failure class, and a value whose chain cannot be read,
|
|
162
|
+
* such as a proxy whose trap throws, or whose chain repeats a link or never ends. A value with a
|
|
163
|
+
* `null` prototype has an empty chain, which no key matches.
|
|
164
|
+
*/
|
|
165
|
+
function offeredChain(thrown) {
|
|
166
|
+
const chain = prototypeChain(thrown);
|
|
167
|
+
return chain?.includes(LoomError.prototype) ? undefined : chain;
|
|
168
|
+
}
|
|
169
|
+
/** How a broken translator's diagnostic names a value that is not a failure. */
|
|
170
|
+
function returnedKind(value) {
|
|
171
|
+
if (value === null) {
|
|
172
|
+
return 'null';
|
|
173
|
+
}
|
|
174
|
+
if (isThenable(value)) {
|
|
175
|
+
return 'a promise';
|
|
176
|
+
}
|
|
177
|
+
const kind = typeof value;
|
|
178
|
+
return /^[aeiou]/u.test(kind) ? `an ${kind}` : `a ${kind}`;
|
|
179
|
+
}
|
|
180
|
+
/** Whether a translator's answer is a failure, read without letting a hostile value throw. */
|
|
181
|
+
function isFailure(value) {
|
|
182
|
+
try {
|
|
183
|
+
return value instanceof LoomError;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** The defect of a broken translator under its rule, with the sentence and cause each shape gives. */
|
|
190
|
+
function brokenDefect(sentence, cause) {
|
|
191
|
+
return new InternalError(brokenTranslator, {
|
|
192
|
+
cause,
|
|
193
|
+
correction: brokenTranslatorCorrection,
|
|
194
|
+
sentence,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The defect of a translator that threw. Its cause is a new `AggregateError` that holds the
|
|
199
|
+
* translator's throw and then the original throw, so neither is lost and neither is mutated. The
|
|
200
|
+
* reason is read through `reasonOf`, which escapes it onto one line.
|
|
201
|
+
*/
|
|
202
|
+
function threwDefect(who, error, thrown) {
|
|
203
|
+
const reason = reasonOf(error);
|
|
204
|
+
const cause = new AggregateError([error, thrown], 'The translator threw while translating the original throw.');
|
|
205
|
+
return brokenDefect(`${who} threw: ${asSentence(reason)}`, cause);
|
|
206
|
+
}
|
|
207
|
+
/** The one translator call, whose throw or non-failure answer is the defect of that translator. */
|
|
208
|
+
function consult(contributor, record, thrown) {
|
|
209
|
+
const who = `The translator ${contributor.subject} registered for ${record.name}`;
|
|
210
|
+
let answer = undefined;
|
|
211
|
+
try {
|
|
212
|
+
answer = record.offer(thrown);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return threwDefect(who, error, thrown);
|
|
216
|
+
}
|
|
217
|
+
if (answer === undefined || isFailure(answer)) {
|
|
218
|
+
return answer;
|
|
219
|
+
}
|
|
220
|
+
// A translator is synchronous, so a returned promise is ignored once its rejection is observed.
|
|
221
|
+
if (isThenable(answer)) {
|
|
222
|
+
ignoreRejection(answer);
|
|
223
|
+
}
|
|
224
|
+
return brokenDefect(`${who} returned ${returnedKind(answer)} instead of a failure.`, thrown);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The failure the translators answer for one foreign throw, or `undefined` when the throw is never
|
|
228
|
+
* offered or every translator passed. Resolution follows the override walk: each contributor in
|
|
229
|
+
* order, the thrown value's chain walked in full at each, most derived first, and within one
|
|
230
|
+
* class the list order. A broken translator ends the walk with its defect, so no later translator
|
|
231
|
+
* hides it.
|
|
232
|
+
*/
|
|
233
|
+
function translateThrow(registry, thrown) {
|
|
234
|
+
if ((typeof thrown !== 'object' && typeof thrown !== 'function') || thrown === null) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
// With no translation registered, the chain is never read.
|
|
238
|
+
const chain = registry.some((contributor) => contributor.byPrototype.size > 0)
|
|
239
|
+
? (offeredChain(thrown) ?? [])
|
|
240
|
+
: [];
|
|
241
|
+
for (const contributor of registry) {
|
|
242
|
+
for (const prototype of chain) {
|
|
243
|
+
for (const record of contributor.byPrototype.get(prototype) ?? []) {
|
|
244
|
+
const answer = consult(contributor, record, thrown);
|
|
245
|
+
if (answer !== undefined) {
|
|
246
|
+
return answer;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
export { readTranslations, translate, translateThrow };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" preserve="true" />
|
|
2
2
|
import type { Readable, Writable } from 'node:stream';
|
|
3
3
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
+
import type { FailureExitCode } from './exit-codes.js';
|
|
4
5
|
import type { ExtensionValue } from './extension.js';
|
|
5
6
|
import type { CommandGraph, CommandNode, ResultNode } from './inspect.js';
|
|
6
7
|
import type { RenderingPolicy } from './rendering.js';
|
|
@@ -101,7 +102,8 @@ export type GlobalNameConstraint<Name extends string, Globals> = Name extends ke
|
|
|
101
102
|
} : unknown;
|
|
102
103
|
/** A required record key excludes open strings; distribution rejects each union member. */
|
|
103
104
|
export type NameConstraint<Name extends string, Whole extends string = Name> = {} extends Record<Name, unknown> ? LiteralNameFault : Name extends Whole ? [Whole] extends [Name] ? unknown : LiteralNameFault : LiteralNameFault;
|
|
104
|
-
|
|
105
|
+
/** The status `run()` resolves: success, a failure class's code, or a cancellation code. */
|
|
106
|
+
export type ExitCode = 0 | FailureExitCode | 130 | 143;
|
|
105
107
|
export interface InputTerminal {
|
|
106
108
|
isTTY: boolean;
|
|
107
109
|
}
|
|
@@ -122,6 +124,14 @@ export interface Host {
|
|
|
122
124
|
stdin: Readable;
|
|
123
125
|
stdout: Writable;
|
|
124
126
|
stderr: Writable;
|
|
127
|
+
/**
|
|
128
|
+
* Reads one source file for a defect's Developer Diagnostic, or answers `undefined`. Core calls
|
|
129
|
+
* it only in a development build, only while it reports a defect, and only for a path under the
|
|
130
|
+
* working directory. Process capture supplies a reader that refuses a file outside `cwd` after
|
|
131
|
+
* resolving symbolic links, and answers `undefined` for a path that is not a regular file or
|
|
132
|
+
* that exceeds 1 MiB.
|
|
133
|
+
*/
|
|
134
|
+
readSource?: (path: string, cwd: string) => string | undefined;
|
|
125
135
|
}
|
|
126
136
|
export interface RunOptions {
|
|
127
137
|
rendering?: RenderingPolicy;
|
|
@@ -175,7 +185,7 @@ export interface ViewContext {
|
|
|
175
185
|
export interface View<Data> {
|
|
176
186
|
render: (data: Readonly<Data>, context: ViewContext) => string;
|
|
177
187
|
/** A view has one shape; the row view of Results is the other. */
|
|
178
|
-
row?:
|
|
188
|
+
row?: undefined;
|
|
179
189
|
}
|
|
180
190
|
/**
|
|
181
191
|
* A row view renders a sequence one row at a time.
|
|
@@ -187,7 +197,7 @@ export interface RowView<Row> {
|
|
|
187
197
|
head?: (context: ViewContext) => string;
|
|
188
198
|
tail?: (count: number, context: ViewContext) => string;
|
|
189
199
|
/** A row view has one shape; the whole view of Rendered output is the other. */
|
|
190
|
-
render?:
|
|
200
|
+
render?: undefined;
|
|
191
201
|
}
|
|
192
202
|
/** The views record of a value result: every entry renders the whole value. */
|
|
193
203
|
export type ResultViews<Value> = Readonly<Record<string, View<Value>>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
The tables derive from the Unicode Character Database.
|
|
3
|
+
Copyright © 1991-2025 Unicode, Inc. Licensed under the Unicode License v3,
|
|
4
|
+
in licenses/unicode-LICENSE.txt of @loomcli/core.
|
|
5
|
+
|
|
6
|
+
They are generated from the tables of @rockorager/uucode.
|
|
7
|
+
Copyright (c) 2026 Tim Culverhouse. Licensed under the MIT License,
|
|
8
|
+
in licenses/uucode-LICENSE.md of @loomcli/core.
|
|
9
|
+
*/
|
|
10
|
+
/** The grapheme break state machine, indexed by state and two break properties. */
|
|
11
|
+
export declare const graphemeTable: {
|
|
12
|
+
breakStateCount: number;
|
|
13
|
+
breakTable: Uint8Array<ArrayBuffer>;
|
|
14
|
+
graphemeBreakPropertyCount: number;
|
|
15
|
+
};
|
|
16
|
+
/** Each code point's width and grapheme break property, in a three-stage lookup. */
|
|
17
|
+
export declare const widthTable: {
|
|
18
|
+
emojiVSFlag: number;
|
|
19
|
+
maxCodePoint: number;
|
|
20
|
+
stage1: Uint16Array<ArrayBuffer>;
|
|
21
|
+
stage1Shift: number;
|
|
22
|
+
stage2: Uint8Array<ArrayBuffer>;
|
|
23
|
+
stage2Mask: number;
|
|
24
|
+
stage3: Uint16Array<ArrayBuffer>;
|
|
25
|
+
widthMask: number;
|
|
26
|
+
zeroWidthFlag: number;
|
|
27
|
+
};
|