@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.
Files changed (90) hide show
  1. package/NOTICE +34 -0
  2. package/dist/application.d.ts +18 -2
  3. package/dist/application.js +302 -75
  4. package/dist/bindings.d.ts +15 -10
  5. package/dist/bindings.js +34 -15
  6. package/dist/capture.d.ts +65 -0
  7. package/dist/capture.js +99 -0
  8. package/dist/chain.d.ts +15 -6
  9. package/dist/chain.js +40 -20
  10. package/dist/command-rules.d.ts +55 -0
  11. package/dist/command-rules.js +142 -0
  12. package/dist/command.d.ts +65 -23
  13. package/dist/command.js +734 -236
  14. package/dist/controls.d.ts +8 -0
  15. package/dist/controls.js +23 -0
  16. package/dist/defect.d.ts +18 -0
  17. package/dist/defect.js +272 -0
  18. package/dist/developer.d.ts +24 -0
  19. package/dist/developer.js +52 -0
  20. package/dist/diagnostic-text.d.ts +81 -0
  21. package/dist/diagnostic-text.js +283 -0
  22. package/dist/diagnostic.d.ts +11 -0
  23. package/dist/diagnostic.js +70 -0
  24. package/dist/errors.d.ts +108 -24
  25. package/dist/errors.js +324 -53
  26. package/dist/exit-codes.d.ts +45 -0
  27. package/dist/exit-codes.js +46 -0
  28. package/dist/extension.d.ts +41 -8
  29. package/dist/extension.js +142 -57
  30. package/dist/facts.d.ts +71 -11
  31. package/dist/facts.js +106 -23
  32. package/dist/globals.d.ts +29 -21
  33. package/dist/globals.js +117 -46
  34. package/dist/glyphs.generated.js +1 -1
  35. package/dist/hints.d.ts +79 -0
  36. package/dist/hints.js +247 -0
  37. package/dist/host.d.ts +13 -0
  38. package/dist/host.js +43 -1
  39. package/dist/identity.d.ts +19 -0
  40. package/dist/identity.js +72 -0
  41. package/dist/index.d.ts +12 -2
  42. package/dist/index.js +6 -0
  43. package/dist/input-rules.d.ts +68 -0
  44. package/dist/input-rules.js +152 -0
  45. package/dist/inspect.d.ts +12 -12
  46. package/dist/inspect.js +92 -48
  47. package/dist/lanes.js +1 -1
  48. package/dist/locate.js +4 -4
  49. package/dist/options.d.ts +30 -2
  50. package/dist/options.js +143 -46
  51. package/dist/output.d.ts +9 -2
  52. package/dist/output.js +18 -2
  53. package/dist/plain.d.ts +56 -0
  54. package/dist/plain.js +238 -0
  55. package/dist/plugin-rules.d.ts +68 -0
  56. package/dist/plugin-rules.js +164 -0
  57. package/dist/plugin-settings.d.ts +18 -0
  58. package/dist/plugin-settings.js +38 -0
  59. package/dist/plugin.d.ts +27 -15
  60. package/dist/plugin.js +429 -144
  61. package/dist/prototypes.d.ts +7 -0
  62. package/dist/prototypes.js +29 -0
  63. package/dist/rendering.d.ts +6 -1
  64. package/dist/rendering.js +23 -5
  65. package/dist/rules.d.ts +51 -0
  66. package/dist/rules.js +115 -0
  67. package/dist/sequence.js +6 -1
  68. package/dist/sources.d.ts +6 -4
  69. package/dist/sources.js +25 -16
  70. package/dist/style-layout.js +2 -2
  71. package/dist/style-width.d.ts +13 -0
  72. package/dist/style-width.js +170 -0
  73. package/dist/style-wire.js +1 -1
  74. package/dist/style.js +1 -1
  75. package/dist/theme.d.ts +4 -0
  76. package/dist/theme.js +25 -5
  77. package/dist/thenable.d.ts +15 -0
  78. package/dist/thenable.js +29 -0
  79. package/dist/translators.d.ts +69 -0
  80. package/dist/translators.js +253 -0
  81. package/dist/types.d.ts +13 -3
  82. package/dist/unicode.generated.d.ts +27 -0
  83. package/dist/unicode.generated.js +1036 -0
  84. package/dist/validation.d.ts +69 -7
  85. package/dist/validation.js +211 -67
  86. package/dist/view.d.ts +61 -22
  87. package/dist/view.js +168 -81
  88. package/licenses/unicode-LICENSE.txt +41 -0
  89. package/licenses/uucode-LICENSE.md +35 -0
  90. package/package.json +9 -5
