@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,885 @@
|
|
|
1
|
+
import { YamlScalar } from "../../YamlNode.js";
|
|
2
|
+
import { registerAnchor } from "./anchors.js";
|
|
3
|
+
import { lineCol } from "./state.js";
|
|
4
|
+
import { resolveTagHandle } from "./tags.js";
|
|
5
|
+
|
|
6
|
+
//#region src/internal/composer/scalars.ts
|
|
7
|
+
const NULL_RE = /^(?:null|Null|NULL|~)$/;
|
|
8
|
+
const TRUE_RE = /^(?:true|True|TRUE)$/;
|
|
9
|
+
const FALSE_RE = /^(?:false|False|FALSE)$/;
|
|
10
|
+
const INT_RE = /^[-+]?[0-9]+$/;
|
|
11
|
+
const OCT_RE = /^0o[0-7]+$/;
|
|
12
|
+
const HEX_RE = /^0x[\dA-Fa-f]+$/;
|
|
13
|
+
const FLOAT_RE = /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?$/;
|
|
14
|
+
const INF_RE = /^[-+]?\.(?:inf|Inf|INF)$/;
|
|
15
|
+
const NAN_RE = /^\.(?:nan|NaN|NAN)$/;
|
|
16
|
+
/**
|
|
17
|
+
* Parses an integer string, returning `bigint` when the value exceeds
|
|
18
|
+
* `Number.MAX_SAFE_INTEGER` to avoid silent precision loss.
|
|
19
|
+
*/
|
|
20
|
+
function safeParseInt(value, radix) {
|
|
21
|
+
const n = Number.parseInt(value, radix);
|
|
22
|
+
if (Number.isSafeInteger(n)) return n;
|
|
23
|
+
return BigInt(`${radix === 16 ? "0x" : radix === 8 ? "0o" : ""}${value}`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Classify a plain scalar's numeric form for duplicate-key identity. `1` is an
|
|
27
|
+
* `!!int` and `1.0` an `!!float` even though both resolve to the JS number `1`,
|
|
28
|
+
* so they are distinct mapping keys; `1` and `0x1` are both `!!int 1` and so
|
|
29
|
+
* are the same key. Returns `null` for non-numeric plain text. Uses the same
|
|
30
|
+
* regexes as {@link resolvePlainScalar} so the classification always agrees
|
|
31
|
+
* with how the value was resolved.
|
|
32
|
+
*/
|
|
33
|
+
function classifyPlainNumeric(raw) {
|
|
34
|
+
const t = raw.trim();
|
|
35
|
+
if (OCT_RE.test(t) || HEX_RE.test(t) || INT_RE.test(t)) return "int";
|
|
36
|
+
if (INF_RE.test(t) || NAN_RE.test(t) || FLOAT_RE.test(t)) return "float";
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function resolvePlainScalar(value) {
|
|
40
|
+
if (value === "" || NULL_RE.test(value)) return null;
|
|
41
|
+
if (TRUE_RE.test(value)) return true;
|
|
42
|
+
if (FALSE_RE.test(value)) return false;
|
|
43
|
+
if (OCT_RE.test(value)) return safeParseInt(value.slice(2), 8);
|
|
44
|
+
if (HEX_RE.test(value)) return safeParseInt(value.slice(2), 16);
|
|
45
|
+
if (INT_RE.test(value)) return safeParseInt(value, 10);
|
|
46
|
+
if (INF_RE.test(value)) return value.startsWith("-") ? -Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
47
|
+
if (NAN_RE.test(value)) return NaN;
|
|
48
|
+
if (FLOAT_RE.test(value)) {
|
|
49
|
+
const n = Number.parseFloat(value);
|
|
50
|
+
if (!Number.isNaN(n)) return n;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function resolveTaggedScalar(rawValue, tag) {
|
|
55
|
+
switch (tag) {
|
|
56
|
+
case "!!str":
|
|
57
|
+
case "tag:yaml.org,2002:str": return rawValue;
|
|
58
|
+
case "!!int":
|
|
59
|
+
case "tag:yaml.org,2002:int": {
|
|
60
|
+
if (OCT_RE.test(rawValue)) return Number.parseInt(rawValue.slice(2), 8);
|
|
61
|
+
if (HEX_RE.test(rawValue)) return Number.parseInt(rawValue.slice(2), 16);
|
|
62
|
+
const n = Number.parseInt(rawValue, 10);
|
|
63
|
+
return Number.isNaN(n) ? rawValue : n;
|
|
64
|
+
}
|
|
65
|
+
case "!!float":
|
|
66
|
+
case "tag:yaml.org,2002:float": {
|
|
67
|
+
if (INF_RE.test(rawValue)) return rawValue.startsWith("-") ? -Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
68
|
+
if (NAN_RE.test(rawValue)) return NaN;
|
|
69
|
+
const n = Number.parseFloat(rawValue);
|
|
70
|
+
return Number.isNaN(n) ? rawValue : n;
|
|
71
|
+
}
|
|
72
|
+
case "!!bool":
|
|
73
|
+
case "tag:yaml.org,2002:bool":
|
|
74
|
+
if (TRUE_RE.test(rawValue)) return true;
|
|
75
|
+
if (FALSE_RE.test(rawValue)) return false;
|
|
76
|
+
return rawValue;
|
|
77
|
+
case "!!null":
|
|
78
|
+
case "tag:yaml.org,2002:null": return null;
|
|
79
|
+
default: return rawValue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function resolveScalar(rawValue, style, tag, state) {
|
|
83
|
+
if (tag) return resolveTaggedScalar(rawValue, state ? resolveTagHandle(tag, state) : tag);
|
|
84
|
+
if (style !== "plain") return rawValue;
|
|
85
|
+
return resolvePlainScalar(rawValue);
|
|
86
|
+
}
|
|
87
|
+
function getScalarStyle(node) {
|
|
88
|
+
if (node.type === "block-scalar") return node.source.trimStart()[0] === ">" ? "block-folded" : "block-literal";
|
|
89
|
+
const first = node.source[0];
|
|
90
|
+
if (first === "'") return "single-quoted";
|
|
91
|
+
if (first === "\"") return "double-quoted";
|
|
92
|
+
return "plain";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extracts the chomp indicator from a block scalar's header.
|
|
96
|
+
* Returns "strip" for `-`, "keep" for `+`, "clip" otherwise (default).
|
|
97
|
+
* Returns undefined for non-block scalars.
|
|
98
|
+
*/
|
|
99
|
+
function getBlockChomp(node) {
|
|
100
|
+
if (node.type !== "block-scalar") return void 0;
|
|
101
|
+
const src = node.source.trimStart();
|
|
102
|
+
const headerEnd = src.indexOf("\n");
|
|
103
|
+
const header = headerEnd === -1 ? src : src.slice(0, headerEnd);
|
|
104
|
+
if (header.includes("+")) return "keep";
|
|
105
|
+
if (header.includes("-")) return "strip";
|
|
106
|
+
return "clip";
|
|
107
|
+
}
|
|
108
|
+
function getScalarValue(node, fullText) {
|
|
109
|
+
if (node.type === "block-scalar") return decodeBlockScalar(node.source, fullText, node.offset);
|
|
110
|
+
const style = getScalarStyle(node);
|
|
111
|
+
if (style === "single-quoted") return decodeSingleQuoted(node.source);
|
|
112
|
+
if (style === "double-quoted") return decodeDoubleQuoted(node.source);
|
|
113
|
+
return decodePlainScalar(node.source);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* YAML 1.2 §6.5 flow line folding for plain scalars.
|
|
117
|
+
* - Bare newline between non-empty lines becomes a space (fold)
|
|
118
|
+
* - Empty line(s) preserved as newline characters
|
|
119
|
+
* - Leading whitespace on continuation lines trimmed
|
|
120
|
+
* - Trailing whitespace before newlines trimmed
|
|
121
|
+
*/
|
|
122
|
+
function decodePlainScalar(raw) {
|
|
123
|
+
const trimmed = raw.trim();
|
|
124
|
+
if (!trimmed.includes("\n")) return trimmed;
|
|
125
|
+
return foldFlowLines(trimmed);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Decode single-quoted scalar with flow folding.
|
|
129
|
+
* Only escape: '' → '
|
|
130
|
+
* Bare newlines follow flow folding rules.
|
|
131
|
+
*/
|
|
132
|
+
function decodeSingleQuoted(raw) {
|
|
133
|
+
const unescaped = raw.slice(1, -1).replace(/''/g, "'");
|
|
134
|
+
if (!unescaped.includes("\n")) return unescaped;
|
|
135
|
+
return foldFlowLines(unescaped);
|
|
136
|
+
}
|
|
137
|
+
function decodeDoubleQuoted(raw) {
|
|
138
|
+
const inner = raw.slice(1, -1);
|
|
139
|
+
let result = "";
|
|
140
|
+
let significantEnd = 0;
|
|
141
|
+
let i = 0;
|
|
142
|
+
while (i < inner.length) {
|
|
143
|
+
const ch = inner[i];
|
|
144
|
+
if (ch === "\\") {
|
|
145
|
+
i++;
|
|
146
|
+
const esc = inner[i];
|
|
147
|
+
switch (esc) {
|
|
148
|
+
case "\\":
|
|
149
|
+
result += "\\";
|
|
150
|
+
break;
|
|
151
|
+
case "\"":
|
|
152
|
+
result += "\"";
|
|
153
|
+
break;
|
|
154
|
+
case "/":
|
|
155
|
+
result += "/";
|
|
156
|
+
break;
|
|
157
|
+
case "b":
|
|
158
|
+
result += "\b";
|
|
159
|
+
break;
|
|
160
|
+
case "f":
|
|
161
|
+
result += "\f";
|
|
162
|
+
break;
|
|
163
|
+
case "n":
|
|
164
|
+
result += "\n";
|
|
165
|
+
break;
|
|
166
|
+
case "r":
|
|
167
|
+
result += "\r";
|
|
168
|
+
break;
|
|
169
|
+
case "t":
|
|
170
|
+
result += " ";
|
|
171
|
+
break;
|
|
172
|
+
case "0":
|
|
173
|
+
result += "\0";
|
|
174
|
+
break;
|
|
175
|
+
case "a":
|
|
176
|
+
result += "\x07";
|
|
177
|
+
break;
|
|
178
|
+
case "e":
|
|
179
|
+
result += "\x1B";
|
|
180
|
+
break;
|
|
181
|
+
case "v":
|
|
182
|
+
result += "\v";
|
|
183
|
+
break;
|
|
184
|
+
case " ":
|
|
185
|
+
result += " ";
|
|
186
|
+
break;
|
|
187
|
+
case "N":
|
|
188
|
+
result += "
";
|
|
189
|
+
break;
|
|
190
|
+
case "_":
|
|
191
|
+
result += "\xA0";
|
|
192
|
+
break;
|
|
193
|
+
case "L":
|
|
194
|
+
result += "\u2028";
|
|
195
|
+
break;
|
|
196
|
+
case "P":
|
|
197
|
+
result += "\u2029";
|
|
198
|
+
break;
|
|
199
|
+
case "x": {
|
|
200
|
+
const hex = inner.slice(i + 1, i + 3);
|
|
201
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
202
|
+
i += 2;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "u": {
|
|
206
|
+
const hex = inner.slice(i + 1, i + 5);
|
|
207
|
+
result += String.fromCodePoint(Number.parseInt(hex, 16));
|
|
208
|
+
i += 4;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "U": {
|
|
212
|
+
const hex = inner.slice(i + 1, i + 9);
|
|
213
|
+
const cp = Number.parseInt(hex, 16);
|
|
214
|
+
result += cp <= 1114111 ? String.fromCodePoint(cp) : "�";
|
|
215
|
+
i += 8;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "\n":
|
|
219
|
+
i++;
|
|
220
|
+
while (i < inner.length && (inner[i] === " " || inner[i] === " ")) i++;
|
|
221
|
+
continue;
|
|
222
|
+
case "\r":
|
|
223
|
+
i++;
|
|
224
|
+
if (i < inner.length && inner[i] === "\n") i++;
|
|
225
|
+
while (i < inner.length && (inner[i] === " " || inner[i] === " ")) i++;
|
|
226
|
+
continue;
|
|
227
|
+
default: result += esc === void 0 ? "\\" : esc;
|
|
228
|
+
}
|
|
229
|
+
significantEnd = result.length;
|
|
230
|
+
i++;
|
|
231
|
+
} else if (ch === "\n" || ch === "\r" && inner[i + 1] === "\n") {
|
|
232
|
+
result = result.slice(0, significantEnd);
|
|
233
|
+
i += ch === "\r" ? 2 : 1;
|
|
234
|
+
while (i < inner.length && (inner[i] === " " || inner[i] === " ")) i++;
|
|
235
|
+
if (i < inner.length && (inner[i] === "\n" || inner[i] === "\r")) while (i < inner.length && (inner[i] === "\n" || inner[i] === "\r")) {
|
|
236
|
+
result += "\n";
|
|
237
|
+
i += inner[i] === "\r" && inner[i + 1] === "\n" ? 2 : 1;
|
|
238
|
+
while (i < inner.length && (inner[i] === " " || inner[i] === " ")) i++;
|
|
239
|
+
}
|
|
240
|
+
else result += " ";
|
|
241
|
+
significantEnd = result.length;
|
|
242
|
+
} else {
|
|
243
|
+
result += ch;
|
|
244
|
+
if (ch !== " " && ch !== " ") significantEnd = result.length;
|
|
245
|
+
i++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Apply YAML 1.2 §6.5 flow line folding to a string.
|
|
252
|
+
* - Split into lines, trim trailing whitespace from each
|
|
253
|
+
* - Newline between non-empty lines becomes a space
|
|
254
|
+
* - Empty line preserved as newline in output
|
|
255
|
+
* - Leading whitespace (indentation) on continuation lines trimmed
|
|
256
|
+
*/
|
|
257
|
+
function foldFlowLines(text) {
|
|
258
|
+
const lines = text.split("\n");
|
|
259
|
+
let result = "";
|
|
260
|
+
for (let i = 0; i < lines.length; i++) {
|
|
261
|
+
const line = lines[i] ?? "";
|
|
262
|
+
if (i === 0) {
|
|
263
|
+
result += line.replace(/[ \t]+$/, "");
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const isLast = i === lines.length - 1;
|
|
267
|
+
const trimmed = isLast ? line.trimStart() : line.trim();
|
|
268
|
+
if (trimmed === "") if (isLast) {
|
|
269
|
+
if (result.length === 0 || result[result.length - 1] !== "\n") result += " ";
|
|
270
|
+
} else result += "\n";
|
|
271
|
+
else {
|
|
272
|
+
if (result.length > 0 && result[result.length - 1] !== "\n") result += " ";
|
|
273
|
+
result += trimmed;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Collect a multi-line plain scalar key from consecutive CST children.
|
|
280
|
+
* Like `collectMultilinePlainScalar`, but for keys: collects plain scalars
|
|
281
|
+
* up until the `:` value separator, merging them with flow line folding.
|
|
282
|
+
* Returns the folded key text and the index after the last consumed child.
|
|
283
|
+
*/
|
|
284
|
+
function collectMultilineKey(children, startIdx) {
|
|
285
|
+
const first = children[startIdx];
|
|
286
|
+
if (first?.type !== "flow-scalar") return {
|
|
287
|
+
value: first?.source.trim() ?? "",
|
|
288
|
+
nextIdx: startIdx + 1
|
|
289
|
+
};
|
|
290
|
+
const parts = [first.source.trim()];
|
|
291
|
+
let idx = startIdx + 1;
|
|
292
|
+
while (idx < children.length) {
|
|
293
|
+
const child = children[idx];
|
|
294
|
+
if (!child) break;
|
|
295
|
+
if (child.type === "newline" || child.type === "whitespace" && child.source.trim() === "") {
|
|
296
|
+
idx++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (child.type === "whitespace" && (child.source === ":" || child.source === ",")) break;
|
|
300
|
+
if (child.type === "flow-scalar" && getScalarStyle(child) === "plain") {
|
|
301
|
+
parts.push(child.source.trim());
|
|
302
|
+
idx++;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
if (parts.length === 1) return {
|
|
308
|
+
value: parts[0] ?? "",
|
|
309
|
+
nextIdx: idx
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
value: foldFlowLines(parts.join("\n")),
|
|
313
|
+
nextIdx: idx
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Extract the trimmed content of the line at `offset` in `text`.
|
|
318
|
+
* Returns the trimmed text and the offset of the next line (or EOF).
|
|
319
|
+
*/
|
|
320
|
+
function extractLineContent(text, offset) {
|
|
321
|
+
let lineStart = offset;
|
|
322
|
+
while (lineStart > 0 && text[lineStart - 1] !== "\n") lineStart--;
|
|
323
|
+
let lineEnd = offset;
|
|
324
|
+
while (lineEnd < text.length && text[lineEnd] !== "\n" && text[lineEnd] !== "\r") lineEnd++;
|
|
325
|
+
return {
|
|
326
|
+
lineText: text.slice(lineStart, lineEnd).trim(),
|
|
327
|
+
lineEndOffset: lineEnd
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Skip all children whose offset falls on the same line as `lineOffset`.
|
|
332
|
+
* Returns the index of the first child that is past the line end.
|
|
333
|
+
*/
|
|
334
|
+
function skipChildrenOnLine(children, startIdx, lineEndOffset) {
|
|
335
|
+
let idx = startIdx;
|
|
336
|
+
while (idx < children.length) {
|
|
337
|
+
const c = children[idx];
|
|
338
|
+
if (!c) break;
|
|
339
|
+
if (c.type === "newline" && c.offset >= lineEndOffset) break;
|
|
340
|
+
if (c.offset > lineEndOffset) break;
|
|
341
|
+
idx++;
|
|
342
|
+
}
|
|
343
|
+
return idx;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Collect a multi-line plain scalar from consecutive CST children.
|
|
347
|
+
* Starting from a plain flow-scalar at `startIdx`, look ahead through
|
|
348
|
+
* newlines and whitespace for more plain flow-scalars that continue the
|
|
349
|
+
* same value. Returns the folded scalar text and the index after the last
|
|
350
|
+
* consumed child.
|
|
351
|
+
*
|
|
352
|
+
* A continuation scalar must:
|
|
353
|
+
* - Be a plain flow-scalar (not quoted)
|
|
354
|
+
* - NOT be followed by a value-sep (`:`) — that makes it a mapping key
|
|
355
|
+
* - Be separated from the previous scalar only by newlines/whitespace
|
|
356
|
+
*
|
|
357
|
+
* When the lexer mis-tokenizes continuation line content as anchors, tags,
|
|
358
|
+
* aliases, directives, or block-seq entries, this function detects such
|
|
359
|
+
* lines and extracts the raw source text as continuation parts (3MYT, FBC9,
|
|
360
|
+
* XLQ9, AB8U).
|
|
361
|
+
*
|
|
362
|
+
* `endOffset` is the source end of the last consumed fragment, so callers
|
|
363
|
+
* can span the composed scalar node across the whole folded value (the
|
|
364
|
+
* sourceMultiline decoration pass then stamps it from the span).
|
|
365
|
+
*/
|
|
366
|
+
function collectMultilinePlainScalar(children, startIdx, minContinuationColumn, sourceText) {
|
|
367
|
+
const first = children[startIdx];
|
|
368
|
+
if (first?.type !== "flow-scalar") return {
|
|
369
|
+
value: first?.source.trim() ?? "",
|
|
370
|
+
nextIdx: startIdx + 1,
|
|
371
|
+
partsCount: 1,
|
|
372
|
+
endOffset: first === void 0 ? 0 : first.offset + first.length
|
|
373
|
+
};
|
|
374
|
+
if (getScalarStyle(first) !== "plain") return {
|
|
375
|
+
value: getScalarValue(first),
|
|
376
|
+
nextIdx: startIdx + 1,
|
|
377
|
+
partsCount: 1,
|
|
378
|
+
endOffset: first.offset + first.length
|
|
379
|
+
};
|
|
380
|
+
const parts = [first.source.trim()];
|
|
381
|
+
let endOffset = first.offset + first.length;
|
|
382
|
+
let emptyLines = 0;
|
|
383
|
+
let idx = startIdx + 1;
|
|
384
|
+
let sawNewline = false;
|
|
385
|
+
while (idx < children.length) {
|
|
386
|
+
const child = children[idx];
|
|
387
|
+
if (!child) break;
|
|
388
|
+
if (child.type === "newline") {
|
|
389
|
+
emptyLines++;
|
|
390
|
+
sawNewline = true;
|
|
391
|
+
idx++;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (child.type === "whitespace") {
|
|
395
|
+
if (child.source === ":" || child.source === "?" || child.source === "-") break;
|
|
396
|
+
idx++;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (child.type === "comment") break;
|
|
400
|
+
if (child.type === "flow-scalar" && getScalarStyle(child) === "plain") {
|
|
401
|
+
if (hasValueSepAfterInList(children, idx + 1)) break;
|
|
402
|
+
if (hasBlockMapAfterInList(children, idx + 1)) break;
|
|
403
|
+
if (minContinuationColumn !== void 0 && sourceText) {
|
|
404
|
+
if (lineCol(sourceText, child.offset).column < minContinuationColumn) break;
|
|
405
|
+
}
|
|
406
|
+
if (emptyLines > 1) for (let e = 0; e < emptyLines - 1; e++) parts.push("");
|
|
407
|
+
parts.push(child.source.trim());
|
|
408
|
+
endOffset = child.offset + child.length;
|
|
409
|
+
emptyLines = 0;
|
|
410
|
+
sawNewline = false;
|
|
411
|
+
idx++;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (sawNewline && sourceText && child.type !== "flow-scalar" && child.type !== "block-scalar") {
|
|
415
|
+
const childCol = lineCol(sourceText, child.offset).column;
|
|
416
|
+
const isDirectiveContinuation = child.type === "directive";
|
|
417
|
+
if (minContinuationColumn !== void 0 && !isDirectiveContinuation && childCol < minContinuationColumn) break;
|
|
418
|
+
if (childCol > 0 || isDirectiveContinuation) {
|
|
419
|
+
const { lineText, lineEndOffset } = extractLineContent(sourceText, child.offset);
|
|
420
|
+
if (lineText.length > 0) {
|
|
421
|
+
if (emptyLines > 1) for (let e = 0; e < emptyLines - 1; e++) parts.push("");
|
|
422
|
+
parts.push(lineText);
|
|
423
|
+
let contentEnd = lineEndOffset;
|
|
424
|
+
while (contentEnd > 0 && (sourceText[contentEnd - 1] === " " || sourceText[contentEnd - 1] === " ")) contentEnd--;
|
|
425
|
+
endOffset = contentEnd;
|
|
426
|
+
emptyLines = 0;
|
|
427
|
+
sawNewline = false;
|
|
428
|
+
idx = skipChildrenOnLine(children, idx, lineEndOffset);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
if (parts.length === 1) return {
|
|
436
|
+
value: parts[0] ?? "",
|
|
437
|
+
nextIdx: idx,
|
|
438
|
+
partsCount: 1,
|
|
439
|
+
endOffset
|
|
440
|
+
};
|
|
441
|
+
return {
|
|
442
|
+
value: foldFlowLines(parts.join("\n")),
|
|
443
|
+
nextIdx: idx,
|
|
444
|
+
partsCount: parts.length,
|
|
445
|
+
endOffset
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Find the index of the next non-trivia child (skips newline, whitespace, comment).
|
|
450
|
+
* If `stopAtDash` is true, returns null when a `-` indicator is encountered before
|
|
451
|
+
* any significant child (used to avoid merging across sequence entry boundaries).
|
|
452
|
+
*/
|
|
453
|
+
function findNextSignificantChild(children, startIdx, stopAtDash = false) {
|
|
454
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
455
|
+
const c = children[j];
|
|
456
|
+
if (!c) continue;
|
|
457
|
+
if (c.type === "newline" || c.type === "comment") continue;
|
|
458
|
+
if (c.type === "whitespace") {
|
|
459
|
+
if (stopAtDash && c.source.trim() === "-") return null;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
return j;
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Check if a value separator (`:`) follows in a CST children list,
|
|
468
|
+
* skipping whitespace and newlines.
|
|
469
|
+
*/
|
|
470
|
+
function hasValueSepAfterInList(children, startIdx) {
|
|
471
|
+
return findValueSepOffset(children, startIdx) >= 0;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Check if the next non-trivia child is a block-map (indicating that the
|
|
475
|
+
* preceding scalar is the first key of a nested implicit mapping). Returns
|
|
476
|
+
* false if a sibling `:` value-sep is encountered first, since that means
|
|
477
|
+
* the scalar is a key at the current level (not a nested mapping start).
|
|
478
|
+
*/
|
|
479
|
+
function hasBlockMapAfterInList(children, startIdx) {
|
|
480
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
481
|
+
const c = children[j];
|
|
482
|
+
if (!c) continue;
|
|
483
|
+
if (c.type === "newline" || c.type === "comment") continue;
|
|
484
|
+
if (c.type === "whitespace") {
|
|
485
|
+
if (c.source === ":") return false;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
return c.type === "block-map";
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
/** Find the offset of the next ":" value separator in a CST children list, or -1 if none. */
|
|
493
|
+
function findValueSepOffset(children, startIdx) {
|
|
494
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
495
|
+
const c = children[j];
|
|
496
|
+
if (!c) continue;
|
|
497
|
+
if (c.type === "newline" || c.type === "comment") continue;
|
|
498
|
+
if (c.type === "whitespace") {
|
|
499
|
+
if (c.source === ":") return c.offset;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
return -1;
|
|
503
|
+
}
|
|
504
|
+
return -1;
|
|
505
|
+
}
|
|
506
|
+
/** Check if a ":" value-sep exists between startIdx (inclusive) and endIdx (exclusive). */
|
|
507
|
+
function hasValueSepBetween(children, startIdx, endIdx) {
|
|
508
|
+
for (let j = startIdx; j < endIdx; j++) {
|
|
509
|
+
const c = children[j];
|
|
510
|
+
if (!c) continue;
|
|
511
|
+
if (c.type === "whitespace" && c.source === ":") return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Returns true when the first non-trivia child of a block-map CST node is a
|
|
517
|
+
* `:` value separator (i.e., the block map begins with an implicit empty key
|
|
518
|
+
* followed by a value indicator). Used to decide whether a pending anchor/tag
|
|
519
|
+
* belongs to that empty key rather than to the block map itself.
|
|
520
|
+
*/
|
|
521
|
+
function blockMapStartsWithValueSep(blockMap) {
|
|
522
|
+
for (const c of blockMap.children ?? []) {
|
|
523
|
+
if (!c) continue;
|
|
524
|
+
if (c.type === "newline" || c.type === "comment") continue;
|
|
525
|
+
if (c.type === "whitespace") {
|
|
526
|
+
if (c.source === ":") return true;
|
|
527
|
+
if (c.source.trim() === "") continue;
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Like `hasValueSepAfterInList`, but also skips over plain flow-scalars
|
|
536
|
+
* that appear after a newline. Used to detect multi-line keys:
|
|
537
|
+
* `multi\n line: value` where `:` comes after continuation plain scalars.
|
|
538
|
+
* Only allows skipping plain scalars that were preceded by a newline,
|
|
539
|
+
* preventing false matches across comma-delimited entries on the same line.
|
|
540
|
+
*/
|
|
541
|
+
function hasValueSepThroughPlainScalars(children, startIdx) {
|
|
542
|
+
let sawNewline = false;
|
|
543
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
544
|
+
const c = children[j];
|
|
545
|
+
if (!c) continue;
|
|
546
|
+
if (c.type === "newline") {
|
|
547
|
+
sawNewline = true;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (c.type === "comment") continue;
|
|
551
|
+
if (c.type === "whitespace") {
|
|
552
|
+
if (c.source === ":") return true;
|
|
553
|
+
if (c.source === ",") return false;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (sawNewline && c.type === "flow-scalar" && getScalarStyle(c) === "plain") continue;
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
/** Find the next non-trivia CST child in a list, returning the node and its index. */
|
|
562
|
+
function findNextContentInList(children, startIdx) {
|
|
563
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
564
|
+
const c = children[j];
|
|
565
|
+
if (!c) continue;
|
|
566
|
+
if (c.type === "whitespace" || c.type === "newline" || c.type === "comment") continue;
|
|
567
|
+
return {
|
|
568
|
+
node: c,
|
|
569
|
+
idx: j
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
function findFirstContent(children) {
|
|
575
|
+
for (const c of children) {
|
|
576
|
+
if (!c) continue;
|
|
577
|
+
if (c.type === "whitespace" && c.source.trim() === "") continue;
|
|
578
|
+
if (c.type === "newline") continue;
|
|
579
|
+
return c;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function findLastContent(children) {
|
|
583
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
584
|
+
const c = children[i];
|
|
585
|
+
if (!c) continue;
|
|
586
|
+
if (c.type === "whitespace" && c.source.trim() === "") continue;
|
|
587
|
+
if (c.type === "newline") continue;
|
|
588
|
+
return c;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Find the next content child (skipping trivia AND anchor/tag properties),
|
|
593
|
+
* or null. Used at the document level where properties precede content.
|
|
594
|
+
*/
|
|
595
|
+
function findNextContentChild(children, startIdx) {
|
|
596
|
+
for (let i = startIdx; i < children.length; i++) {
|
|
597
|
+
const c = children[i];
|
|
598
|
+
if (!c) continue;
|
|
599
|
+
if (c.type === "whitespace" || c.type === "newline" || c.type === "comment" || c.type === "anchor" || c.type === "tag") continue;
|
|
600
|
+
return c;
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
function indexOfChild(children, target) {
|
|
605
|
+
for (let i = 0; i < children.length; i++) if (children[i] === target) return i;
|
|
606
|
+
return -1;
|
|
607
|
+
}
|
|
608
|
+
/** Check if there's a value separator ":" after startIdx (skipping only whitespace). */
|
|
609
|
+
function hasValueSepAfter(children, startIdx) {
|
|
610
|
+
for (let j = startIdx; j < children.length; j++) {
|
|
611
|
+
const c = children[j];
|
|
612
|
+
if (!c) continue;
|
|
613
|
+
if (c.type === "whitespace" && c.source === ":") return true;
|
|
614
|
+
if (c.type === "whitespace" && c.source !== ":") continue;
|
|
615
|
+
if (c.type === "newline") continue;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Scan backward in the full document text from a block scalar indicator's
|
|
622
|
+
* position to find the parent context indentation level n. This handles:
|
|
623
|
+
* - Same-line ":" (mapping value): n = key's column indent
|
|
624
|
+
* - Same-line "-" (seq entry): n = column of "-"
|
|
625
|
+
* - Own-line (preceded by newline, tag, anchor): scan further back across
|
|
626
|
+
* lines to find the ":" or "-" that introduced this value
|
|
627
|
+
*/
|
|
628
|
+
function findParentIndent(fullText, indicatorOffset) {
|
|
629
|
+
let scanBack = indicatorOffset - 1;
|
|
630
|
+
while (scanBack >= 0 && (fullText[scanBack] === " " || fullText[scanBack] === " ")) scanBack--;
|
|
631
|
+
if (scanBack >= 0 && fullText[scanBack] === ":") return findKeyIndent(fullText, scanBack);
|
|
632
|
+
if (scanBack >= 0 && fullText[scanBack] === "-") return findColOnLine(fullText, scanBack);
|
|
633
|
+
while (scanBack >= 0) {
|
|
634
|
+
const ch = fullText[scanBack];
|
|
635
|
+
if (ch === ":") return findKeyIndent(fullText, scanBack);
|
|
636
|
+
if (ch === "-") {
|
|
637
|
+
const afterDash = scanBack + 1;
|
|
638
|
+
if (afterDash >= fullText.length || fullText[afterDash] === " " || fullText[afterDash] === " " || fullText[afterDash] === "\n" || fullText[afterDash] === "\r") return findColOnLine(fullText, scanBack);
|
|
639
|
+
}
|
|
640
|
+
scanBack--;
|
|
641
|
+
}
|
|
642
|
+
return 0;
|
|
643
|
+
}
|
|
644
|
+
/** Find the column of a character on its line. */
|
|
645
|
+
function findColOnLine(text, pos) {
|
|
646
|
+
let lineStart = pos;
|
|
647
|
+
while (lineStart > 0 && text[lineStart - 1] !== "\n" && text[lineStart - 1] !== "\r") lineStart--;
|
|
648
|
+
return pos - lineStart;
|
|
649
|
+
}
|
|
650
|
+
/** Find the key indentation for a mapping ":" at the given position. */
|
|
651
|
+
function findKeyIndent(text, colonPos) {
|
|
652
|
+
let lineStart = colonPos;
|
|
653
|
+
while (lineStart > 0 && text[lineStart - 1] !== "\n" && text[lineStart - 1] !== "\r") lineStart--;
|
|
654
|
+
let spaces = 0;
|
|
655
|
+
while (lineStart + spaces < text.length && text[lineStart + spaces] === " ") spaces++;
|
|
656
|
+
if (lineStart + spaces < text.length && text[lineStart + spaces] === "-") {
|
|
657
|
+
const afterDash = lineStart + spaces + 1;
|
|
658
|
+
if (afterDash < text.length && (text[afterDash] === " " || text[afterDash] === " ")) return spaces + 2;
|
|
659
|
+
}
|
|
660
|
+
return spaces;
|
|
661
|
+
}
|
|
662
|
+
function decodeBlockScalar(raw, fullText, nodeOffset) {
|
|
663
|
+
const firstChar = raw.trimStart()[0];
|
|
664
|
+
const isFolded = firstChar === ">";
|
|
665
|
+
let i = raw.indexOf(firstChar === ">" ? ">" : "|");
|
|
666
|
+
if (i < 0) return "";
|
|
667
|
+
i++;
|
|
668
|
+
let chomp = "clip";
|
|
669
|
+
let explicitIndent = 0;
|
|
670
|
+
for (let hc = 0; hc < 2 && i < raw.length && raw[i] !== "\n" && raw[i] !== "\r"; hc++) {
|
|
671
|
+
const ch = raw[i];
|
|
672
|
+
if (ch === "-") {
|
|
673
|
+
chomp = "strip";
|
|
674
|
+
i++;
|
|
675
|
+
} else if (ch === "+") {
|
|
676
|
+
chomp = "keep";
|
|
677
|
+
i++;
|
|
678
|
+
} else if (ch !== void 0 && ch >= "1" && ch <= "9") {
|
|
679
|
+
explicitIndent = Number.parseInt(ch, 10);
|
|
680
|
+
i++;
|
|
681
|
+
} else break;
|
|
682
|
+
}
|
|
683
|
+
while (i < raw.length && raw[i] !== "\n" && raw[i] !== "\r") i++;
|
|
684
|
+
if (i < raw.length) if (raw[i] === "\r" && raw[i + 1] === "\n") i += 2;
|
|
685
|
+
else i++;
|
|
686
|
+
let contentIndent = explicitIndent;
|
|
687
|
+
let foundContent = explicitIndent > 0;
|
|
688
|
+
if (explicitIndent > 0 && fullText !== void 0 && nodeOffset !== void 0) {
|
|
689
|
+
contentIndent = findParentIndent(fullText, nodeOffset) + explicitIndent;
|
|
690
|
+
foundContent = true;
|
|
691
|
+
} else if (contentIndent === 0) {
|
|
692
|
+
let scanAhead = i;
|
|
693
|
+
while (scanAhead < raw.length) {
|
|
694
|
+
let spaces = 0;
|
|
695
|
+
while (scanAhead < raw.length && raw[scanAhead] === " ") {
|
|
696
|
+
spaces++;
|
|
697
|
+
scanAhead++;
|
|
698
|
+
}
|
|
699
|
+
if (scanAhead >= raw.length || raw[scanAhead] === "\n" || raw[scanAhead] === "\r") {
|
|
700
|
+
if (scanAhead < raw.length) {
|
|
701
|
+
scanAhead++;
|
|
702
|
+
if (raw[scanAhead - 1] === "\r" && scanAhead < raw.length && raw[scanAhead] === "\n") scanAhead++;
|
|
703
|
+
}
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
contentIndent = spaces;
|
|
707
|
+
foundContent = true;
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (!foundContent) {
|
|
712
|
+
if (chomp === "keep") {
|
|
713
|
+
let count = 0;
|
|
714
|
+
let j = i;
|
|
715
|
+
while (j < raw.length) {
|
|
716
|
+
while (j < raw.length && (raw[j] === " " || raw[j] === " ")) j++;
|
|
717
|
+
if (j >= raw.length) {
|
|
718
|
+
if (count === 0) count = 1;
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
if (raw[j] === "\n") {
|
|
722
|
+
count++;
|
|
723
|
+
j++;
|
|
724
|
+
} else if (raw[j] === "\r") {
|
|
725
|
+
count++;
|
|
726
|
+
j++;
|
|
727
|
+
if (j < raw.length && raw[j] === "\n") j++;
|
|
728
|
+
} else break;
|
|
729
|
+
}
|
|
730
|
+
return "\n".repeat(count);
|
|
731
|
+
}
|
|
732
|
+
return "";
|
|
733
|
+
}
|
|
734
|
+
const lines = [];
|
|
735
|
+
const trailingNewlines = [];
|
|
736
|
+
while (i < raw.length) {
|
|
737
|
+
let spaces = 0;
|
|
738
|
+
while (i < raw.length && raw[i] === " ") {
|
|
739
|
+
spaces++;
|
|
740
|
+
i++;
|
|
741
|
+
}
|
|
742
|
+
if (i >= raw.length || raw[i] === "\n" || raw[i] === "\r") {
|
|
743
|
+
if (spaces > contentIndent) {
|
|
744
|
+
for (const nl of trailingNewlines) lines.push(nl);
|
|
745
|
+
trailingNewlines.length = 0;
|
|
746
|
+
lines.push(" ".repeat(spaces - contentIndent));
|
|
747
|
+
} else trailingNewlines.push("");
|
|
748
|
+
if (i < raw.length) if (raw[i] === "\r" && i + 1 < raw.length && raw[i + 1] === "\n") i += 2;
|
|
749
|
+
else i++;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (spaces < contentIndent) break;
|
|
753
|
+
for (const _nl of trailingNewlines) lines.push("");
|
|
754
|
+
trailingNewlines.length = 0;
|
|
755
|
+
const extra = " ".repeat(spaces - contentIndent);
|
|
756
|
+
const contentStart = i;
|
|
757
|
+
while (i < raw.length && raw[i] !== "\n" && raw[i] !== "\r") i++;
|
|
758
|
+
lines.push(extra + raw.slice(contentStart, i));
|
|
759
|
+
if (i < raw.length) if (raw[i] === "\r" && i + 1 < raw.length && raw[i + 1] === "\n") i += 2;
|
|
760
|
+
else i++;
|
|
761
|
+
}
|
|
762
|
+
let value;
|
|
763
|
+
if (isFolded) {
|
|
764
|
+
let result = "";
|
|
765
|
+
let prevMoreIndented = false;
|
|
766
|
+
let hadContent = false;
|
|
767
|
+
for (let li = 0; li < lines.length; li++) {
|
|
768
|
+
const ln = lines[li] ?? "";
|
|
769
|
+
const isMoreIndented = ln.length > 0 && (ln[0] === " " || ln[0] === " ");
|
|
770
|
+
if (ln === "") result += "\n";
|
|
771
|
+
else if (!hadContent) {
|
|
772
|
+
result += ln;
|
|
773
|
+
prevMoreIndented = isMoreIndented;
|
|
774
|
+
hadContent = true;
|
|
775
|
+
} else {
|
|
776
|
+
if (result[result.length - 1] === "\n") if (isMoreIndented || prevMoreIndented) result += `\n${ln}`;
|
|
777
|
+
else result += ln;
|
|
778
|
+
else if (isMoreIndented || prevMoreIndented) result += `\n${ln}`;
|
|
779
|
+
else result += ` ${ln}`;
|
|
780
|
+
prevMoreIndented = isMoreIndented;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (hadContent || trailingNewlines.length > 0) {
|
|
784
|
+
if (chomp === "keep") {
|
|
785
|
+
result += "\n";
|
|
786
|
+
for (const _nl of trailingNewlines) result += "\n";
|
|
787
|
+
} else if (chomp !== "strip") result += "\n";
|
|
788
|
+
}
|
|
789
|
+
value = result;
|
|
790
|
+
} else {
|
|
791
|
+
value = lines.join("\n");
|
|
792
|
+
if (lines.length > 0 || trailingNewlines.length > 0) {
|
|
793
|
+
if (chomp === "keep") {
|
|
794
|
+
value += "\n";
|
|
795
|
+
for (const _nl of trailingNewlines) value += "\n";
|
|
796
|
+
} else if (chomp !== "strip") value += "\n";
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return value;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* 5LLU, S98Z, W9L4: per YAML 1.2 §8.1.1, leading empty lines preceding the
|
|
803
|
+
* first content line in a block scalar must satisfy l-empty(n,c) — at most
|
|
804
|
+
* n leading spaces, where n is the content indent. When the indent indicator
|
|
805
|
+
* is auto-detected from the first non-empty line, that line establishes n;
|
|
806
|
+
* any preceding empty line with more than n spaces is malformed.
|
|
807
|
+
*/
|
|
808
|
+
function validateBlockScalarLeadingEmpties(cst, state) {
|
|
809
|
+
const raw = cst.source;
|
|
810
|
+
let i = 0;
|
|
811
|
+
while (i < raw.length && raw[i] !== "\n" && raw[i] !== "\r") i++;
|
|
812
|
+
if (i < raw.length) if (raw[i] === "\r" && raw[i + 1] === "\n") i += 2;
|
|
813
|
+
else i++;
|
|
814
|
+
const emptyIndents = [];
|
|
815
|
+
while (i < raw.length) {
|
|
816
|
+
const lineStart = i;
|
|
817
|
+
let spaces = 0;
|
|
818
|
+
while (i < raw.length && raw[i] === " ") {
|
|
819
|
+
spaces++;
|
|
820
|
+
i++;
|
|
821
|
+
}
|
|
822
|
+
if (i >= raw.length || raw[i] === "\n" || raw[i] === "\r") {
|
|
823
|
+
emptyIndents.push({
|
|
824
|
+
indent: spaces,
|
|
825
|
+
offsetInRaw: lineStart
|
|
826
|
+
});
|
|
827
|
+
if (i < raw.length) if (raw[i] === "\r" && raw[i + 1] === "\n") i += 2;
|
|
828
|
+
else i++;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
const contentIndent = spaces;
|
|
832
|
+
for (const empty of emptyIndents) if (empty.indent > contentIndent) {
|
|
833
|
+
const offset = cst.offset + empty.offsetInRaw;
|
|
834
|
+
state.errors.push({
|
|
835
|
+
code: "InvalidIndentation",
|
|
836
|
+
message: "Block scalar leading empty line cannot be more indented than the first content line",
|
|
837
|
+
offset,
|
|
838
|
+
length: empty.indent
|
|
839
|
+
});
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function makeScalar(cst, state, meta) {
|
|
846
|
+
const style = getScalarStyle(cst);
|
|
847
|
+
if (style === "block-literal" || style === "block-folded") validateBlockScalarLeadingEmpties(cst, state);
|
|
848
|
+
const rawValue = getScalarValue(cst, state.text);
|
|
849
|
+
const value = resolveScalar(rawValue, style, meta?.tag, state);
|
|
850
|
+
const chomp = getBlockChomp(cst);
|
|
851
|
+
const needsRaw = style === "plain" && typeof value !== "string" && value !== void 0 && shouldPreserveRaw(rawValue, value);
|
|
852
|
+
const scalar = new YamlScalar({
|
|
853
|
+
value,
|
|
854
|
+
style,
|
|
855
|
+
offset: cst.offset,
|
|
856
|
+
length: cst.length,
|
|
857
|
+
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
858
|
+
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
859
|
+
...meta?.comment !== void 0 ? { comment: meta.comment } : {},
|
|
860
|
+
...chomp !== void 0 ? { chomp } : {},
|
|
861
|
+
...needsRaw ? { raw: rawValue } : {}
|
|
862
|
+
});
|
|
863
|
+
if (meta?.anchor) registerAnchor(scalar, meta.anchor, state, cst.offset);
|
|
864
|
+
return scalar;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Returns true when the scalar's source representation should be preserved
|
|
868
|
+
* for canonical round-trip — i.e. the source form differs from `String(value)`
|
|
869
|
+
* but resolves to the same value.
|
|
870
|
+
*
|
|
871
|
+
* Special-float values (NaN, +/-Infinity) are excluded: their canonical YAML
|
|
872
|
+
* spelling is the lowercase `.inf` / `.nan` form per spec §10.3, so source
|
|
873
|
+
* variants like `.INF` or `.NaN` should normalize on round-trip rather than
|
|
874
|
+
* preserve.
|
|
875
|
+
*/
|
|
876
|
+
function shouldPreserveRaw(rawValue, value) {
|
|
877
|
+
if (typeof value === "number") {
|
|
878
|
+
if (Number.isNaN(value) || !Number.isFinite(value)) return false;
|
|
879
|
+
return rawValue !== String(value);
|
|
880
|
+
}
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
//#endregion
|
|
885
|
+
export { blockMapStartsWithValueSep, classifyPlainNumeric, collectMultilineKey, collectMultilinePlainScalar, findFirstContent, findLastContent, findNextContentChild, findNextContentInList, findNextSignificantChild, findValueSepOffset, foldFlowLines, getBlockChomp, getScalarStyle, getScalarValue, hasBlockMapAfterInList, hasValueSepAfter, hasValueSepAfterInList, hasValueSepBetween, hasValueSepThroughPlainScalars, indexOfChild, makeScalar, resolveScalar, shouldPreserveRaw };
|