@effected/yaml 0.6.1 → 0.8.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/README.md +78 -7
- package/Yaml.js +87 -39
- package/YamlDocument.js +25 -6
- package/YamlFormat.js +171 -20
- package/YamlLint.js +239 -0
- package/YamlLintRule.js +36 -0
- package/YamlNode.js +36 -2
- package/YamlToken.js +139 -0
- package/YamlVisitor.js +40 -5
- package/index.d.ts +439 -12
- package/index.js +4 -1
- package/internal/composer/anchors.js +2 -27
- package/internal/composer/block.js +246 -58
- package/internal/composer/comments.js +187 -0
- package/internal/composer/document.js +49 -12
- package/internal/composer/flow.js +82 -9
- package/internal/composer/scalars.js +77 -28
- package/internal/composer/state.js +16 -2
- package/internal/cst-parser.js +11 -2
- package/internal/diagnostics.js +12 -3
- package/internal/fold.js +22 -10
- package/internal/lexer.js +23 -17
- package/internal/rules/catalog.js +39 -0
- package/internal/rules/colon-spacing.js +77 -0
- package/internal/rules/comments-spacing.js +68 -0
- package/internal/rules/document-end.js +65 -0
- package/internal/rules/document-start.js +64 -0
- package/internal/rules/empty-lines.js +70 -0
- package/internal/rules/eof-newline.js +33 -0
- package/internal/rules/hyphen-spacing.js +52 -0
- package/internal/rules/indentation.js +103 -0
- package/internal/rules/key-duplicates.js +49 -0
- package/internal/rules/line-length.js +36 -0
- package/internal/rules/parse-validity.js +33 -0
- package/internal/rules/quoted-strings.js +81 -0
- package/internal/rules/trailing-spaces.js +39 -0
- package/internal/rules/truthy.js +84 -0
- package/internal/rules/util.js +92 -0
- package/internal/stringifier.js +285 -69
- package/package.json +4 -3
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/composer/comments.ts
|
|
4
|
+
/** True when there is no line break between `start` and `end` in `text`. */
|
|
5
|
+
function sameLineSpan(text, start, end) {
|
|
6
|
+
if (start < 0) return false;
|
|
7
|
+
return !text.slice(Math.max(0, start), Math.max(0, end)).includes("\n");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* True when only horizontal whitespace precedes `offset` on its line — i.e.
|
|
11
|
+
* the token at `offset` starts its own line. Purely local, so it stays
|
|
12
|
+
* correct even when a preceding node's span over-extends past line ends.
|
|
13
|
+
*/
|
|
14
|
+
function isOwnLineAt(text, offset) {
|
|
15
|
+
let i = offset - 1;
|
|
16
|
+
while (i >= 0) {
|
|
17
|
+
const ch = text[i];
|
|
18
|
+
if (ch === " " || ch === " ") {
|
|
19
|
+
i--;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
return ch === "\n" || ch === "\r";
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True when the line immediately above the line containing `offset` is blank
|
|
28
|
+
* (empty or horizontal whitespace only). Purely local — see
|
|
29
|
+
* {@link isOwnLineAt} for why span-based gap checks are not used.
|
|
30
|
+
*/
|
|
31
|
+
function hasBlankLineAbove(text, offset) {
|
|
32
|
+
return blankLineAboveStart(text, offset) >= 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Start offset of the blank line immediately above the line containing
|
|
36
|
+
* `offset`, or `-1` when that line is not blank. The offset form of
|
|
37
|
+
* {@link hasBlankLineAbove}, for callers that must locate the blank line
|
|
38
|
+
* (e.g. to test whether it falls inside a preceding scalar token's span).
|
|
39
|
+
*/
|
|
40
|
+
function blankLineAboveStart(text, offset) {
|
|
41
|
+
const lineBreak = text.lastIndexOf("\n", Math.max(0, offset - 1));
|
|
42
|
+
if (lineBreak < 0) return -1;
|
|
43
|
+
const prevBreak = text.lastIndexOf("\n", lineBreak - 1);
|
|
44
|
+
return text.slice(prevBreak + 1, lineBreak).trim() === "" ? prevBreak + 1 : -1;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The deepest trailing scalar reached by descending last-child edges from
|
|
48
|
+
* `node` (a map's last pair's value, a seq's last item) — the node whose
|
|
49
|
+
* token span a textually-following blank line could fall inside.
|
|
50
|
+
*/
|
|
51
|
+
function deepestTrailingScalar(node) {
|
|
52
|
+
let current = node;
|
|
53
|
+
for (;;) {
|
|
54
|
+
if (current instanceof YamlScalar) return current;
|
|
55
|
+
if (current instanceof YamlMap) {
|
|
56
|
+
const last = current.items[current.items.length - 1];
|
|
57
|
+
if (last === void 0) return void 0;
|
|
58
|
+
current = last.value ?? last.key;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (current instanceof YamlSeq) {
|
|
62
|
+
const last = current.items[current.items.length - 1];
|
|
63
|
+
if (last === void 0) return void 0;
|
|
64
|
+
current = last;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True when the blank line immediately above `offset` is CONTENT of the
|
|
72
|
+
* preceding keep-chomp block scalar, not a stylistic blank. Under `+`
|
|
73
|
+
* chomping the trailing line breaks are part of the scalar's VALUE (the
|
|
74
|
+
* composer already stores them there), so recording the same blank line as
|
|
75
|
+
* `spaceBefore` (or a leading-blank embed on a terminal comment run) would
|
|
76
|
+
* double-count it: emission renders both the kept newline and the stylistic
|
|
77
|
+
* blank, growing the document on every format pass. Clip/strip scalars are
|
|
78
|
+
* deliberately NOT excluded — their emission drops the trailing blank from
|
|
79
|
+
* the value, so the `spaceBefore` capture is exactly what re-creates it.
|
|
80
|
+
*
|
|
81
|
+
* `prev` is the last composed node before `offset` (a pair's value, a seq
|
|
82
|
+
* item); the check descends to its deepest trailing scalar and requires the
|
|
83
|
+
* blank line to start inside that scalar's token span.
|
|
84
|
+
*/
|
|
85
|
+
function blankAboveIsKeepChompContent(text, offset, prev) {
|
|
86
|
+
if (prev === void 0) return false;
|
|
87
|
+
const scalar = deepestTrailingScalar(prev);
|
|
88
|
+
if (scalar === void 0 || scalar.chomp !== "keep") return false;
|
|
89
|
+
if (scalar.style !== "block-literal" && scalar.style !== "block-folded") return false;
|
|
90
|
+
const start = blankLineAboveStart(text, offset);
|
|
91
|
+
return start >= scalar.offset && start < scalar.offset + scalar.length;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* True when the line immediately below the line containing `offset` is blank
|
|
95
|
+
* (empty or horizontal whitespace only) AND is itself followed by another
|
|
96
|
+
* line. Purely local, the mirror of {@link hasBlankLineAbove} — used to embed
|
|
97
|
+
* a blank line AFTER a comment run as a trailing empty line in the stored
|
|
98
|
+
* comment string.
|
|
99
|
+
*/
|
|
100
|
+
function hasBlankLineBelow(text, offset) {
|
|
101
|
+
const lineEnd = text.indexOf("\n", Math.max(0, offset));
|
|
102
|
+
if (lineEnd < 0) return false;
|
|
103
|
+
const nextEnd = text.indexOf("\n", lineEnd + 1);
|
|
104
|
+
if (nextEnd < 0) return false;
|
|
105
|
+
return text.slice(lineEnd + 1, nextEnd).trim() === "";
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The stored text of a comment token: the RAW post-`#` slice, reference
|
|
109
|
+
* parity with the `yaml` npm package — `# section` stores `" section"`,
|
|
110
|
+
* `#no-space` stores `"no-space"`, `# aligned` keeps its alignment.
|
|
111
|
+
*
|
|
112
|
+
* The one reserved string is `""`, which encodes an embedded blank line
|
|
113
|
+
* inside a joined comment run — so a spaces-only raw slice stores with ONE
|
|
114
|
+
* extra trailing space and the renderers strip it back off. A bare `#`
|
|
115
|
+
* (raw `""`) stores `" "`, `# ` (raw `" "`) stores `" "`, and so on; the
|
|
116
|
+
* escape is injective, so every comment spelling roundtrips byte-intact.
|
|
117
|
+
* Raw storage is what makes byte-intact roundtrip possible; trimming would
|
|
118
|
+
* canonicalize every comment to `# text`.
|
|
119
|
+
*/
|
|
120
|
+
function rawCommentText(source) {
|
|
121
|
+
const raw = source.startsWith("#") ? source.slice(1) : source;
|
|
122
|
+
return /^ *$/.test(raw) ? `${raw} ` : raw;
|
|
123
|
+
}
|
|
124
|
+
/** Join two optional comment blocks with a newline. */
|
|
125
|
+
function joinComments(a, b) {
|
|
126
|
+
return a === void 0 ? b : `${a}\n${b}`;
|
|
127
|
+
}
|
|
128
|
+
/** Zero-based column of `offset` within its line. */
|
|
129
|
+
function columnAt(text, offset) {
|
|
130
|
+
if (offset <= 0) return 0;
|
|
131
|
+
return offset - (text.lastIndexOf("\n", offset - 1) + 1);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Rebuild `node` with the given comment fields merged in (existing fields are
|
|
135
|
+
* kept unless overridden; `commentBefore`/`comment` join with a newline when
|
|
136
|
+
* both sides are present). Aliases are returned unchanged — they carry no
|
|
137
|
+
* comment fields.
|
|
138
|
+
*/
|
|
139
|
+
function withCommentFields(node, fields) {
|
|
140
|
+
if (node instanceof YamlScalar) return new YamlScalar({
|
|
141
|
+
value: node.value,
|
|
142
|
+
style: node.style,
|
|
143
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
144
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
145
|
+
...mergedCommentFields(node, fields),
|
|
146
|
+
...node.chomp !== void 0 ? { chomp: node.chomp } : {},
|
|
147
|
+
...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
|
|
148
|
+
...node.raw !== void 0 ? { raw: node.raw } : {},
|
|
149
|
+
...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
|
|
150
|
+
offset: node.offset,
|
|
151
|
+
length: node.length
|
|
152
|
+
});
|
|
153
|
+
if (node instanceof YamlMap) return new YamlMap({
|
|
154
|
+
items: node.items,
|
|
155
|
+
style: node.style,
|
|
156
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
157
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
158
|
+
...mergedCommentFields(node, fields),
|
|
159
|
+
...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
|
|
160
|
+
offset: node.offset,
|
|
161
|
+
length: node.length
|
|
162
|
+
});
|
|
163
|
+
if (node instanceof YamlSeq) return new YamlSeq({
|
|
164
|
+
items: node.items,
|
|
165
|
+
style: node.style,
|
|
166
|
+
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
167
|
+
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
168
|
+
...mergedCommentFields(node, fields),
|
|
169
|
+
...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
|
|
170
|
+
offset: node.offset,
|
|
171
|
+
length: node.length
|
|
172
|
+
});
|
|
173
|
+
return node;
|
|
174
|
+
}
|
|
175
|
+
function mergedCommentFields(existing, incoming) {
|
|
176
|
+
const commentBefore = incoming.commentBefore !== void 0 ? existing.commentBefore !== void 0 ? `${existing.commentBefore}\n${incoming.commentBefore}` : incoming.commentBefore : existing.commentBefore;
|
|
177
|
+
const comment = incoming.comment !== void 0 ? existing.comment !== void 0 ? `${existing.comment}\n${incoming.comment}` : incoming.comment : existing.comment;
|
|
178
|
+
const spaceBefore = incoming.spaceBefore ?? existing.spaceBefore;
|
|
179
|
+
return {
|
|
180
|
+
...commentBefore !== void 0 ? { commentBefore } : {},
|
|
181
|
+
...comment !== void 0 ? { comment } : {},
|
|
182
|
+
...spaceBefore !== void 0 ? { spaceBefore } : {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
export { blankAboveIsKeepChompContent, blankLineAboveStart, columnAt, hasBlankLineAbove, hasBlankLineBelow, isOwnLineAt, joinComments, rawCommentText, sameLineSpan, withCommentFields };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { YamlAlias, YamlMap, YamlPair, YamlScalar, YamlSeq } from "../../YamlNode.js";
|
|
2
|
-
import { checkAnchorOnAlias, getAnchorName, makeAlias, registerAnchor } from "./anchors.js";
|
|
3
2
|
import { parseCSTAll } from "../cst-parser.js";
|
|
4
|
-
import {
|
|
3
|
+
import { checkAnchorOnAlias, getAnchorName, makeAlias, registerAnchor } from "./anchors.js";
|
|
4
|
+
import { hasBlankLineAbove, hasBlankLineBelow, rawCommentText } from "./comments.js";
|
|
5
|
+
import { clearMeta, commentProps, createState, hasMeta, sameLine } from "./state.js";
|
|
5
6
|
import { parseDirective, validateTagHandlesInDocument } from "./tags.js";
|
|
6
7
|
import { collectMultilinePlainScalar, findNextContentChild, getScalarStyle, hasBlockMapAfterInList, hasValueSepAfter, indexOfChild, makeScalar, resolveScalar } from "./scalars.js";
|
|
7
8
|
import { composeBlockMap, composeBlockSeq, composeFlatBlockMap } from "./block.js";
|
|
@@ -131,6 +132,9 @@ function composeDocument(cst, state, hasSubsequentDocuments = false, nextDocCst)
|
|
|
131
132
|
const directives = [];
|
|
132
133
|
let contents = null;
|
|
133
134
|
let documentComment;
|
|
135
|
+
let documentCommentAfter;
|
|
136
|
+
let lastLeadingCommentOffset = -1;
|
|
137
|
+
let sawDocEndMarker = false;
|
|
134
138
|
const hasDocStart = children.some((c) => c.type === "whitespace" && c.source === "---");
|
|
135
139
|
let i = 0;
|
|
136
140
|
const meta = {};
|
|
@@ -166,6 +170,7 @@ function composeDocument(cst, state, hasSubsequentDocuments = false, nextDocCst)
|
|
|
166
170
|
}
|
|
167
171
|
if (child.type === "newline" && hasMeta(meta)) sawNewlineSinceMeta = true;
|
|
168
172
|
if (child.type === "whitespace" || child.type === "newline") {
|
|
173
|
+
if (child.type === "whitespace" && child.source === "...") sawDocEndMarker = true;
|
|
169
174
|
if (child.type === "whitespace" && (child.source === "]" || child.source === "}")) state.errors.push({
|
|
170
175
|
code: "MalformedFlowCollection",
|
|
171
176
|
message: `Unexpected flow indicator '${child.source}' at document level`,
|
|
@@ -175,8 +180,18 @@ function composeDocument(cst, state, hasSubsequentDocuments = false, nextDocCst)
|
|
|
175
180
|
i++;
|
|
176
181
|
continue;
|
|
177
182
|
}
|
|
178
|
-
if (child.type === "comment" && contents === null) {
|
|
179
|
-
|
|
183
|
+
if (child.type === "comment" && contents === null && !sawDocEndMarker) {
|
|
184
|
+
const text = rawCommentText(child.source);
|
|
185
|
+
if (documentComment !== void 0 && hasBlankLineAbove(state.text, child.offset)) documentComment = `${documentComment}\n`;
|
|
186
|
+
documentComment = documentComment === void 0 ? text : `${documentComment}\n${text}`;
|
|
187
|
+
lastLeadingCommentOffset = child.offset;
|
|
188
|
+
i++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (child.type === "comment") {
|
|
192
|
+
const text = rawCommentText(child.source);
|
|
193
|
+
if (documentCommentAfter !== void 0 && hasBlankLineAbove(state.text, child.offset)) documentCommentAfter = `${documentCommentAfter}\n`;
|
|
194
|
+
documentCommentAfter = documentCommentAfter === void 0 ? text : `${documentCommentAfter}\n${text}`;
|
|
180
195
|
i++;
|
|
181
196
|
continue;
|
|
182
197
|
}
|
|
@@ -395,6 +410,8 @@ function composeDocument(cst, state, hasSubsequentDocuments = false, nextDocCst)
|
|
|
395
410
|
break;
|
|
396
411
|
}
|
|
397
412
|
}
|
|
413
|
+
if (documentComment !== void 0 && contents !== null && hasBlankLineBelow(state.text, lastLeadingCommentOffset)) documentComment = `${documentComment}\n`;
|
|
414
|
+
state.escapedComments.length = 0;
|
|
398
415
|
return {
|
|
399
416
|
contents,
|
|
400
417
|
errors: [...state.errors],
|
|
@@ -403,7 +420,8 @@ function composeDocument(cst, state, hasSubsequentDocuments = false, nextDocCst)
|
|
|
403
420
|
hasDocumentStart: hasDocStart,
|
|
404
421
|
hasDocumentEnd: hasDocEnd,
|
|
405
422
|
hasDocumentStartTab: hasDocStartTab,
|
|
406
|
-
...documentComment !== void 0 ? { comment: documentComment } : {}
|
|
423
|
+
...documentComment !== void 0 ? { comment: documentComment } : {},
|
|
424
|
+
...documentCommentAfter !== void 0 ? { commentAfter: documentCommentAfter } : {}
|
|
407
425
|
};
|
|
408
426
|
}
|
|
409
427
|
/**
|
|
@@ -568,8 +586,9 @@ function decorateSourceMultiline(node, text) {
|
|
|
568
586
|
style: node.style,
|
|
569
587
|
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
570
588
|
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
571
|
-
...node
|
|
589
|
+
...commentProps(node),
|
|
572
590
|
...node.chomp !== void 0 ? { chomp: node.chomp } : {},
|
|
591
|
+
...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
|
|
573
592
|
...node.raw !== void 0 ? { raw: node.raw } : {},
|
|
574
593
|
sourceMultiline: true,
|
|
575
594
|
offset: node.offset,
|
|
@@ -580,7 +599,7 @@ function decorateSourceMultiline(node, text) {
|
|
|
580
599
|
const newItems = node.items.map((pair) => new YamlPair({
|
|
581
600
|
key: decorateSourceMultiline(pair.key, text) ?? pair.key,
|
|
582
601
|
value: pair.value === null ? null : decorateSourceMultiline(pair.value, text),
|
|
583
|
-
...pair
|
|
602
|
+
...commentProps(pair)
|
|
584
603
|
}));
|
|
585
604
|
const multiline = isSourceMultiline(text, node.offset, node.length);
|
|
586
605
|
return new YamlMap({
|
|
@@ -588,7 +607,7 @@ function decorateSourceMultiline(node, text) {
|
|
|
588
607
|
style: node.style,
|
|
589
608
|
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
590
609
|
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
591
|
-
...node
|
|
610
|
+
...commentProps(node),
|
|
592
611
|
...multiline ? { sourceMultiline: true } : {},
|
|
593
612
|
offset: node.offset,
|
|
594
613
|
length: node.length
|
|
@@ -602,7 +621,7 @@ function decorateSourceMultiline(node, text) {
|
|
|
602
621
|
style: node.style,
|
|
603
622
|
...node.tag !== void 0 ? { tag: node.tag } : {},
|
|
604
623
|
...node.anchor !== void 0 ? { anchor: node.anchor } : {},
|
|
605
|
-
...node
|
|
624
|
+
...commentProps(node),
|
|
606
625
|
...multiline ? { sourceMultiline: true } : {},
|
|
607
626
|
offset: node.offset,
|
|
608
627
|
length: node.length
|
|
@@ -619,6 +638,7 @@ function decorateDocumentSourceMultiline(doc, text) {
|
|
|
619
638
|
warnings: doc.warnings,
|
|
620
639
|
directives: doc.directives,
|
|
621
640
|
...doc.comment !== void 0 ? { comment: doc.comment } : {},
|
|
641
|
+
...doc.commentAfter !== void 0 ? { commentAfter: doc.commentAfter } : {},
|
|
622
642
|
hasDocumentStart: doc.hasDocumentStart,
|
|
623
643
|
hasDocumentEnd: doc.hasDocumentEnd,
|
|
624
644
|
hasDocumentStartTab: doc.hasDocumentStartTab
|
|
@@ -641,12 +661,29 @@ const EMPTY_DOCUMENT = {
|
|
|
641
661
|
* state and therefore appear in the returned document's `errors`.
|
|
642
662
|
*/
|
|
643
663
|
function composeFirstDocument(text, options) {
|
|
664
|
+
return composeFirstDocumentCounted(text, options).document;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* {@link composeFirstDocument} plus the CST-level document count of the whole
|
|
668
|
+
* stream, from the single CST parse the compose already performs — no second
|
|
669
|
+
* walk, and correct where a regex on `---` is not (a `---` inside a block
|
|
670
|
+
* scalar or quoted string is content, not a document marker). Callers that
|
|
671
|
+
* must refuse multi-document input (the `YamlFormat` single-document
|
|
672
|
+
* contract) read `documentCount` instead of re-parsing.
|
|
673
|
+
*/
|
|
674
|
+
function composeFirstDocumentCounted(text, options) {
|
|
644
675
|
const cstNodes = parseCSTAll(text);
|
|
645
676
|
const state = createState(text, FLOW, options);
|
|
646
677
|
validateCrossDocumentDirectives(cstNodes, state);
|
|
647
678
|
const doc = cstNodes[0];
|
|
648
|
-
if (!doc) return
|
|
649
|
-
|
|
679
|
+
if (!doc) return {
|
|
680
|
+
document: EMPTY_DOCUMENT,
|
|
681
|
+
documentCount: 0
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
document: decorateDocumentSourceMultiline(composeDocument(doc, state, cstNodes.length > 1, cstNodes[1]), text),
|
|
685
|
+
documentCount: cstNodes.length
|
|
686
|
+
};
|
|
650
687
|
}
|
|
651
688
|
/**
|
|
652
689
|
* Compose every document of `text` with full error recovery — v3
|
|
@@ -674,4 +711,4 @@ function composeAllDocuments(text, options) {
|
|
|
674
711
|
}
|
|
675
712
|
|
|
676
713
|
//#endregion
|
|
677
|
-
export { composeAllDocuments, composeDocument, composeFirstDocument, validateCrossDocumentDirectives };
|
|
714
|
+
export { EMPTY_DOCUMENT, composeAllDocuments, composeDocument, composeFirstDocument, composeFirstDocumentCounted, validateCrossDocumentDirectives };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { YamlMap, YamlScalar, YamlSeq } from "../../YamlNode.js";
|
|
2
2
|
import { checkAnchorOnAlias, getAnchorName, makeAlias, registerAnchor } from "./anchors.js";
|
|
3
|
+
import { blankLineAboveStart, hasBlankLineAbove, isOwnLineAt, joinComments, rawCommentText, withCommentFields } from "./comments.js";
|
|
3
4
|
import { enterNesting, exitNesting, hasMeta } from "./state.js";
|
|
4
5
|
import { collectMultilineKey, collectMultilinePlainScalar, getScalarStyle, hasValueSepThroughPlainScalars, makeScalar, resolveScalar } from "./scalars.js";
|
|
5
6
|
import { buildPairs, checkDuplicateKeys, checkMultilineImplicitKeys } from "./block.js";
|
|
@@ -117,8 +118,9 @@ function composeFlowMapInner(cst, state, meta, parentBlockColumn) {
|
|
|
117
118
|
validateFlowContentIndent(cst, state, parentBlockColumn);
|
|
118
119
|
validateFlowSeparators(children, state, "{", "}");
|
|
119
120
|
const items = flattenFlowChildren(children.filter((c) => !(c.type === "whitespace" && (c.source === "{" || c.source === "}" || c.source.trim() === ""))), state);
|
|
120
|
-
buildPairs(items, pairs, state.text);
|
|
121
|
+
const trailingComment = buildPairs(items, pairs, state.text);
|
|
121
122
|
if (state.options.uniqueKeys) checkDuplicateKeys(pairs, state);
|
|
123
|
+
const mapComment = meta?.comment !== void 0 ? trailingComment !== void 0 ? `${meta.comment}\n${trailingComment}` : meta.comment : trailingComment;
|
|
122
124
|
const map = new YamlMap({
|
|
123
125
|
items: pairs,
|
|
124
126
|
style: "flow",
|
|
@@ -126,7 +128,7 @@ function composeFlowMapInner(cst, state, meta, parentBlockColumn) {
|
|
|
126
128
|
length: cst.length,
|
|
127
129
|
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
128
130
|
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
129
|
-
...
|
|
131
|
+
...mapComment !== void 0 ? { comment: mapComment } : {}
|
|
130
132
|
});
|
|
131
133
|
if (meta?.anchor) registerAnchor(map, meta.anchor, state, cst.offset);
|
|
132
134
|
return map;
|
|
@@ -188,7 +190,8 @@ function flattenFlowChildren(children, state) {
|
|
|
188
190
|
if (child.type === "comment") {
|
|
189
191
|
items.push({
|
|
190
192
|
kind: "comment",
|
|
191
|
-
comment:
|
|
193
|
+
comment: rawCommentText(child.source),
|
|
194
|
+
offset: child.offset
|
|
192
195
|
});
|
|
193
196
|
continue;
|
|
194
197
|
}
|
|
@@ -370,25 +373,95 @@ function composeFlowSeqInner(cst, state, meta, parentBlockColumn) {
|
|
|
370
373
|
current.push(child);
|
|
371
374
|
}
|
|
372
375
|
if (current.length > 0) segments.push(current);
|
|
376
|
+
let seqTrailing;
|
|
377
|
+
let pending = [];
|
|
378
|
+
let pendingSpace = false;
|
|
379
|
+
let lastItemEnd = -1;
|
|
380
|
+
let pushedIdx = -1;
|
|
381
|
+
const joinPending = (parts) => {
|
|
382
|
+
if (parts.length === 0) return void 0;
|
|
383
|
+
let out = parts[0].text;
|
|
384
|
+
for (let k = 1; k < parts.length; k++) {
|
|
385
|
+
const part = parts[k];
|
|
386
|
+
out += part.blankAbove ? `\n\n${part.text}` : `\n${part.text}`;
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
};
|
|
390
|
+
const blankIsBetweenItems = (anchorOffset) => blankLineAboveStart(state.text, anchorOffset) >= lastItemEnd;
|
|
391
|
+
const acceptOwnLineComment = (cText, cOff) => {
|
|
392
|
+
const blankAbove = cOff >= 0 && hasBlankLineAbove(state.text, cOff);
|
|
393
|
+
if (blankAbove && pending.length === 0 && lastItemEnd >= 0 && blankIsBetweenItems(cOff)) pendingSpace = true;
|
|
394
|
+
pending.push({
|
|
395
|
+
text: cText,
|
|
396
|
+
offset: cOff,
|
|
397
|
+
blankAbove: blankAbove && pending.length > 0
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
/** The pending comment fields for the item being pushed, then reset. */
|
|
401
|
+
const takePendingFields = (anchorOffset) => {
|
|
402
|
+
let commentBefore = joinPending(pending);
|
|
403
|
+
if (anchorOffset >= 0 && hasBlankLineAbove(state.text, anchorOffset)) {
|
|
404
|
+
if (commentBefore !== void 0) commentBefore = `${commentBefore}\n`;
|
|
405
|
+
else if (!pendingSpace && lastItemEnd >= 0 && blankIsBetweenItems(anchorOffset)) pendingSpace = true;
|
|
406
|
+
}
|
|
407
|
+
const fields = {
|
|
408
|
+
...commentBefore !== void 0 ? { commentBefore } : {},
|
|
409
|
+
...pendingSpace ? { spaceBefore: true } : {}
|
|
410
|
+
};
|
|
411
|
+
pending = [];
|
|
412
|
+
pendingSpace = false;
|
|
413
|
+
return fields;
|
|
414
|
+
};
|
|
415
|
+
const hasPendingFields = (fields) => fields.commentBefore !== void 0 || fields.spaceBefore === true;
|
|
373
416
|
for (const segment of segments) if (segment.some((c) => c.type === "whitespace" && c.source === ":")) {
|
|
374
|
-
const
|
|
417
|
+
const content = segment.filter((c) => !(c.type === "whitespace" && c.source.trim() === ""));
|
|
418
|
+
const semItems = flattenFlowChildren(content, state);
|
|
375
419
|
const pairs = [];
|
|
376
|
-
buildPairs(semItems, pairs, state.text);
|
|
420
|
+
const segTrailing = buildPairs(semItems, pairs, state.text);
|
|
377
421
|
if (!segment.some((c) => c.type === "whitespace" && c.source === "?")) checkMultilineImplicitKeys(pairs, state, semItems);
|
|
378
422
|
const firstPair = pairs[0];
|
|
379
423
|
if (firstPair) {
|
|
424
|
+
const segFirst = content[0];
|
|
425
|
+
const fields = takePendingFields(segFirst !== void 0 ? segFirst.offset : -1);
|
|
380
426
|
const map = new YamlMap({
|
|
381
427
|
items: pairs,
|
|
382
428
|
style: "flow",
|
|
383
429
|
offset: firstPair.key.offset,
|
|
384
|
-
length: 0
|
|
430
|
+
length: 0,
|
|
431
|
+
...fields,
|
|
432
|
+
...segTrailing !== void 0 ? { comment: segTrailing } : {}
|
|
385
433
|
});
|
|
386
434
|
items.push(map);
|
|
387
|
-
|
|
435
|
+
pushedIdx = items.length - 1;
|
|
436
|
+
lastItemEnd = map.offset + map.length;
|
|
437
|
+
} else if (segTrailing !== void 0) seqTrailing = joinComments(seqTrailing, segTrailing);
|
|
388
438
|
} else {
|
|
389
439
|
const semItems = flattenFlowChildren(segment.filter((c) => !(c.type === "whitespace" && c.source.trim() === "")), state);
|
|
390
|
-
for (const si of semItems)
|
|
440
|
+
for (const si of semItems) {
|
|
441
|
+
if (si.kind === "comment") {
|
|
442
|
+
const cText = si.comment ?? "";
|
|
443
|
+
if (!(si.offset === void 0 ? true : isOwnLineAt(state.text, si.offset)) && pushedIdx >= 0) {
|
|
444
|
+
const prev = items[pushedIdx];
|
|
445
|
+
if (prev) items[pushedIdx] = withCommentFields(prev, { comment: cText });
|
|
446
|
+
} else acceptOwnLineComment(cText, si.offset ?? -1);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (si.kind === "node" && si.node) {
|
|
450
|
+
const fields = takePendingFields(si.node.length > 0 ? si.node.offset : -1);
|
|
451
|
+
const node = hasPendingFields(fields) ? withCommentFields(si.node, fields) : si.node;
|
|
452
|
+
items.push(node);
|
|
453
|
+
pushedIdx = items.length - 1;
|
|
454
|
+
lastItemEnd = si.node.offset + si.node.length;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (pending.length > 0) {
|
|
459
|
+
const joined = joinPending(pending);
|
|
460
|
+
const first = pending[0];
|
|
461
|
+
const withLead = joined !== void 0 && first !== void 0 && lastItemEnd >= 0 && first.offset >= 0 && hasBlankLineAbove(state.text, first.offset) ? `\n${joined}` : joined;
|
|
462
|
+
if (withLead !== void 0) seqTrailing = joinComments(seqTrailing, withLead);
|
|
391
463
|
}
|
|
464
|
+
const seqComment = meta?.comment !== void 0 ? seqTrailing !== void 0 ? `${meta.comment}\n${seqTrailing}` : meta.comment : seqTrailing;
|
|
392
465
|
const seq = new YamlSeq({
|
|
393
466
|
items,
|
|
394
467
|
style: "flow",
|
|
@@ -396,7 +469,7 @@ function composeFlowSeqInner(cst, state, meta, parentBlockColumn) {
|
|
|
396
469
|
length: cst.length,
|
|
397
470
|
...meta?.tag !== void 0 ? { tag: meta.tag } : {},
|
|
398
471
|
...meta?.anchor !== void 0 ? { anchor: meta.anchor } : {},
|
|
399
|
-
...
|
|
472
|
+
...seqComment !== void 0 ? { comment: seqComment } : {}
|
|
400
473
|
});
|
|
401
474
|
if (meta?.anchor) registerAnchor(seq, meta.anchor, state, cst.offset);
|
|
402
475
|
return seq;
|