@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
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
|
|
2
|
+
import { checkAnchorOnAlias, getAnchorName, makeAlias, registerAnchor } from "./anchors.js";
|
|
3
|
+
import { enterNesting, exitNesting, hasMeta } from "./state.js";
|
|
4
|
+
import { collectMultilineKey, collectMultilinePlainScalar, getScalarStyle, hasValueSepThroughPlainScalars, makeScalar, resolveScalar } from "./scalars.js";
|
|
5
|
+
import { buildPairs, checkDuplicateKeys, checkMultilineImplicitKeys } from "./block.js";
|
|
6
|
+
|
|
7
|
+
//#region src/internal/composer/flow.ts
|
|
8
|
+
/**
|
|
9
|
+
* Validate that flow collection entries are separated by commas.
|
|
10
|
+
*
|
|
11
|
+
* Detects the specific pattern: content, `:`, content, content (no comma).
|
|
12
|
+
* This catches `{foo: 1 bar: 2}` while allowing multiline plain scalars
|
|
13
|
+
* like `{multi\n line: value}` (consecutive scalars without colon between).
|
|
14
|
+
*
|
|
15
|
+
* State machine: idle → saw-colon → saw-value → error-if-no-comma
|
|
16
|
+
*/
|
|
17
|
+
function validateFlowSeparators(children, state, openBracket, closeBracket) {
|
|
18
|
+
let colonCount = 0;
|
|
19
|
+
let contentAfterColon = 0;
|
|
20
|
+
for (const child of children) {
|
|
21
|
+
if (child.type === "whitespace" && (child.source === openBracket || child.source === closeBracket)) continue;
|
|
22
|
+
if (child.type === "newline") continue;
|
|
23
|
+
if (child.type === "comment") {
|
|
24
|
+
if (contentAfterColon > 0) {
|
|
25
|
+
colonCount = 1;
|
|
26
|
+
contentAfterColon = 1;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (child.type === "whitespace" && child.source.trim() === "") continue;
|
|
31
|
+
if (child.type === "whitespace" && child.source === ",") {
|
|
32
|
+
colonCount = 0;
|
|
33
|
+
contentAfterColon = 0;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (child.type === "whitespace" && child.source === ":") {
|
|
37
|
+
colonCount++;
|
|
38
|
+
contentAfterColon = 0;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (child.type === "flow-scalar" || child.type === "block-scalar" || child.type === "flow-map" || child.type === "flow-seq" || child.type === "alias") {
|
|
42
|
+
contentAfterColon++;
|
|
43
|
+
if (colonCount > 0 && contentAfterColon > 1) state.errors.push({
|
|
44
|
+
code: "MalformedFlowCollection",
|
|
45
|
+
message: "Missing comma between flow collection entries",
|
|
46
|
+
offset: child.offset,
|
|
47
|
+
length: child.length
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 9C9N, VJP3/00: continuation lines of multi-line flow content must be
|
|
54
|
+
* indented past the parent block context. Caller passes the parent block
|
|
55
|
+
* column; for document-root flow content (no parent block) the caller
|
|
56
|
+
* omits it and the check is skipped.
|
|
57
|
+
*/
|
|
58
|
+
function validateFlowContentIndent(cst, state, parentBlockColumn) {
|
|
59
|
+
if (parentBlockColumn === void 0 || parentBlockColumn < 0) return;
|
|
60
|
+
const text = state.text;
|
|
61
|
+
const start = cst.offset;
|
|
62
|
+
const end = cst.offset + cst.length;
|
|
63
|
+
let i = start;
|
|
64
|
+
let inLineStart = false;
|
|
65
|
+
let lineStart = -1;
|
|
66
|
+
while (i < end && i < text.length) {
|
|
67
|
+
const ch = text[i];
|
|
68
|
+
if (ch === "\n") {
|
|
69
|
+
inLineStart = true;
|
|
70
|
+
lineStart = i + 1;
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (inLineStart) {
|
|
75
|
+
if (ch === " " || ch === " ") {
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (i - lineStart <= parentBlockColumn) {
|
|
80
|
+
state.errors.push({
|
|
81
|
+
code: "InvalidIndentation",
|
|
82
|
+
message: "Flow content continuation line must be indented past the parent block",
|
|
83
|
+
offset: i,
|
|
84
|
+
length: 1
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
inLineStart = false;
|
|
89
|
+
}
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function composeFlowMap(cst, state, meta, parentBlockColumn) {
|
|
94
|
+
if (!enterNesting(state, cst)) return new YamlMap({
|
|
95
|
+
items: [],
|
|
96
|
+
style: "flow",
|
|
97
|
+
offset: cst.offset,
|
|
98
|
+
length: cst.length
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
return composeFlowMapInner(cst, state, meta, parentBlockColumn);
|
|
102
|
+
} finally {
|
|
103
|
+
exitNesting(state);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function composeFlowMapInner(cst, state, meta, parentBlockColumn) {
|
|
107
|
+
const children = cst.children ?? [];
|
|
108
|
+
const pairs = [];
|
|
109
|
+
const hasOpen = children.some((c) => c.type === "whitespace" && c.source === "{");
|
|
110
|
+
const hasClose = children.some((c) => c.type === "whitespace" && c.source === "}");
|
|
111
|
+
if (hasOpen && !hasClose) state.errors.push({
|
|
112
|
+
code: "MalformedFlowCollection",
|
|
113
|
+
message: "Unclosed flow mapping (missing `}`)",
|
|
114
|
+
offset: cst.offset,
|
|
115
|
+
length: cst.length
|
|
116
|
+
});
|
|
117
|
+
validateFlowContentIndent(cst, state, parentBlockColumn);
|
|
118
|
+
validateFlowSeparators(children, state, "{", "}");
|
|
119
|
+
buildPairs(flattenFlowChildren(children.filter((c) => !(c.type === "whitespace" && (c.source === "{" || c.source === "}" || c.source.trim() === ""))), state), pairs, state.text);
|
|
120
|
+
if (state.options.uniqueKeys) checkDuplicateKeys(pairs, state);
|
|
121
|
+
const map = new YamlMap({
|
|
122
|
+
items: pairs,
|
|
123
|
+
style: "flow",
|
|
124
|
+
offset: cst.offset,
|
|
125
|
+
length: cst.length,
|
|
126
|
+
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
127
|
+
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
128
|
+
...meta?.comment !== void 0 ? { comment: meta.comment } : {}
|
|
129
|
+
});
|
|
130
|
+
if (meta?.anchor) registerAnchor(map, meta.anchor, state, cst.offset);
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
function flattenFlowChildren(children, state) {
|
|
134
|
+
const items = [];
|
|
135
|
+
let pendingMeta = {};
|
|
136
|
+
for (let i = 0; i < children.length; i++) {
|
|
137
|
+
const child = children[i];
|
|
138
|
+
if (!child) continue;
|
|
139
|
+
if (child.type === "newline") continue;
|
|
140
|
+
if (child.type === "whitespace") {
|
|
141
|
+
if (child.source === ",") {
|
|
142
|
+
if (hasMeta(pendingMeta)) {
|
|
143
|
+
const scalar = new YamlScalar({
|
|
144
|
+
value: resolveScalar("", "plain", pendingMeta.tag, state),
|
|
145
|
+
style: "plain",
|
|
146
|
+
offset: child.offset,
|
|
147
|
+
length: 0,
|
|
148
|
+
...pendingMeta.tag !== void 0 ? { tag: pendingMeta.tag } : {},
|
|
149
|
+
...pendingMeta.anchor !== void 0 ? { anchor: pendingMeta.anchor } : {}
|
|
150
|
+
});
|
|
151
|
+
if (pendingMeta.anchor) registerAnchor(scalar, pendingMeta.anchor, state, child.offset);
|
|
152
|
+
pendingMeta = {};
|
|
153
|
+
items.push({
|
|
154
|
+
kind: "node",
|
|
155
|
+
node: scalar
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (child.source === ":") {
|
|
161
|
+
if (hasMeta(pendingMeta)) {
|
|
162
|
+
const scalar = new YamlScalar({
|
|
163
|
+
value: resolveScalar("", "plain", pendingMeta.tag, state),
|
|
164
|
+
style: "plain",
|
|
165
|
+
offset: child.offset,
|
|
166
|
+
length: 0,
|
|
167
|
+
...pendingMeta.tag !== void 0 ? { tag: pendingMeta.tag } : {},
|
|
168
|
+
...pendingMeta.anchor !== void 0 ? { anchor: pendingMeta.anchor } : {}
|
|
169
|
+
});
|
|
170
|
+
if (pendingMeta.anchor) registerAnchor(scalar, pendingMeta.anchor, state, child.offset);
|
|
171
|
+
pendingMeta = {};
|
|
172
|
+
items.push({
|
|
173
|
+
kind: "node",
|
|
174
|
+
node: scalar
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
items.push({
|
|
178
|
+
kind: "value-sep",
|
|
179
|
+
offset: child.offset
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (child.source === "?") items.push({ kind: "key" });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (child.type === "comment") {
|
|
186
|
+
items.push({
|
|
187
|
+
kind: "comment",
|
|
188
|
+
comment: child.source.startsWith("#") ? child.source.slice(1).trim() : child.source
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (child.type === "error") {
|
|
193
|
+
state.errors.push({
|
|
194
|
+
code: "UnexpectedToken",
|
|
195
|
+
message: `Unexpected content: ${child.source.trim() || "(empty)"}`,
|
|
196
|
+
offset: child.offset,
|
|
197
|
+
length: child.length
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (child.type === "anchor") {
|
|
202
|
+
pendingMeta.anchor = getAnchorName(child, state.text);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (child.type === "tag") {
|
|
206
|
+
pendingMeta.tag = child.source;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (child.type === "flow-scalar" || child.type === "block-scalar") {
|
|
210
|
+
if (child.type === "flow-scalar" && getScalarStyle(child) === "plain" && child.source.startsWith("#") && child.offset > 0) {
|
|
211
|
+
const prev = state.text[child.offset - 1];
|
|
212
|
+
if (prev !== " " && prev !== " " && prev !== "\n" && prev !== "\r") state.errors.push({
|
|
213
|
+
code: "UnexpectedToken",
|
|
214
|
+
message: "Comment must be preceded by whitespace",
|
|
215
|
+
offset: child.offset,
|
|
216
|
+
length: child.length
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (child.type === "flow-scalar" && getScalarStyle(child) === "plain" && (child.source === "-" || child.source === "?")) state.errors.push({
|
|
220
|
+
code: "UnexpectedToken",
|
|
221
|
+
message: `Invalid plain scalar '${child.source}' in flow context`,
|
|
222
|
+
offset: child.offset,
|
|
223
|
+
length: child.length
|
|
224
|
+
});
|
|
225
|
+
if (child.type === "flow-scalar" && getScalarStyle(child) === "plain") {
|
|
226
|
+
if (hasValueSepThroughPlainScalars(children, i + 1)) {
|
|
227
|
+
const { value, nextIdx } = collectMultilineKey(children, i);
|
|
228
|
+
const scalar = new YamlScalar({
|
|
229
|
+
value: resolveScalar(value, "plain", pendingMeta.tag, state),
|
|
230
|
+
style: "plain",
|
|
231
|
+
offset: child.offset,
|
|
232
|
+
length: child.length,
|
|
233
|
+
...pendingMeta.tag !== void 0 ? { tag: pendingMeta.tag } : {},
|
|
234
|
+
...pendingMeta.anchor !== void 0 ? { anchor: pendingMeta.anchor } : {}
|
|
235
|
+
});
|
|
236
|
+
if (pendingMeta.anchor) registerAnchor(scalar, pendingMeta.anchor, state, child.offset);
|
|
237
|
+
pendingMeta = {};
|
|
238
|
+
items.push({
|
|
239
|
+
kind: "node",
|
|
240
|
+
node: scalar
|
|
241
|
+
});
|
|
242
|
+
i = nextIdx - 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const { value, nextIdx, partsCount, endOffset } = collectMultilinePlainScalar(children, i, void 0, state.text);
|
|
246
|
+
const scalar = new YamlScalar({
|
|
247
|
+
value: resolveScalar(value, "plain", pendingMeta.tag, state),
|
|
248
|
+
style: "plain",
|
|
249
|
+
offset: child.offset,
|
|
250
|
+
length: partsCount > 1 ? endOffset - child.offset : child.length,
|
|
251
|
+
...pendingMeta.tag !== void 0 ? { tag: pendingMeta.tag } : {},
|
|
252
|
+
...pendingMeta.anchor !== void 0 ? { anchor: pendingMeta.anchor } : {}
|
|
253
|
+
});
|
|
254
|
+
if (pendingMeta.anchor) registerAnchor(scalar, pendingMeta.anchor, state, child.offset);
|
|
255
|
+
pendingMeta = {};
|
|
256
|
+
items.push({
|
|
257
|
+
kind: "node",
|
|
258
|
+
node: scalar
|
|
259
|
+
});
|
|
260
|
+
i = nextIdx - 1;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const scalar = makeScalar(child, state, hasMeta(pendingMeta) ? pendingMeta : void 0);
|
|
264
|
+
pendingMeta = {};
|
|
265
|
+
items.push({
|
|
266
|
+
kind: "node",
|
|
267
|
+
node: scalar
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (child.type === "alias") {
|
|
272
|
+
checkAnchorOnAlias(pendingMeta, child, state);
|
|
273
|
+
const alias = makeAlias(child, state);
|
|
274
|
+
pendingMeta = {};
|
|
275
|
+
items.push({
|
|
276
|
+
kind: "node",
|
|
277
|
+
node: alias
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (child.type === "flow-map") {
|
|
282
|
+
const map = composeFlowMap(child, state, hasMeta(pendingMeta) ? pendingMeta : void 0);
|
|
283
|
+
pendingMeta = {};
|
|
284
|
+
items.push({
|
|
285
|
+
kind: "node",
|
|
286
|
+
node: map
|
|
287
|
+
});
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (child.type === "flow-seq") {
|
|
291
|
+
const seq = composeFlowSeq(child, state, hasMeta(pendingMeta) ? pendingMeta : void 0);
|
|
292
|
+
pendingMeta = {};
|
|
293
|
+
items.push({
|
|
294
|
+
kind: "node",
|
|
295
|
+
node: seq
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (hasMeta(pendingMeta)) {
|
|
300
|
+
const scalar = new YamlScalar({
|
|
301
|
+
value: resolveScalar("", "plain", pendingMeta.tag, state),
|
|
302
|
+
style: "plain",
|
|
303
|
+
offset: 0,
|
|
304
|
+
length: 0,
|
|
305
|
+
...pendingMeta.tag !== void 0 ? { tag: pendingMeta.tag } : {},
|
|
306
|
+
...pendingMeta.anchor !== void 0 ? { anchor: pendingMeta.anchor } : {}
|
|
307
|
+
});
|
|
308
|
+
if (pendingMeta.anchor) registerAnchor(scalar, pendingMeta.anchor, state, 0);
|
|
309
|
+
items.push({
|
|
310
|
+
kind: "node",
|
|
311
|
+
node: scalar
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return items;
|
|
315
|
+
}
|
|
316
|
+
function composeFlowSeq(cst, state, meta, parentBlockColumn) {
|
|
317
|
+
if (!enterNesting(state, cst)) return new YamlSeq({
|
|
318
|
+
items: [],
|
|
319
|
+
style: "flow",
|
|
320
|
+
offset: cst.offset,
|
|
321
|
+
length: cst.length
|
|
322
|
+
});
|
|
323
|
+
try {
|
|
324
|
+
return composeFlowSeqInner(cst, state, meta, parentBlockColumn);
|
|
325
|
+
} finally {
|
|
326
|
+
exitNesting(state);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function composeFlowSeqInner(cst, state, meta, parentBlockColumn) {
|
|
330
|
+
const children = cst.children ?? [];
|
|
331
|
+
const items = [];
|
|
332
|
+
validateFlowContentIndent(cst, state, parentBlockColumn);
|
|
333
|
+
validateFlowSeparators(children, state, "[", "]");
|
|
334
|
+
const hasOpen = children.some((c) => c.type === "whitespace" && c.source === "[");
|
|
335
|
+
const hasClose = children.some((c) => c.type === "whitespace" && c.source === "]");
|
|
336
|
+
if (hasOpen && !hasClose) state.errors.push({
|
|
337
|
+
code: "MalformedFlowCollection",
|
|
338
|
+
message: "Unclosed flow sequence (missing `]`)",
|
|
339
|
+
offset: cst.offset,
|
|
340
|
+
length: cst.length
|
|
341
|
+
});
|
|
342
|
+
const segments = [];
|
|
343
|
+
let current = [];
|
|
344
|
+
let seenContent = false;
|
|
345
|
+
let lastWasComma = false;
|
|
346
|
+
for (const child of children) {
|
|
347
|
+
if (child.type === "whitespace" && (child.source === "[" || child.source === "]")) continue;
|
|
348
|
+
if (child.type === "whitespace" && child.source === ",") {
|
|
349
|
+
if (!current.some((c) => c.type !== "whitespace" && c.type !== "newline" && c.type !== "comment") && (lastWasComma || !seenContent)) state.errors.push({
|
|
350
|
+
code: "MalformedFlowCollection",
|
|
351
|
+
message: "Empty entry in flow sequence",
|
|
352
|
+
offset: child.offset,
|
|
353
|
+
length: 1
|
|
354
|
+
});
|
|
355
|
+
if (current.length > 0) segments.push(current);
|
|
356
|
+
current = [];
|
|
357
|
+
lastWasComma = true;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (child.type !== "whitespace" && child.type !== "newline" && child.type !== "comment") {
|
|
361
|
+
seenContent = true;
|
|
362
|
+
lastWasComma = false;
|
|
363
|
+
}
|
|
364
|
+
current.push(child);
|
|
365
|
+
}
|
|
366
|
+
if (current.length > 0) segments.push(current);
|
|
367
|
+
for (const segment of segments) if (segment.some((c) => c.type === "whitespace" && c.source === ":")) {
|
|
368
|
+
const semItems = flattenFlowChildren(segment.filter((c) => !(c.type === "whitespace" && c.source.trim() === "")), state);
|
|
369
|
+
const pairs = [];
|
|
370
|
+
buildPairs(semItems, pairs, state.text);
|
|
371
|
+
if (!segment.some((c) => c.type === "whitespace" && c.source === "?")) checkMultilineImplicitKeys(pairs, state, semItems);
|
|
372
|
+
const firstPair = pairs[0];
|
|
373
|
+
if (firstPair) {
|
|
374
|
+
const map = new YamlMap({
|
|
375
|
+
items: pairs,
|
|
376
|
+
style: "flow",
|
|
377
|
+
offset: firstPair.key.offset,
|
|
378
|
+
length: 0
|
|
379
|
+
});
|
|
380
|
+
items.push(map);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const semItems = flattenFlowChildren(segment.filter((c) => !(c.type === "whitespace" && c.source.trim() === "")), state);
|
|
384
|
+
for (const si of semItems) if (si.kind === "node" && si.node) items.push(si.node);
|
|
385
|
+
}
|
|
386
|
+
const seq = new YamlSeq({
|
|
387
|
+
items,
|
|
388
|
+
style: "flow",
|
|
389
|
+
offset: cst.offset,
|
|
390
|
+
length: cst.length,
|
|
391
|
+
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
392
|
+
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
393
|
+
...meta?.comment !== void 0 ? { comment: meta.comment } : {}
|
|
394
|
+
});
|
|
395
|
+
if (meta?.anchor) registerAnchor(seq, meta.anchor, state, cst.offset);
|
|
396
|
+
return seq;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
export { composeFlowMap, composeFlowSeq, flattenFlowChildren };
|