@effected/yaml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/Yaml.js +414 -0
- package/YamlDiagnostic.js +126 -0
- package/YamlDocument.js +181 -0
- package/YamlEdit.js +45 -0
- package/YamlFormat.js +309 -0
- package/YamlNode.js +396 -0
- package/YamlVisitor.js +172 -0
- package/index.d.ts +1331 -0
- package/index.js +9 -0
- package/internal/composer/anchors.js +97 -0
- package/internal/composer/block.js +1155 -0
- package/internal/composer/document.js +677 -0
- package/internal/composer/flow.js +400 -0
- package/internal/composer/scalars.js +885 -0
- package/internal/composer/state.js +117 -0
- package/internal/composer/tags.js +88 -0
- package/internal/cst-parser.js +716 -0
- package/internal/diagnostics.js +80 -0
- package/internal/diff.js +56 -0
- package/internal/fold.js +153 -0
- package/internal/lexer.js +959 -0
- package/internal/stringifier.js +797 -0
- package/package.json +47 -0
- package/tsdoc-metadata.json +11 -0
package/YamlNode.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/YamlNode.ts
|
|
4
|
+
/**
|
|
5
|
+
* YAML scalar presentation styles.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
const ScalarStyle = Schema.Literals([
|
|
10
|
+
"plain",
|
|
11
|
+
"single-quoted",
|
|
12
|
+
"double-quoted",
|
|
13
|
+
"block-literal",
|
|
14
|
+
"block-folded"
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* YAML collection presentation styles.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
const CollectionStyle = Schema.Literals(["block", "flow"]);
|
|
22
|
+
/**
|
|
23
|
+
* Block-scalar chomping indicators (`-` strip, default clip, `+` keep).
|
|
24
|
+
* Referenced by the {@link YamlScalar} `chomp` field schema.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
const ScalarChomp = Schema.Literals([
|
|
29
|
+
"strip",
|
|
30
|
+
"clip",
|
|
31
|
+
"keep"
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* A YAML scalar AST node, representing a leaf value such as a string,
|
|
35
|
+
* number, boolean, or null.
|
|
36
|
+
*
|
|
37
|
+
* - `value` — the resolved JavaScript value (null, boolean, number, bigint or
|
|
38
|
+
* string).
|
|
39
|
+
* - `style` — the scalar presentation style in the source document.
|
|
40
|
+
* - `tag` — optional explicit YAML tag (e.g. `!!str`, `!!int`).
|
|
41
|
+
* - `anchor` — optional anchor name for aliasing.
|
|
42
|
+
* - `comment` — optional trailing or leading comment text.
|
|
43
|
+
* - `chomp` — block-scalar chomping indicator, when the scalar is a block
|
|
44
|
+
* scalar.
|
|
45
|
+
* - `raw` — the raw source text, preserved when it differs from the resolved
|
|
46
|
+
* value in a way stringification needs to know about.
|
|
47
|
+
* - `sourceMultiline` — `true` when the source span covers two or more lines;
|
|
48
|
+
* absent on synthetic nodes.
|
|
49
|
+
* - `offset` / `length` — the node's span in the source.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
var YamlScalar = class extends Schema.TaggedClass()("YamlScalar", {
|
|
54
|
+
value: Schema.Unknown,
|
|
55
|
+
tag: Schema.optionalKey(Schema.String),
|
|
56
|
+
style: ScalarStyle,
|
|
57
|
+
anchor: Schema.optionalKey(Schema.String),
|
|
58
|
+
comment: Schema.optionalKey(Schema.String),
|
|
59
|
+
chomp: Schema.optionalKey(ScalarChomp),
|
|
60
|
+
raw: Schema.optionalKey(Schema.String),
|
|
61
|
+
sourceMultiline: Schema.optionalKey(Schema.Boolean),
|
|
62
|
+
offset: Schema.Number,
|
|
63
|
+
length: Schema.Number
|
|
64
|
+
}) {
|
|
65
|
+
/**
|
|
66
|
+
* Navigate to a descendant by path (string segments for mapping keys,
|
|
67
|
+
* numbers for sequence indices). `Option.none()` when any segment cannot
|
|
68
|
+
* be resolved. Pure.
|
|
69
|
+
*/
|
|
70
|
+
find(path) {
|
|
71
|
+
return findByPath(this, path);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Find the deepest node whose span contains `offset` (half-open interval),
|
|
75
|
+
* or `Option.none()` when the offset falls outside this subtree. Pure.
|
|
76
|
+
*/
|
|
77
|
+
findAtOffset(offset) {
|
|
78
|
+
return findDeepestAtOffset(this, offset);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Return the path from this node to the given descendant node (matched by
|
|
82
|
+
* reference identity), or `Option.none()` when it is not in this subtree.
|
|
83
|
+
* The inverse of {@link YamlScalar.find}. Pure.
|
|
84
|
+
*/
|
|
85
|
+
pathOf(node) {
|
|
86
|
+
return pathToNode(this, node);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reconstruct the plain JavaScript value of this subtree. Aliases resolve
|
|
90
|
+
* through `anchors` (anchors encountered during the walk register
|
|
91
|
+
* incrementally, so an alias sees the most recent definition at its point
|
|
92
|
+
* of use); unresolvable aliases yield `null`. Pure and total.
|
|
93
|
+
*/
|
|
94
|
+
toValue(anchors) {
|
|
95
|
+
return nodeToValue(this, anchors, defaultBudget());
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A YAML alias AST node, referencing a previously defined anchor by name
|
|
100
|
+
* (without the leading `*`).
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
var YamlAlias = class extends Schema.TaggedClass()("YamlAlias", {
|
|
105
|
+
name: Schema.String,
|
|
106
|
+
offset: Schema.Number,
|
|
107
|
+
length: Schema.Number
|
|
108
|
+
}) {
|
|
109
|
+
/** See `YamlScalar.find`. Pure. */
|
|
110
|
+
find(path) {
|
|
111
|
+
return findByPath(this, path);
|
|
112
|
+
}
|
|
113
|
+
/** See `YamlScalar.findAtOffset`. Pure. */
|
|
114
|
+
findAtOffset(offset) {
|
|
115
|
+
return findDeepestAtOffset(this, offset);
|
|
116
|
+
}
|
|
117
|
+
/** See `YamlScalar.pathOf`. Pure. */
|
|
118
|
+
pathOf(node) {
|
|
119
|
+
return pathToNode(this, node);
|
|
120
|
+
}
|
|
121
|
+
/** See `YamlScalar.toValue`. Pure and total. */
|
|
122
|
+
toValue(anchors) {
|
|
123
|
+
return nodeToValue(this, anchors, defaultBudget());
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* A discriminated-union schema covering all four YAML AST value node types:
|
|
128
|
+
* {@link YamlScalar}, {@link YamlMap}, {@link YamlSeq} and {@link YamlAlias}.
|
|
129
|
+
* Defined lazily via `Schema.suspend` to break the recursive reference chain
|
|
130
|
+
* `YamlNode → YamlMap → YamlPair → YamlNode`.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* Construct member nodes via their `.make(...)` static (e.g.
|
|
134
|
+
* `YamlScalar.make(...)`), never `new YamlScalar(...)` — the internal
|
|
135
|
+
* composer's hot-path `new` construction is the one recorded exception, kept
|
|
136
|
+
* internal to the engine for its allocation-sensitive walk.
|
|
137
|
+
*
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
const YamlNode = Schema.suspend(() => Schema.Union([
|
|
141
|
+
YamlScalar,
|
|
142
|
+
YamlMap,
|
|
143
|
+
YamlSeq,
|
|
144
|
+
YamlAlias
|
|
145
|
+
]));
|
|
146
|
+
/**
|
|
147
|
+
* A YAML key-value pair AST node, representing one entry within a mapping.
|
|
148
|
+
* `value` is `null` when absent (e.g. `key:` with no value).
|
|
149
|
+
*
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
var YamlPair = class extends Schema.TaggedClass()("YamlPair", {
|
|
153
|
+
key: Schema.suspend(() => YamlNode),
|
|
154
|
+
value: Schema.NullOr(Schema.suspend(() => YamlNode)),
|
|
155
|
+
comment: Schema.optionalKey(Schema.String)
|
|
156
|
+
}) {};
|
|
157
|
+
/**
|
|
158
|
+
* A YAML mapping AST node, representing a collection of {@link YamlPair}
|
|
159
|
+
* entries.
|
|
160
|
+
*
|
|
161
|
+
* - `style` — the presentation style: `"block"` or `"flow"`.
|
|
162
|
+
* - `sourceMultiline` — `true` when the source span covers two or more lines;
|
|
163
|
+
* used by the canonical stringifier. Absent on synthetic nodes.
|
|
164
|
+
*
|
|
165
|
+
* @public
|
|
166
|
+
*/
|
|
167
|
+
var YamlMap = class extends Schema.TaggedClass()("YamlMap", {
|
|
168
|
+
items: Schema.Array(Schema.suspend(() => YamlPair)),
|
|
169
|
+
tag: Schema.optionalKey(Schema.String),
|
|
170
|
+
anchor: Schema.optionalKey(Schema.String),
|
|
171
|
+
style: CollectionStyle,
|
|
172
|
+
comment: Schema.optionalKey(Schema.String),
|
|
173
|
+
sourceMultiline: Schema.optionalKey(Schema.Boolean),
|
|
174
|
+
offset: Schema.Number,
|
|
175
|
+
length: Schema.Number
|
|
176
|
+
}) {
|
|
177
|
+
/** See `YamlScalar.find`. Pure. */
|
|
178
|
+
find(path) {
|
|
179
|
+
return findByPath(this, path);
|
|
180
|
+
}
|
|
181
|
+
/** See `YamlScalar.findAtOffset`. Pure. */
|
|
182
|
+
findAtOffset(offset) {
|
|
183
|
+
return findDeepestAtOffset(this, offset);
|
|
184
|
+
}
|
|
185
|
+
/** See `YamlScalar.pathOf`. Pure. */
|
|
186
|
+
pathOf(node) {
|
|
187
|
+
return pathToNode(this, node);
|
|
188
|
+
}
|
|
189
|
+
/** See `YamlScalar.toValue`. Pure and total. */
|
|
190
|
+
toValue(anchors) {
|
|
191
|
+
return nodeToValue(this, anchors, defaultBudget());
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* A YAML sequence AST node, representing an ordered list of
|
|
196
|
+
* {@link (YamlNode:type)} values.
|
|
197
|
+
*
|
|
198
|
+
* @public
|
|
199
|
+
*/
|
|
200
|
+
var YamlSeq = class extends Schema.TaggedClass()("YamlSeq", {
|
|
201
|
+
items: Schema.Array(Schema.suspend(() => YamlNode)),
|
|
202
|
+
tag: Schema.optionalKey(Schema.String),
|
|
203
|
+
anchor: Schema.optionalKey(Schema.String),
|
|
204
|
+
style: CollectionStyle,
|
|
205
|
+
comment: Schema.optionalKey(Schema.String),
|
|
206
|
+
sourceMultiline: Schema.optionalKey(Schema.Boolean),
|
|
207
|
+
offset: Schema.Number,
|
|
208
|
+
length: Schema.Number
|
|
209
|
+
}) {
|
|
210
|
+
/** See `YamlScalar.find`. Pure. */
|
|
211
|
+
find(path) {
|
|
212
|
+
return findByPath(this, path);
|
|
213
|
+
}
|
|
214
|
+
/** See `YamlScalar.findAtOffset`. Pure. */
|
|
215
|
+
findAtOffset(offset) {
|
|
216
|
+
return findDeepestAtOffset(this, offset);
|
|
217
|
+
}
|
|
218
|
+
/** See `YamlScalar.pathOf`. Pure. */
|
|
219
|
+
pathOf(node) {
|
|
220
|
+
return pathToNode(this, node);
|
|
221
|
+
}
|
|
222
|
+
/** See `YamlScalar.toValue`. Pure and total. */
|
|
223
|
+
toValue(anchors) {
|
|
224
|
+
return nodeToValue(this, anchors, defaultBudget());
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
function findByPath(root, path) {
|
|
228
|
+
let current = root;
|
|
229
|
+
for (const segment of path) {
|
|
230
|
+
if (current === null) return Option.none();
|
|
231
|
+
if (typeof segment === "string") {
|
|
232
|
+
if (!(current instanceof YamlMap)) return Option.none();
|
|
233
|
+
const pair = current.items.find((p) => p.key instanceof YamlScalar && typeof p.key.value === "string" && p.key.value === segment);
|
|
234
|
+
if (!pair || pair.value === null) return Option.none();
|
|
235
|
+
current = pair.value;
|
|
236
|
+
} else {
|
|
237
|
+
if (!(current instanceof YamlSeq)) return Option.none();
|
|
238
|
+
const item = current.items[segment];
|
|
239
|
+
if (item === void 0) return Option.none();
|
|
240
|
+
current = item;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return current === null ? Option.none() : Option.some(current);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Half-open interval test `[offset, offset + length)` so a cursor positioned
|
|
247
|
+
* immediately after a node is NOT considered inside it.
|
|
248
|
+
*/
|
|
249
|
+
function containsOffset(node, offset) {
|
|
250
|
+
return offset >= node.offset && offset < node.offset + node.length;
|
|
251
|
+
}
|
|
252
|
+
function findDeepestAtOffset(node, offset) {
|
|
253
|
+
if (!containsOffset(node, offset)) return Option.none();
|
|
254
|
+
if (node instanceof YamlMap) for (const pair of node.items) {
|
|
255
|
+
const keyResult = findDeepestAtOffset(pair.key, offset);
|
|
256
|
+
if (Option.isSome(keyResult)) return keyResult;
|
|
257
|
+
if (pair.value !== null) {
|
|
258
|
+
const valResult = findDeepestAtOffset(pair.value, offset);
|
|
259
|
+
if (Option.isSome(valResult)) return valResult;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (node instanceof YamlSeq) for (const item of node.items) {
|
|
263
|
+
const itemResult = findDeepestAtOffset(item, offset);
|
|
264
|
+
if (Option.isSome(itemResult)) return itemResult;
|
|
265
|
+
}
|
|
266
|
+
return Option.some(node);
|
|
267
|
+
}
|
|
268
|
+
function pathToNode(root, target) {
|
|
269
|
+
const path = [];
|
|
270
|
+
return descendToNode(root, target, path) ? Option.some(path) : Option.none();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Depth-first identity search accumulating mapping-key/sequence-index
|
|
274
|
+
* segments. Only scalar string keys produce navigable segments (matching
|
|
275
|
+
* `find`); descendants reachable only through complex keys are not
|
|
276
|
+
* addressable by path.
|
|
277
|
+
*/
|
|
278
|
+
function descendToNode(node, target, path) {
|
|
279
|
+
if (node === target) return true;
|
|
280
|
+
if (node instanceof YamlMap) {
|
|
281
|
+
for (const pair of node.items) if (pair.key instanceof YamlScalar && typeof pair.key.value === "string") {
|
|
282
|
+
if (pair.key === target) {
|
|
283
|
+
path.push(pair.key.value);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (pair.value !== null) {
|
|
287
|
+
path.push(pair.key.value);
|
|
288
|
+
if (descendToNode(pair.value, target, path)) return true;
|
|
289
|
+
path.pop();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (node instanceof YamlSeq) for (let i = 0; i < node.items.length; i++) {
|
|
294
|
+
const item = node.items[i];
|
|
295
|
+
path.push(i);
|
|
296
|
+
if (descendToNode(item, target, path)) return true;
|
|
297
|
+
path.pop();
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
/** Set a mapping key as an own data property — `__proto__` included. */
|
|
302
|
+
function setOwnProperty(obj, key, value) {
|
|
303
|
+
if (key === "__proto__") Object.defineProperty(obj, key, {
|
|
304
|
+
value,
|
|
305
|
+
writable: true,
|
|
306
|
+
enumerable: true,
|
|
307
|
+
configurable: true
|
|
308
|
+
});
|
|
309
|
+
else obj[key] = value;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Thrown by the value-extraction walk when alias expansion materializes more
|
|
313
|
+
* output nodes than the budget allows — the YAML "billion laughs" guard. A
|
|
314
|
+
* chain of aliases each referencing the previous (`a2: [*a1×10]`, `a3:
|
|
315
|
+
* [*a2×10]`, …) multiplies output size exponentially while the alias-*token*
|
|
316
|
+
* count stays small, so the composer's per-token `maxAliasCount` limit does
|
|
317
|
+
* not catch it; only bounding the expanded node count does.
|
|
318
|
+
*
|
|
319
|
+
* Not re-exported from the package entry point (`index.ts`) — the facade
|
|
320
|
+
* catches it and materializes a fatal `AliasCountExceeded` `YamlParseError`
|
|
321
|
+
* (or, for `Yaml.equals`, treats the input as malformed).
|
|
322
|
+
*/
|
|
323
|
+
var AliasExpansionBudgetExceeded = class extends Error {
|
|
324
|
+
constructor(limit) {
|
|
325
|
+
super(`Alias expansion exceeded budget of ${limit} nodes`);
|
|
326
|
+
this.name = "AliasExpansionBudgetExceeded";
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Multiplier converting a `maxAliasCount` budget into a cap on the number of
|
|
331
|
+
* output nodes materialized *through alias expansion*. Deliberately generous:
|
|
332
|
+
* alias-free content never ticks the counter (see {@link nodeToValue}), so a
|
|
333
|
+
* large but benign document — or a single alias referencing a large alias-free
|
|
334
|
+
* block — stays far under the cap, while an exponential alias chain accumulates
|
|
335
|
+
* across the shared budget and trips it long before the heap is exhausted.
|
|
336
|
+
*/
|
|
337
|
+
const ALIAS_EXPANSION_FACTOR = 1e4;
|
|
338
|
+
/** The output-node cap for a given `maxAliasCount`. */
|
|
339
|
+
function aliasExpansionLimit(maxAliasCount) {
|
|
340
|
+
return (maxAliasCount + 1) * ALIAS_EXPANSION_FACTOR;
|
|
341
|
+
}
|
|
342
|
+
/** Default cap for a direct `toValue()` call, matching the default `maxAliasCount` of 100. */
|
|
343
|
+
const DEFAULT_ALIAS_EXPANSION_LIMIT = aliasExpansionLimit(100);
|
|
344
|
+
/** A fresh default budget for a direct `toValue()` call. */
|
|
345
|
+
function defaultBudget() {
|
|
346
|
+
return {
|
|
347
|
+
count: 0,
|
|
348
|
+
limit: DEFAULT_ALIAS_EXPANSION_LIMIT
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Value extraction with an explicit alias-expansion budget derived from
|
|
353
|
+
* `maxAliasCount`. The facade drives this so a `maxAliasCount` from parse
|
|
354
|
+
* options bounds the "billion laughs" expansion; throws
|
|
355
|
+
* {@link AliasExpansionBudgetExceeded} when the cap is exceeded. Not
|
|
356
|
+
* re-exported from the package entry point.
|
|
357
|
+
*/
|
|
358
|
+
function nodeToJsValue(node, anchors, maxAliasCount) {
|
|
359
|
+
return nodeToValue(node, anchors, {
|
|
360
|
+
count: 0,
|
|
361
|
+
limit: aliasExpansionLimit(maxAliasCount)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
function nodeToValue(node, anchors, budget, counting = false) {
|
|
365
|
+
if (node === null) return null;
|
|
366
|
+
if (counting && budget !== void 0) {
|
|
367
|
+
budget.count++;
|
|
368
|
+
if (budget.count > budget.limit) throw new AliasExpansionBudgetExceeded(budget.limit);
|
|
369
|
+
}
|
|
370
|
+
if (anchors !== void 0 && !(node instanceof YamlAlias) && node.anchor !== void 0) anchors.set(node.anchor, node);
|
|
371
|
+
if (node instanceof YamlScalar) return node.value;
|
|
372
|
+
if (node instanceof YamlMap) {
|
|
373
|
+
const result = {};
|
|
374
|
+
for (const pair of node.items) {
|
|
375
|
+
let key;
|
|
376
|
+
if (pair.key instanceof YamlScalar) {
|
|
377
|
+
if (anchors !== void 0 && pair.key.anchor !== void 0) anchors.set(pair.key.anchor, pair.key);
|
|
378
|
+
key = String(pair.key.value ?? "");
|
|
379
|
+
} else if (pair.key instanceof YamlAlias) {
|
|
380
|
+
const resolved = anchors?.get(pair.key.name);
|
|
381
|
+
key = resolved !== void 0 ? String(nodeToValue(resolved, anchors, budget, true) ?? "") : "";
|
|
382
|
+
} else key = "";
|
|
383
|
+
setOwnProperty(result, key, nodeToValue(pair.value, anchors, budget, counting));
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
if (node instanceof YamlSeq) return node.items.map((item) => nodeToValue(item, anchors, budget, counting));
|
|
388
|
+
if (node instanceof YamlAlias) {
|
|
389
|
+
const resolved = anchors?.get(node.name);
|
|
390
|
+
return resolved !== void 0 ? nodeToValue(resolved, anchors, budget, true) : null;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#endregion
|
|
396
|
+
export { AliasExpansionBudgetExceeded, CollectionStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq, aliasExpansionLimit, nodeToJsValue };
|
package/YamlVisitor.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { YamlAlias, YamlMap, YamlScalar, YamlSeq } from "./YamlNode.js";
|
|
2
|
+
import { composeAllDocuments } from "./internal/composer/document.js";
|
|
3
|
+
import { YamlDiagnostic } from "./YamlDiagnostic.js";
|
|
4
|
+
import { Data, Stream } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/YamlVisitor.ts
|
|
7
|
+
/**
|
|
8
|
+
* Constructors and matchers for the `YamlVisitorEvent` union (e.g.
|
|
9
|
+
* `YamlVisitorEvent.Scalar({ path, depth, value, style })`,
|
|
10
|
+
* `YamlVisitorEvent.$is("MapStart")`).
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
const YamlVisitorEvent = Data.taggedEnum();
|
|
15
|
+
/**
|
|
16
|
+
* SAX-style YAML AST visitor statics. Not instantiable.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
var YamlVisitor = class {
|
|
21
|
+
constructor() {}
|
|
22
|
+
/**
|
|
23
|
+
* Create a lazy `Stream` of `YamlVisitorEvent` from YAML text, in document
|
|
24
|
+
* order. Multi-document streams (separated by `---`) produce a separate
|
|
25
|
+
* `DocumentStart`/`DocumentEnd` pair per document. Events are produced on
|
|
26
|
+
* demand, so combining with `Stream.take` allows efficient partial scans
|
|
27
|
+
* of large documents without materializing the whole event sequence.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* Infallible at the type level: diagnostics recorded while composing
|
|
31
|
+
* (fatal or not, including an exceeded `maxAliasCount`, recorded as
|
|
32
|
+
* `AliasCountExceeded`) surface as `Error` events inside the stream rather
|
|
33
|
+
* than failing it.
|
|
34
|
+
*/
|
|
35
|
+
static visit(text, options) {
|
|
36
|
+
return Stream.fromIterable(visitGen(text, options));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function* visitGen(text, options) {
|
|
40
|
+
const { documents, streamErrors } = composeAllDocuments(text, {
|
|
41
|
+
strict: options?.strict,
|
|
42
|
+
maxAliasCount: options?.maxAliasCount,
|
|
43
|
+
uniqueKeys: options?.uniqueKeys
|
|
44
|
+
});
|
|
45
|
+
for (const raw of streamErrors) yield YamlVisitorEvent.Error({
|
|
46
|
+
path: [],
|
|
47
|
+
depth: 0,
|
|
48
|
+
diagnostic: YamlDiagnostic.fromRaw(raw, text)
|
|
49
|
+
});
|
|
50
|
+
for (const doc of documents) yield* walkDocument(doc, text);
|
|
51
|
+
}
|
|
52
|
+
function* walkDocument(doc, text) {
|
|
53
|
+
const path = [];
|
|
54
|
+
const depth = 0;
|
|
55
|
+
for (const dir of doc.directives) yield YamlVisitorEvent.Directive({
|
|
56
|
+
path,
|
|
57
|
+
depth,
|
|
58
|
+
name: dir.name,
|
|
59
|
+
parameters: dir.parameters.join(" ")
|
|
60
|
+
});
|
|
61
|
+
yield YamlVisitorEvent.DocumentStart({
|
|
62
|
+
path,
|
|
63
|
+
depth,
|
|
64
|
+
directives: doc.directives.map((d) => ({
|
|
65
|
+
name: d.name,
|
|
66
|
+
parameters: d.parameters
|
|
67
|
+
}))
|
|
68
|
+
});
|
|
69
|
+
for (const raw of doc.errors) yield YamlVisitorEvent.Error({
|
|
70
|
+
path,
|
|
71
|
+
depth,
|
|
72
|
+
diagnostic: YamlDiagnostic.fromRaw(raw, text)
|
|
73
|
+
});
|
|
74
|
+
for (const raw of doc.warnings) yield YamlVisitorEvent.Error({
|
|
75
|
+
path,
|
|
76
|
+
depth,
|
|
77
|
+
diagnostic: YamlDiagnostic.fromRaw(raw, text)
|
|
78
|
+
});
|
|
79
|
+
if (doc.comment !== void 0) yield YamlVisitorEvent.Comment({
|
|
80
|
+
path,
|
|
81
|
+
depth,
|
|
82
|
+
text: doc.comment
|
|
83
|
+
});
|
|
84
|
+
if (doc.contents !== null) yield* walkNode(doc.contents, path, depth);
|
|
85
|
+
yield YamlVisitorEvent.DocumentEnd({
|
|
86
|
+
path,
|
|
87
|
+
depth
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function* walkNode(node, path, depth) {
|
|
91
|
+
if (node instanceof YamlScalar) {
|
|
92
|
+
if (node.comment !== void 0) yield YamlVisitorEvent.Comment({
|
|
93
|
+
path,
|
|
94
|
+
depth,
|
|
95
|
+
text: node.comment
|
|
96
|
+
});
|
|
97
|
+
yield YamlVisitorEvent.Scalar({
|
|
98
|
+
path,
|
|
99
|
+
depth,
|
|
100
|
+
value: node.value,
|
|
101
|
+
style: node.style,
|
|
102
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
103
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {}
|
|
104
|
+
});
|
|
105
|
+
} else if (node instanceof YamlAlias) yield YamlVisitorEvent.Alias({
|
|
106
|
+
path,
|
|
107
|
+
depth,
|
|
108
|
+
name: node.name
|
|
109
|
+
});
|
|
110
|
+
else if (node instanceof YamlMap) {
|
|
111
|
+
if (node.comment !== void 0) yield YamlVisitorEvent.Comment({
|
|
112
|
+
path,
|
|
113
|
+
depth,
|
|
114
|
+
text: node.comment
|
|
115
|
+
});
|
|
116
|
+
yield YamlVisitorEvent.MapStart({
|
|
117
|
+
path,
|
|
118
|
+
depth,
|
|
119
|
+
style: node.style,
|
|
120
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
121
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {}
|
|
122
|
+
});
|
|
123
|
+
for (const pair of node.items) yield* walkPair(pair, path, depth + 1);
|
|
124
|
+
yield YamlVisitorEvent.MapEnd({
|
|
125
|
+
path,
|
|
126
|
+
depth
|
|
127
|
+
});
|
|
128
|
+
} else if (node instanceof YamlSeq) {
|
|
129
|
+
if (node.comment !== void 0) yield YamlVisitorEvent.Comment({
|
|
130
|
+
path,
|
|
131
|
+
depth,
|
|
132
|
+
text: node.comment
|
|
133
|
+
});
|
|
134
|
+
yield YamlVisitorEvent.SeqStart({
|
|
135
|
+
path,
|
|
136
|
+
depth,
|
|
137
|
+
style: node.style,
|
|
138
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
139
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {}
|
|
140
|
+
});
|
|
141
|
+
for (let i = 0; i < node.items.length; i++) {
|
|
142
|
+
const item = node.items[i];
|
|
143
|
+
yield* walkNode(item, [...path, i], depth + 1);
|
|
144
|
+
}
|
|
145
|
+
yield YamlVisitorEvent.SeqEnd({
|
|
146
|
+
path,
|
|
147
|
+
depth
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function* walkPair(pair, parentPath, depth) {
|
|
152
|
+
const resolvedKey = pair.key instanceof YamlScalar ? pair.key.value : null;
|
|
153
|
+
const resolvedValue = pair.value instanceof YamlScalar ? pair.value.value : null;
|
|
154
|
+
const keySegment = typeof resolvedKey === "string" ? resolvedKey : typeof resolvedKey === "number" ? resolvedKey : String(resolvedKey);
|
|
155
|
+
const pairPath = [...parentPath, keySegment];
|
|
156
|
+
if (pair.comment !== void 0) yield YamlVisitorEvent.Comment({
|
|
157
|
+
path: pairPath,
|
|
158
|
+
depth,
|
|
159
|
+
text: pair.comment
|
|
160
|
+
});
|
|
161
|
+
yield YamlVisitorEvent.Pair({
|
|
162
|
+
path: pairPath,
|
|
163
|
+
depth,
|
|
164
|
+
key: resolvedKey,
|
|
165
|
+
value: resolvedValue
|
|
166
|
+
});
|
|
167
|
+
yield* walkNode(pair.key, pairPath, depth + 1);
|
|
168
|
+
if (pair.value !== null) yield* walkNode(pair.value, pairPath, depth + 1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { YamlVisitor, YamlVisitorEvent };
|