@@ -0,0 +1,283 @@
1
+ import { escapeControlCharacters } from './controls.js';
2
+ import { isPlainObject } from './plain.js';
3
+ /** The width a diagnostic takes in a thrown error's message, or when the stderr width is unknown. */
4
+ const messageWidth = 80;
5
+ /** How far a finding's code sits from the left edge. */
6
+ const findingIndent = ' ';
7
+ /** How deep a finding's argument is printed before the rest reads as an ellipsis. */
8
+ const depthLimit = 4;
9
+ /** The stand-in for a value a finding does not spell out: a function, a validator, or a cycle. */
10
+ const elided = '…';
11
+ /** A key an object literal can spell without quotes. */
12
+ const identifierKey = /^[A-Za-z_$][\w$]*$/u;
13
+ /** A string as a single-quoted JavaScript literal, with every control escaped. */
14
+ function quoteString(text) {
15
+ return `'${escapeControlCharacters(text.replaceAll('\\', String.raw `\\`).replaceAll("'", String.raw `\'`))}'`;
16
+ }
17
+ /**
18
+ * Stand-ins a finding prints as the code they name, such as `new Command('get')` for the Command
19
+ * value an attach received, which a finding would otherwise print as an ellipsis.
20
+ */
21
+ const spellings = new WeakMap();
22
+ /** A finding argument that prints as the code given, which the caller has already escaped. */
23
+ function spelled(code) {
24
+ const value = Object.freeze({});
25
+ spellings.set(value, code);
26
+ return value;
27
+ }
28
+ /** The code a stand-in prints as, or `undefined` for any other value. */
29
+ function spellingOf(value) {
30
+ return typeof value === 'object' && value !== null ? spellings.get(value) : undefined;
31
+ }
32
+ /** Whether a value is a Standard Schema, which a finding prints as an ellipsis like a function. */
33
+ function isValidator(value) {
34
+ return '~standard' in value;
35
+ }
36
+ /** A primitive as JavaScript source, or `undefined` for an object or a function. */
37
+ function primitiveCode(value) {
38
+ switch (typeof value) {
39
+ case 'string': {
40
+ return quoteString(value);
41
+ }
42
+ case 'number': {
43
+ return Object.is(value, -0) ? '-0' : String(value);
44
+ }
45
+ case 'bigint': {
46
+ return `${String(value)}n`;
47
+ }
48
+ case 'symbol': {
49
+ return escapeControlCharacters(String(value));
50
+ }
51
+ case 'boolean':
52
+ case 'undefined': {
53
+ return String(value);
54
+ }
55
+ default: {
56
+ return value === null ? 'null' : undefined;
57
+ }
58
+ }
59
+ }
60
+ /** One object key as an object literal spells it. */
61
+ function keyCode(key) {
62
+ return identifierKey.test(key) ? key : quoteString(key);
63
+ }
64
+ /**
65
+ * Printed code in progress, and the span each argument path and key covers, which a mark
66
+ * underlines. Reading a value runs no author code beyond property reads, and a read that throws
67
+ * prints an ellipsis.
68
+ */
69
+ class CodePrinter {
70
+ text;
71
+ spans = new Map();
72
+ constructor(text) {
73
+ this.text = text;
74
+ }
75
+ /** Appends one value and records the span it covers under its path. */
76
+ value(value, place) {
77
+ const start = this.text.length;
78
+ try {
79
+ // Nested members append while the composite is read, so its tail appends after them.
80
+ const tail = this.composite(value, place);
81
+ this.text += tail;
82
+ }
83
+ catch {
84
+ this.text = `${this.text.slice(0, start)}${elided}`;
85
+ }
86
+ this.spans.set(place.path, { end: this.text.length, start });
87
+ }
88
+ /** Appends each item with a comma between, as a list of arguments or members is written. */
89
+ separated(items, write) {
90
+ for (const [index, item] of items.entries()) {
91
+ if (index > 0) {
92
+ this.text += ', ';
93
+ }
94
+ write(item, index);
95
+ }
96
+ }
97
+ /**
98
+ * The code for one value, appending nested members as it goes and returning what is left to
99
+ * append. A function, a validator, a class instance, a cycle, and a value deeper than the limit
100
+ * print an ellipsis.
101
+ */
102
+ composite(value, { path, seen }) {
103
+ const known = primitiveCode(value) ?? spellingOf(value);
104
+ if (known !== undefined) {
105
+ return known;
106
+ }
107
+ if (typeof value !== 'object' || value === null || seen.has(value) || seen.size >= depthLimit) {
108
+ return elided;
109
+ }
110
+ const inner = { path, seen: new Set([...seen, value]) };
111
+ if (Array.isArray(value)) {
112
+ return this.list(value, inner);
113
+ }
114
+ return isValidator(value) || !isPlainObject(value) ? elided : this.record(value, inner);
115
+ }
116
+ /** One array literal. Its members append in place, so each records its own span. */
117
+ list(list, { path, seen }) {
118
+ this.text += '[';
119
+ // A hole reads as undefined, so the list is copied before it is walked.
120
+ this.separated([...list], (member, index) => {
121
+ this.value(member, { path: `${path}.${String(index)}`, seen });
122
+ });
123
+ return ']';
124
+ }
125
+ /** One object literal. A key's span covers the whole `key: value` pair, which a mark underlines. */
126
+ record(record, place) {
127
+ const keys = Object.keys(record);
128
+ if (keys.length === 0) {
129
+ return '{}';
130
+ }
131
+ this.text += '{ ';
132
+ this.separated(keys, (key) => {
133
+ this.member(key, Reflect.get(record, key), place);
134
+ });
135
+ return ' }';
136
+ }
137
+ /** One `key: value` pair of an object literal. */
138
+ member(key, value, { path, seen }) {
139
+ const start = this.text.length;
140
+ const member = `${path}.${key}`;
141
+ this.text += `${keyCode(key)}: `;
142
+ this.value(value, { path: member, seen });
143
+ this.spans.set(member, { end: this.text.length, start });
144
+ }
145
+ }
146
+ /** One call with its arguments, and the span each argument and key covers inside the line. */
147
+ function printCall(prefix, finding) {
148
+ const printer = new CodePrinter(`${prefix}${escapeControlCharacters(finding.call)}(`);
149
+ const seen = new Set();
150
+ printer.separated(finding.arguments, (argument, index) => {
151
+ printer.value(argument, { path: String(index), seen });
152
+ });
153
+ printer.text += ')';
154
+ return printer;
155
+ }
156
+ /** One value as the JavaScript a finding prints for it. */
157
+ function valueCode(value) {
158
+ const printer = new CodePrinter('');
159
+ printer.value(value, { path: '', seen: new Set() });
160
+ return printer.text;
161
+ }
162
+ /** The line of carets under a finding's mark, with its note beside it, or none. */
163
+ function markLine(printed, finding) {
164
+ const span = finding.mark === undefined ? undefined : printed.spans.get(finding.mark);
165
+ if (span === undefined) {
166
+ return [];
167
+ }
168
+ const carets = '^'.repeat(Math.max(1, span.end - span.start));
169
+ const note = finding.note === undefined ? '' : ` ${escapeControlCharacters(finding.note)}`;
170
+ return [`${' '.repeat(span.start)}${carets}${note}`];
171
+ }
172
+ /**
173
+ * The receiver a finding's call sits on: the Command its path names, or the Application for the
174
+ * root, and the comment above it that opens with the path, after the application name when known.
175
+ */
176
+ function receiverLines(path, application) {
177
+ const words = application === undefined ? path : [application, ...path];
178
+ const comment = words.length === 0 ? [] : [`// ${escapeControlCharacters(words.join(' '))}`];
179
+ const last = path.at(-1);
180
+ if (last !== undefined) {
181
+ return [...comment, `new Command(${quoteString(last)})`];
182
+ }
183
+ return [
184
+ ...comment,
185
+ `new Application(${application === undefined ? elided : quoteString(application)})`,
186
+ ];
187
+ }
188
+ /** One finding as indented code: the rebuilt call, and the marks under the part at fault. */
189
+ function findingSection(finding, application) {
190
+ const receiver = finding.path === undefined ? [] : receiverLines(finding.path, application);
191
+ const printed = printCall(finding.path === undefined ? '' : ' .', finding);
192
+ return [...receiver, printed.text, ...markLine(printed, finding)]
193
+ .map((line) => `${findingIndent}${line}`.trimEnd())
194
+ .join('\n');
195
+ }
196
+ /** Prose wrapped to the width at spaces. A word wider than the width keeps a line of its own. */
197
+ function wrap(text, width) {
198
+ const lines = [];
199
+ let line = '';
200
+ for (const word of text.split(' ')) {
201
+ if (line !== '' && line.length + 1 + word.length > width) {
202
+ lines.push(line);
203
+ line = word;
204
+ }
205
+ else {
206
+ line = line === '' ? word : `${line} ${word}`;
207
+ }
208
+ }
209
+ lines.push(line);
210
+ return lines.join('\n');
211
+ }
212
+ /**
213
+ * The sentence with every control escaped. A declaration fault's sentence is written by the author
214
+ * of a rule, so its line breaks, such as the issue lines under a rejected default, stay breaks; a
215
+ * defect's sentence can carry a thrown value's reason, so it stays on one line.
216
+ */
217
+ function sentenceText(anatomy) {
218
+ return anatomy.fallback === 'DEFECT'
219
+ ? escapeControlCharacters(anatomy.sentence)
220
+ : anatomy.sentence.split('\n').map(escapeControlCharacters).join('\n');
221
+ }
222
+ /** The banner: two hyphens, the headline in capitals, a run of hyphens, and the rule's identity. */
223
+ function bannerLine(anatomy, width) {
224
+ const { rule } = anatomy;
225
+ const title = rule === undefined ? anatomy.fallback : rule.headline.toUpperCase();
226
+ const left = `-- ${escapeControlCharacters(title)} `;
227
+ const right = rule === undefined ? '' : ` ${escapeControlCharacters(rule.identity)}`;
228
+ const fill = Math.max(2, width - left.length - right.length);
229
+ return `${left}${'-'.repeat(fill)}${right}`;
230
+ }
231
+ /** The correction: one imperative sentence, or one fix per line when several fit. */
232
+ function correctionSection(correction) {
233
+ if (correction === undefined) {
234
+ return [];
235
+ }
236
+ if (typeof correction === 'string') {
237
+ return [escapeControlCharacters(correction)];
238
+ }
239
+ return correction.length === 0
240
+ ? []
241
+ : [correction.map((fix) => `- ${escapeControlCharacters(fix)}`).join('\n')];
242
+ }
243
+ /**
244
+ * One Developer Diagnostic as plain text with no trailing newline: the banner, the sentence, the
245
+ * findings, the explanation, the correction, and the docs link, separated by one blank line, each
246
+ * left out when the fault does not carry it. The banner is returned apart, so a caller that styles
247
+ * it can.
248
+ */
249
+ function diagnosticSections(anatomy, layout) {
250
+ const { rule } = anatomy;
251
+ const sections = [
252
+ sentenceText(anatomy),
253
+ ...anatomy.findings.map((finding) => findingSection(finding, layout.application)),
254
+ ...anatomy.evidence,
255
+ ...(rule === undefined ? [] : [wrap(escapeControlCharacters(rule.explanation), layout.width)]),
256
+ ...correctionSection(anatomy.correction),
257
+ ...(rule?.docs === undefined ? [] : [`See ${escapeControlCharacters(rule.docs)}`]),
258
+ ];
259
+ return { banner: bannerLine(anatomy, layout.width), body: sections.join('\n\n') };
260
+ }
261
+ /** The whole diagnostic as the plain text a thrown fault's message holds. */
262
+ function diagnosticText(anatomy, layout = { width: messageWidth }) {
263
+ const { banner, body } = diagnosticSections(anatomy, layout);
264
+ return `${banner}\n\n${body}`;
265
+ }
266
+ /** Every descriptor built through `registerRule`, so a hand-built object is not a rule. */
267
+ const rules = new WeakSet();
268
+ /** One frozen descriptor from parts already checked, which every site raising it shares. */
269
+ function registerRule(identity, definition) {
270
+ const rule = Object.freeze({
271
+ docs: definition.docs,
272
+ explanation: definition.explanation,
273
+ headline: definition.headline,
274
+ identity,
275
+ });
276
+ rules.add(rule);
277
+ return rule;
278
+ }
279
+ /** Whether a value is a descriptor `diagnosticRule()` built. */
280
+ function isDiagnosticRule(value) {
281
+ return typeof value === 'object' && value !== null && rules.has(value);
282
+ }
283
+ export { diagnosticSections, diagnosticText, elided, isDiagnosticRule, messageWidth, quoteString, registerRule, spelled, valueCode, wrap, };
@@ -0,0 +1,11 @@
1
+ import type { DiagnosticRule } from './diagnostic-text.js';
2
+ /**
3
+ * Declares one Developer Diagnostic rule: its identity, the headline its banner prints, the
4
+ * explanation that says why the rule exists, and an optional docs link. It returns a frozen
5
+ * descriptor that every site raising the rule shares, as `extension()` and `issueCode()` do.
6
+ */
7
+ export declare function diagnosticRule(identity: string, definition: {
8
+ readonly headline: string;
9
+ readonly explanation: string;
10
+ readonly docs?: string;
11
+ }): DiagnosticRule;
@@ -0,0 +1,70 @@
1
+ import { registerRule } from './diagnostic-text.js';
2
+ import { DeclarationError, quoted } from './errors.js';
3
+ import { isRuleIdentity } from './identity.js';
4
+ import { ruleDocs, ruleIdentity, ruleProse } from './rules.js';
5
+ /** Whether a text holds a character other than whitespace. */
6
+ function isFilled(value) {
7
+ return typeof value === 'string' && value.trim() !== '';
8
+ }
9
+ /** Whether a docs value is an absolute web address. */
10
+ function isWebAddress(value) {
11
+ if (!URL.canParse(value)) {
12
+ return false;
13
+ }
14
+ const { protocol } = new URL(value);
15
+ return protocol === 'https:' || protocol === 'http:';
16
+ }
17
+ /** The definition's own fields, read by shape, because a JavaScript caller reaches the call. */
18
+ function definitionOf(definition) {
19
+ return typeof definition === 'object' && definition !== null ? { ...definition } : {};
20
+ }
21
+ /** The finding for one `diagnosticRule()` call, marking the argument or key at fault. */
22
+ function callFinding(identity, definition, mark) {
23
+ return { arguments: [identity, definition], call: 'diagnosticRule', mark };
24
+ }
25
+ /**
26
+ * The definition's headline, explanation, and docs, checked. A fault marks the key at fault, or the
27
+ * definition itself when the author left the key out.
28
+ */
29
+ function checkDefinition(identity, definition) {
30
+ const fields = definitionOf(definition);
31
+ const { docs, explanation, headline } = fields;
32
+ const part = (key) => callFinding(identity, definition, key in fields ? `1.${key}` : '1');
33
+ if (!isFilled(headline)) {
34
+ throw new DeclarationError(ruleProse, {
35
+ correction: 'Supply a short noun phrase.',
36
+ findings: [part('headline')],
37
+ sentence: `Diagnostic rule ${quoted(identity)} declares an empty headline.`,
38
+ });
39
+ }
40
+ if (!isFilled(explanation)) {
41
+ throw new DeclarationError(ruleProse, {
42
+ correction: 'Supply prose that says why the rule exists.',
43
+ findings: [part('explanation')],
44
+ sentence: `Diagnostic rule ${quoted(identity)} declares an empty explanation.`,
45
+ });
46
+ }
47
+ if (docs !== undefined && (typeof docs !== 'string' || !isWebAddress(docs))) {
48
+ throw new DeclarationError(ruleDocs, {
49
+ correction: 'Supply an absolute https URL, or omit docs.',
50
+ findings: [part('docs')],
51
+ sentence: `Diagnostic rule ${quoted(identity)} declares docs that are not a URL.`,
52
+ });
53
+ }
54
+ return docs === undefined ? { explanation, headline } : { docs, explanation, headline };
55
+ }
56
+ /**
57
+ * Declares one Developer Diagnostic rule: its identity, the headline its banner prints, the
58
+ * explanation that says why the rule exists, and an optional docs link. It returns a frozen
59
+ * descriptor that every site raising the rule shares, as `extension()` and `issueCode()` do.
60
+ */
61
+ export function diagnosticRule(identity, definition) {
62
+ if (!isRuleIdentity(identity)) {
63
+ throw new DeclarationError(ruleIdentity, {
64
+ correction: 'Name it <package>[/<subpath>...]/<kebab-case-rule>, such as "@acme/retry/retry-limit".',
65
+ findings: [callFinding(identity, definition, '0')],
66
+ sentence: `Diagnostic rule ${quoted(identity)} has no package part, or a subpath or rule name that is not kebab-case.`,
67
+ });
68
+ }
69
+ return registerRule(identity, checkDefinition(identity, definition));
70
+ }
package/dist/errors.d.ts CHANGED
@@ -1,9 +1,19 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { DiagnosticParts, DiagnosticRule, Finding } from './diagnostic-text.js';
3
+ import type { FailureExitCode } from './exit-codes.js';
2
4
  import type { InputIdentity } from './types.js';
5
+ /**
6
+ * A value a sentence quotes: raw text an operator typed, or a name or identity an author declared.
7
+ * Text is escaped, so a control character or a bidirectional control in it cannot reorder or break
8
+ * the line. A value of any other kind prints as the code a finding prints for it, because a rule
9
+ * that rejects a value that is not a string quotes that value too. The failure's public field
10
+ * keeps the raw value.
11
+ */
12
+ export declare function quoted(value: unknown): string;
3
13
  /**
4
14
  * The two short-group faults. A value option that is not last in its group names that option's
5
- * spelling; a group that mixes scopes names the whole group and the two letters that disagree.
6
- * The extra letters shape the sentence alone, so `token` and `reason` are the reported facts.
15
+ * spelling; a group that mixes scopes names only the two letters that disagree, because the rest
16
+ * of the group may hold an inline value. `token` keeps the whole group as the reported fact.
7
17
  */
8
18
  type ShortGroupFault = {
9
19
  reason: 'value-position';
@@ -15,32 +25,66 @@ type ShortGroupFault = {
15
25
  other: string;
16
26
  };
17
27
  /**
18
- * Core's own text for one failure: its message under the category prefix its class carries, with
19
- * the trailing newline every view's text carries. The four categories are disjoint branches of the
20
- * hierarchy, so one ordered test reads every class, and a class without a prefix of its own writes
21
- * the sentence alone. It is the default view of every failure class and the text the plain
22
- * fallback path writes, so it runs no application code and nothing downstream composes its newline.
28
+ * The one message a distributed build shows an operator for a defect or a declaration fault: the
29
+ * application name and a fixed phrase, with no reason, class name, code, or path.
30
+ */
31
+ export declare function genericDefectText(application: string): string;
32
+ /**
33
+ * Whether one failure is only the author's to fix: a declaration fault or a defect. A development
34
+ * build shows the author its Developer Diagnostic; a distributed one shows the generic message.
35
+ */
36
+ export declare function isAuthorFault(failure: LoomError): failure is DeclarationError | InternalError;
37
+ /**
38
+ * Core's own text for one failure, with the trailing newline every view's text carries. The
39
+ * application name opens every line of a usage failure's message, one line for each problem it
40
+ * reports, so the operator reads who is speaking on each. A declaration fault and a defect read
41
+ * the generic defect message, because only the author can act on their detail, and a development
42
+ * build shows that detail ahead of every view. Every other class writes its message alone, even
43
+ * one that declares a usage error's exit code. It is the default view of every failure class and
44
+ * the text the plain fallback path writes, so it runs no application code and nothing downstream
45
+ * composes its newline.
23
46
  */
24
- export declare function defaultText(failure: LoomError): string;
47
+ export declare function defaultText(failure: LoomError, application: string): string;
25
48
  /** How a diagnostic names one Command inside a sentence: by name, or as the unnamed root. */
26
49
  export declare function commandSubject(name: string | null): string;
27
50
  /** The routed path names the Command a sentence speaks of; an empty path is the root. */
28
51
  export declare function routedSubject(command: readonly string[]): string;
29
52
  /** The same subject at the start of a sentence. */
30
53
  export declare function commandSentence(name: string | null): string;
54
+ /**
55
+ * The code a failure exits with. A value that inherits from a failure class without having been
56
+ * constructed holds none, and `toFailure` reports it as an internal error, so it reads 1.
57
+ */
58
+ export declare function exitCodeOf(failure: LoomError): FailureExitCode;
31
59
  /**
32
60
  * Every failure `run()` reports is an instance of a public class. Each class carries the facts its
33
- * sentence interpolates, so a view reads them instead of parsing prose, and the exit status is
34
- * a field of the base, so a subclass inherits it. `message` never carries a category prefix; the
35
- * default views add it.
61
+ * sentence interpolates, so a view reads them instead of parsing prose. The exit code is a static
62
+ * field the class declares, read from the nearest ancestor that declares one and captured at the
63
+ * class's first construction, so one class exits with one code and a projection reads it without
64
+ * an instance. The instance reports the same value through a read-only accessor, and no subclass
65
+ * property or assignment changes the code `run()` resolves. `message` never carries a category
66
+ * prefix; the default views add it.
36
67
  */
37
68
  export declare abstract class LoomError extends Error {
38
- readonly exitCode: 1 | 2;
39
- constructor(message: string, exitCode: 1 | 2);
69
+ static readonly exitCode: FailureExitCode;
70
+ /**
71
+ * Reads the constructed class's code, captured at its first construction. A code outside 1
72
+ * through 125 throws a `DeclarationError` in place of the failure and captures nothing, because
73
+ * core never clamps or replaces a code. `options` is the platform's own, so a failure that
74
+ * replaces another error keeps it as `cause` only when its author passes one.
75
+ */
76
+ constructor(message: string, options?: ErrorOptions);
77
+ /**
78
+ * The code this failure exits with. An accessor without a setter, so a TypeScript subclass cannot
79
+ * declare it as a property, and an assignment throws in strict mode code and is ignored in sloppy
80
+ * mode code.
81
+ */
82
+ get exitCode(): FailureExitCode;
40
83
  }
41
84
  /** Exit 2: the invocation, not the application, is wrong. */
42
85
  export declare abstract class UsageError extends LoomError {
43
- constructor(message: string);
86
+ static readonly exitCode: FailureExitCode;
87
+ constructor(message: string, options?: ErrorOptions);
44
88
  }
45
89
  /** One input the validation phase rejected: an omission, or a value its schema refused. */
46
90
  export type InputProblem = {
@@ -56,7 +100,7 @@ export type InputProblem = {
56
100
  /** The whole validation phase in authoring order, so one failure reports every rejected input. */
57
101
  export declare class InputError extends UsageError {
58
102
  readonly problems: readonly InputProblem[];
59
- constructor(message: string, problems: readonly InputProblem[]);
103
+ constructor(message: string, problems: readonly InputProblem[], options?: ErrorOptions);
60
104
  }
61
105
  export declare class UnknownCommandError extends UsageError {
62
106
  readonly token: string;
@@ -98,17 +142,41 @@ export declare class ShortGroupError extends UsageError {
98
142
  readonly reason: 'value-position' | 'mixed-scope';
99
143
  constructor(fault: ShortGroupFault);
100
144
  }
101
- /** Exit 1: the declaration is wrong, so the author reads the diagnostic. */
145
+ /**
146
+ * Exit 1: the declaration is wrong, so the author reads its Developer Diagnostic. A rule and the
147
+ * fault's own parts build it, or a sentence alone does for a fault with no rule. `message` holds
148
+ * the whole diagnostic as plain text at 80 columns, so a fault thrown at an authoring call prints
149
+ * it through the runtime's own uncaught-error output, and `sentence` holds the sentence alone.
150
+ */
102
151
  export declare class DeclarationError extends LoomError {
103
- constructor(message: string);
152
+ readonly rule: DiagnosticRule | undefined;
153
+ readonly sentence: string;
154
+ readonly findings: readonly Finding[];
155
+ readonly correction: string | readonly string[] | undefined;
156
+ constructor(rule: DiagnosticRule, parts: DiagnosticParts, options?: ErrorOptions);
157
+ constructor(sentence: string, options?: ErrorOptions);
104
158
  }
105
- /** Exit 1: the application ended the invocation itself. An application may subclass it. */
159
+ /**
160
+ * Exit 1: the application ended the invocation itself. An application may subclass it, and the
161
+ * subclass may declare its own exit code.
162
+ */
106
163
  export declare class FatalError extends LoomError {
107
- constructor(message: string);
164
+ constructor(message: string, options?: ErrorOptions);
108
165
  }
109
- /** Exit 1: an unexpected exception, a non-error throw, or a view that could not answer. */
166
+ /**
167
+ * Exit 1: a defect, such as an unexpected exception, a non-error throw, or a view that could not
168
+ * answer. A rule and the defect's own parts build it, or a sentence and the thrown value do for a
169
+ * defect with no rule. `message` stays the sentence, because only `run()` reports a defect, and a
170
+ * development build renders its Developer Diagnostic from the parts.
171
+ */
110
172
  export declare class InternalError extends LoomError {
111
173
  readonly cause: unknown;
174
+ readonly rule: DiagnosticRule | undefined;
175
+ readonly sentence: string;
176
+ readonly correction: string | readonly string[] | undefined;
177
+ constructor(rule: DiagnosticRule, parts: Omit<DiagnosticParts, 'findings'> & {
178
+ readonly cause: unknown;
179
+ });
112
180
  constructor(message: string, cause: unknown);
113
181
  }
114
182
  /** The five ways the results lane is broken, each named where core meets it. */
@@ -130,14 +198,30 @@ export declare class ResultError extends InternalError {
130
198
  * with or without a full stop, so a diagnostic supplies one only where the message carries none.
131
199
  */
132
200
  export declare function asSentence(text: string): string;
133
- /** What a diagnostic says about an unexpected value, whether or not it was an Error. */
201
+ /**
202
+ * What a diagnostic says about an unexpected value, whether or not it was an Error, with every
203
+ * control character escaped. A `DeclarationError` answers its sentence, because its message holds
204
+ * its whole diagnostic. Every sentence that quotes a thrown value reads it here, so a reason stays
205
+ * on one line and no bidirectional control reaches a terminal, whichever rule's sentence carries
206
+ * it, while the author's words around it keep their line breaks. Reading it never throws: an Error
207
+ * whose message is not a string or cannot be read, and a value whose prototype cannot be read, such
208
+ * as a proxy whose trap throws, answer one fixed sentence.
209
+ */
134
210
  export declare function reasonOf(thrown: unknown): string;
135
211
  /**
136
212
  * Why a returned value is not the text a view owes. A view is synchronous, so a returned promise is
137
- * a non-string return like any other, and its rejection is adopted and swallowed here: an
138
- * unobserved rejection would end the process before the invocation could report anything.
213
+ * a non-string return like any other: it receives a rejection handler and is otherwise ignored.
139
214
  */
140
215
  export declare function notTextReason(value: unknown): string;
141
- /** Every thrown value reaches reporting as a failure class; anything else is internal. */
216
+ /**
217
+ * Every thrown value reaches reporting as a failure class; anything else is internal. A value that
218
+ * inherits from a failure class without having been constructed holds no code, so it is internal
219
+ * too.
220
+ */
142
221
  export declare function toFailure(thrown: unknown): LoomError;
222
+ /**
223
+ * The defect a foreign throw reports: its reason as the sentence, and the thrown value as the
224
+ * cause a development build's diagnostic shows.
225
+ */
226
+ export declare function foreignFailure(thrown: unknown): InternalError;
143
227
  export {};