@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.
Files changed (40) hide show
  1. package/README.md +78 -7
  2. package/Yaml.js +87 -39
  3. package/YamlDocument.js +25 -6
  4. package/YamlFormat.js +171 -20
  5. package/YamlLint.js +239 -0
  6. package/YamlLintRule.js +36 -0
  7. package/YamlNode.js +36 -2
  8. package/YamlToken.js +139 -0
  9. package/YamlVisitor.js +40 -5
  10. package/index.d.ts +439 -12
  11. package/index.js +4 -1
  12. package/internal/composer/anchors.js +2 -27
  13. package/internal/composer/block.js +246 -58
  14. package/internal/composer/comments.js +187 -0
  15. package/internal/composer/document.js +49 -12
  16. package/internal/composer/flow.js +82 -9
  17. package/internal/composer/scalars.js +77 -28
  18. package/internal/composer/state.js +16 -2
  19. package/internal/cst-parser.js +11 -2
  20. package/internal/diagnostics.js +12 -3
  21. package/internal/fold.js +22 -10
  22. package/internal/lexer.js +23 -17
  23. package/internal/rules/catalog.js +39 -0
  24. package/internal/rules/colon-spacing.js +77 -0
  25. package/internal/rules/comments-spacing.js +68 -0
  26. package/internal/rules/document-end.js +65 -0
  27. package/internal/rules/document-start.js +64 -0
  28. package/internal/rules/empty-lines.js +70 -0
  29. package/internal/rules/eof-newline.js +33 -0
  30. package/internal/rules/hyphen-spacing.js +52 -0
  31. package/internal/rules/indentation.js +103 -0
  32. package/internal/rules/key-duplicates.js +49 -0
  33. package/internal/rules/line-length.js +36 -0
  34. package/internal/rules/parse-validity.js +33 -0
  35. package/internal/rules/quoted-strings.js +81 -0
  36. package/internal/rules/trailing-spaces.js +39 -0
  37. package/internal/rules/truthy.js +84 -0
  38. package/internal/rules/util.js +92 -0
  39. package/internal/stringifier.js +285 -69
  40. package/package.json +4 -3
@@ -29,6 +29,27 @@ var StringifyDepthExceeded = class extends Error {
29
29
  this.name = "StringifyDepthExceeded";
30
30
  }
31
31
  };
32
+ /**
33
+ * YAML 1.2 §8.1.3 caps an implicit block-mapping key: the `:` indicator must
34
+ * appear at most 1024 characters after the start of the key. A key whose
35
+ * RENDERED form (quotes and escapes count, the source scalar does not) is
36
+ * longer than this must spill to explicit-key form (`? key` / `: value`) or
37
+ * strict parsers reject the output. Matches the reference `yaml` package
38
+ * (`stringifyPair`: `str.length > 1024`, block context only — flow contexts
39
+ * never spill): a rendered key of exactly 1024 characters stays implicit.
40
+ */
41
+ const IMPLICIT_KEY_MAX_LENGTH = 1024;
42
+ /**
43
+ * Continuation-line padding for explicit-key compact notation. Structurally
44
+ * the width of the `? ` / `: ` indicators — two columns — and deliberately
45
+ * NOT `ctx.indent`: compact continuation lines must align with the first
46
+ * item, which always starts two columns in, so any other pad silently
47
+ * re-nests (or corrupts) the value on reparse once `indent !== 2`. This is a
48
+ * recorded divergence from the reference `yaml` package, which pads these
49
+ * lines with the configured indent and emits output its own strict parser
50
+ * misreads or rejects at indent ≠ 2.
51
+ */
52
+ const EXPLICIT_COMPACT_PAD = " ";
32
53
  const NULL_RE = /^(?:null|Null|NULL|~)$/;
33
54
  const TRUE_RE = /^(?:true|True|TRUE)$/;
34
55
  const FALSE_RE = /^(?:false|False|FALSE)$/;
@@ -98,10 +119,12 @@ function requiresQuoting(s, ignoreType = false) {
98
119
  if (!ignoreType && wouldBeResolved(s)) return true;
99
120
  const first = s[0];
100
121
  if (first === " " || first === " ") return true;
101
- if (first !== void 0 && INDICATOR_CHARS.has(first)) if (first === ":" || first === "?" || first === "-") {
102
- const second = s[1];
103
- if (s.length === 1 || second === " " || second === " ") return true;
104
- } else return true;
122
+ if (first !== void 0 && INDICATOR_CHARS.has(first)) {
123
+ if (first === ":" || first === "?" || first === "-") {
124
+ const second = s[1];
125
+ if (s.length === 1 || second === " " || second === " ") return true;
126
+ } else return true;
127
+ }
105
128
  if (s.startsWith("---") || s.startsWith("...")) return true;
106
129
  if (s.includes(": ") || s.includes(": ") || s.endsWith(":")) return true;
107
130
  if (s.includes(" #") || s.includes(" #")) return true;
@@ -132,14 +155,15 @@ function renderDoubleQuoted(s, canonical = false) {
132
155
  let result = "";
133
156
  for (let i = 0; i < escaped.length; i++) {
134
157
  const code = escaped.charCodeAt(i);
135
- if (isControlChar(code)) if (code === 0) result += "\\0";
136
- else if (code === 7) result += "\\a";
137
- else if (code === 8) result += "\\b";
138
- else if (code === 11) result += "\\v";
139
- else if (code === 12) result += "\\f";
140
- else if (code === 27) result += "\\e";
141
- else result += `\\x${code.toString(16).padStart(2, "0")}`;
142
- else if (canonical && code > 126) {
158
+ if (isControlChar(code)) {
159
+ if (code === 0) result += "\\0";
160
+ else if (code === 7) result += "\\a";
161
+ else if (code === 8) result += "\\b";
162
+ else if (code === 11) result += "\\v";
163
+ else if (code === 12) result += "\\f";
164
+ else if (code === 27) result += "\\e";
165
+ else result += `\\x${code.toString(16).padStart(2, "0")}`;
166
+ } else if (canonical && code > 126) {
143
167
  if (code >= 55296 && code <= 56319 && i + 1 < escaped.length) {
144
168
  const low = escaped.charCodeAt(i + 1);
145
169
  if (low >= 56320 && low <= 57343) {
@@ -173,7 +197,9 @@ function renderSingleQuoted(s) {
173
197
  * Block literal and block folded styles are always accepted for single-line
174
198
  * strings even though the output is unusual.
175
199
  */
176
- function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition, quoteStyle = "single") {
200
+ function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition, quoteStyle = "single", explicitIndent) {
201
+ const fidelity = !canonical;
202
+ const fidelityIndent = fidelity ? explicitIndent : void 0;
177
203
  if (s.includes("\n")) {
178
204
  let hasControl = false;
179
205
  for (let i = 0; i < s.length; i++) {
@@ -189,14 +215,14 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
189
215
  if (hasInteriorTrailingWhitespace(s)) return renderDoubleQuoted(s, canonical);
190
216
  if (hasNewlineSpacesTab(s)) return renderDoubleQuoted(s, canonical);
191
217
  }
192
- if (style === "block-literal") return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
193
- if (style === "block-folded") return renderBlockFolded(s, indent);
218
+ if (style === "block-literal") return renderBlockLiteral(s, indent, explicitChomp, parentPosition, fidelity, fidelityIndent);
219
+ if (style === "block-folded") return renderBlockFolded(s, indent, fidelity ? explicitChomp : void 0, fidelityIndent);
194
220
  if (style === "plain" || style === "single-quoted") {
195
221
  if (canonical) {
196
222
  const sq = renderSingleQuotedMultiline(s, indent);
197
223
  if (sq !== null) return sq;
198
224
  }
199
- return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
225
+ return renderBlockLiteral(s, indent, explicitChomp, parentPosition, fidelity, fidelityIndent);
200
226
  }
201
227
  return renderDoubleQuoted(s, canonical);
202
228
  }
@@ -212,8 +238,8 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
212
238
  return s;
213
239
  case "single-quoted": return renderSingleQuoted(s);
214
240
  case "double-quoted": return renderDoubleQuoted(s, canonical);
215
- case "block-literal": return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
216
- case "block-folded": return renderBlockFolded(s, indent);
241
+ case "block-literal": return renderBlockLiteral(s, indent, explicitChomp, parentPosition, fidelity, fidelityIndent);
242
+ case "block-folded": return renderBlockFolded(s, indent, fidelity ? explicitChomp : void 0, fidelityIndent);
217
243
  }
218
244
  }
219
245
  /**
@@ -358,6 +384,18 @@ function stringifyObjectLines(obj, ctx, depth) {
358
384
  const keyStr = renderKey(k);
359
385
  const val = obj[k];
360
386
  const valLines = stringifyLines(val, ctx, depth + 1);
387
+ if (keyStr.length > IMPLICIT_KEY_MAX_LENGTH) {
388
+ lines.push(`? ${keyStr}`);
389
+ if (valLines.length === 1) lines.push(`: ${valLines[0]}`);
390
+ else if (valLines[0].startsWith("|") || valLines[0].startsWith(">") || typeof val === "string") {
391
+ lines.push(`: ${valLines[0]}`);
392
+ for (let i = 1; i < valLines.length; i++) lines.push(valLines[i]);
393
+ } else {
394
+ lines.push(`: ${valLines[0]}`);
395
+ for (let i = 1; i < valLines.length; i++) lines.push(valLines[i] === "" ? valLines[i] : `${EXPLICIT_COMPACT_PAD}${valLines[i]}`);
396
+ }
397
+ continue;
398
+ }
361
399
  if (valLines.length === 1 && !isBlockCollection(val, ctx)) lines.push(`${keyStr}: ${valLines[0]}`);
362
400
  else {
363
401
  const first = valLines[0];
@@ -436,8 +474,11 @@ function normalizeNodeTags(node, tagMap) {
436
474
  style: node.style,
437
475
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
438
476
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
477
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
439
478
  ...node.comment !== void 0 ? { comment: node.comment } : {},
479
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
440
480
  ...node.chomp !== void 0 ? { chomp: node.chomp } : {},
481
+ ...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
441
482
  ...node.raw !== void 0 ? { raw: node.raw } : {},
442
483
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
443
484
  offset: node.offset,
@@ -447,12 +488,16 @@ function normalizeNodeTags(node, tagMap) {
447
488
  items: node.items.map((pair) => new YamlPair({
448
489
  key: normalizeNodeTags(pair.key, tagMap),
449
490
  value: pair.value ? normalizeNodeTags(pair.value, tagMap) : null,
450
- ...pair.comment !== void 0 ? { comment: pair.comment } : {}
491
+ ...pair.commentBefore !== void 0 ? { commentBefore: pair.commentBefore } : {},
492
+ ...pair.comment !== void 0 ? { comment: pair.comment } : {},
493
+ ...pair.spaceBefore !== void 0 ? { spaceBefore: pair.spaceBefore } : {}
451
494
  })),
452
495
  style: node.style,
453
496
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
454
497
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
498
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
455
499
  ...node.comment !== void 0 ? { comment: node.comment } : {},
500
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
456
501
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
457
502
  offset: node.offset,
458
503
  length: node.length
@@ -462,7 +507,9 @@ function normalizeNodeTags(node, tagMap) {
462
507
  style: node.style,
463
508
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
464
509
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
510
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
465
511
  ...node.comment !== void 0 ? { comment: node.comment } : {},
512
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
466
513
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
467
514
  offset: node.offset,
468
515
  length: node.length
@@ -480,6 +527,7 @@ function stripNodeComments(node) {
480
527
  ...node.tag !== void 0 ? { tag: node.tag } : {},
481
528
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
482
529
  ...node.chomp !== void 0 ? { chomp: node.chomp } : {},
530
+ ...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
483
531
  ...node.raw !== void 0 ? { raw: node.raw } : {},
484
532
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
485
533
  offset: node.offset,
@@ -534,11 +582,12 @@ function stringifyScalarNodeLines(node, ctx) {
534
582
  return [parts.join(" ")];
535
583
  }
536
584
  let lines;
537
- if (val === null || val === void 0) if (isEmpty) lines = [""];
538
- else lines = ["null"];
539
- else if (typeof val === "boolean") lines = [val ? "true" : "false"];
585
+ if (val === null || val === void 0) {
586
+ if (isEmpty) lines = [""];
587
+ else lines = ["null"];
588
+ } else if (typeof val === "boolean") lines = [val ? "true" : "false"];
540
589
  else if (typeof val === "number") lines = [node.raw !== void 0 ? node.raw : renderNumber(val)];
541
- else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition, ctx.quoteStyle).split("\n");
590
+ else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition, ctx.quoteStyle, node.blockIndent).split("\n");
542
591
  else lines = [renderDoubleQuoted(String(val))];
543
592
  if (node.tag) lines[0] = `${node.tag} ${lines[0]}`;
544
593
  if (node.anchor) lines[0] = `&${node.anchor} ${lines[0]}`;
@@ -578,6 +627,79 @@ function stringifyMappingKeyLines(node, ctx, depth) {
578
627
  return stringifyNodeLines(node, ctx, depth);
579
628
  }
580
629
  /**
630
+ * Undo the capture-side escape on one stored comment line: a spaces-only
631
+ * slice was stored with one extra trailing space so it cannot collide with
632
+ * `""`, the embedded-blank-line encoding (see `rawCommentText`).
633
+ */
634
+ function unescapeCommentLine(line) {
635
+ return /^ +$/.test(line) ? line.slice(0, -1) : line;
636
+ }
637
+ /**
638
+ * Render a stored comment block, one output line per stored line. Stored
639
+ * text is the RAW post-`#` slice (reference parity) with spaces-only lines
640
+ * carrying one escape space: `""` is an embedded blank line; any other line
641
+ * renders as `#` + the unescaped slice (`" "` → `#`, `" "` → `# `).
642
+ */
643
+ function commentBlockLines(comment) {
644
+ return comment.split("\n").map((l) => l === "" ? "" : `#${unescapeCommentLine(l)}`);
645
+ }
646
+ /** Render a trailing comment for appending after content on the same line. */
647
+ function renderTrailingComment(comment) {
648
+ return ` #${comment.split("\n").map(unescapeCommentLine).join(" ")}`;
649
+ }
650
+ /**
651
+ * Emits the `: value` line(s) of an explicit-key block-mapping entry, in
652
+ * compact notation where the value admits it (first line on the colon line,
653
+ * continuation lines indented to align). Shared by the complex-key branch and
654
+ * the implicit-key length spill; `indentSequences` deliberately does not reach
655
+ * this branch (sequence values always align compactly under `: `).
656
+ *
657
+ * Returns the `lines` index that can safely carry the pair's trailing
658
+ * comment (the line where an inline value ends), or `-1` when no line is
659
+ * safe (a compact block collection or a block-scalar header).
660
+ */
661
+ function pushExplicitKeyValueLines(lines, valNode, ctx, depth, pad) {
662
+ if (!valNode) {
663
+ lines.push(":");
664
+ return lines.length - 1;
665
+ }
666
+ const valLines = stringifyNodeLines(valNode, ctx, depth + 1);
667
+ if (valLines.length === 1) {
668
+ lines.push(`: ${valLines[0]}`);
669
+ return lines.length - 1;
670
+ }
671
+ const first = valLines[0];
672
+ const firstStripped = stripScalarMetadataPrefix(first);
673
+ const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
674
+ const isInlineQuoted = valNode instanceof YamlScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""));
675
+ if (isBlockScalarHeader || isInlineQuoted) {
676
+ const headerIdx = lines.length;
677
+ if (isBlockScalarHeader) {
678
+ const scalarComment = !ctx.forceDefaultStyles && valNode instanceof YamlScalar ? valNode.comment : void 0;
679
+ lines.push(scalarComment !== void 0 ? `: ${first}${renderTrailingComment(scalarComment)}` : `: ${first}`);
680
+ for (let v = 1; v < valLines.length; v++) lines.push(valLines[v]);
681
+ return scalarComment !== void 0 ? -1 : headerIdx;
682
+ }
683
+ lines.push(`: ${first}`);
684
+ for (let v = 1; v < valLines.length; v++) lines.push(valLines[v]);
685
+ return lines.length - 1;
686
+ }
687
+ if (first.startsWith("-")) {
688
+ lines.push(`: ${first}`);
689
+ for (let v = 1; v < valLines.length; v++) lines.push(valLines[v] === "" ? "" : `${EXPLICIT_COMPACT_PAD}${valLines[v]}`);
690
+ return -1;
691
+ }
692
+ if (valNode instanceof YamlMap && valNode.items.length > 0 && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block") {
693
+ lines.push(`: ${first}`);
694
+ for (let v = 1; v < valLines.length; v++) lines.push(valLines[v] === "" ? "" : `${EXPLICIT_COMPACT_PAD}${valLines[v]}`);
695
+ return -1;
696
+ }
697
+ const colonIdx = lines.length;
698
+ lines.push(":");
699
+ for (const vl of valLines) lines.push(vl === "" ? "" : `${pad}${vl}`);
700
+ return colonIdx;
701
+ }
702
+ /**
581
703
  * Stringifies a YamlMap node into lines, using the node's collection style.
582
704
  */
583
705
  function stringifyMapNodeLines(node, ctx, depth) {
@@ -595,56 +717,68 @@ function stringifyMapNodeLines(node, ctx, depth) {
595
717
  return [line];
596
718
  }
597
719
  if (style === "flow") {
720
+ const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
721
+ if (!ctx.forceDefaultStyles && (node.comment !== void 0 || items.some((p) => p.commentBefore !== void 0 || p.comment !== void 0 || p.spaceBefore === true))) {
722
+ const flowPad = " ".repeat(ctx.indent);
723
+ const flowLines = [flowPrefix ? `${flowPrefix} {` : "{"];
724
+ for (let fi = 0; fi < items.length; fi++) {
725
+ const pair = items[fi];
726
+ if (pair.spaceBefore === true) flowLines.push("");
727
+ if (pair.commentBefore !== void 0) for (const cl of commentBlockLines(pair.commentBefore)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
728
+ const keyStr = pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null";
729
+ const valStr = pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null";
730
+ const comma = fi < items.length - 1 ? "," : "";
731
+ const trailing = pair.comment !== void 0 ? renderTrailingComment(pair.comment) : "";
732
+ flowLines.push(`${flowPad}${keyStr}: ${valStr}${comma}${trailing}`);
733
+ }
734
+ if (node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
735
+ flowLines.push("}");
736
+ return flowLines;
737
+ }
598
738
  let line = `{${items.map((pair) => {
599
739
  return `${pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null"}`;
600
740
  }).join(", ")}}`;
601
- const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
602
741
  if (flowPrefix) line = `${flowPrefix} ${line}`;
603
742
  return [line];
604
743
  }
605
744
  const pad = " ".repeat(ctx.indent);
745
+ const emitComments = !ctx.forceDefaultStyles;
606
746
  const lines = [];
747
+ /** Append a pair's trailing comment to `lines[idx]` (flattened to one line); `-1` drops it. */
748
+ const appendTrailing = (idx, comment) => {
749
+ if (!emitComments || comment === void 0 || idx < 0 || idx >= lines.length) return;
750
+ lines[idx] = `${lines[idx]}${renderTrailingComment(comment)}`;
751
+ };
752
+ /** A value node's own leading comment block, or undefined. */
753
+ const valueLeading = (valNode) => emitComments && !(valNode instanceof YamlAlias) ? valNode.commentBefore : void 0;
607
754
  for (const pair of items) {
755
+ if (emitComments && pair.spaceBefore === true) lines.push("");
756
+ if (emitComments && pair.commentBefore !== void 0) for (const cl of commentBlockLines(pair.commentBefore)) lines.push(cl);
608
757
  const keyIsScalarWithNewline = pair.key instanceof YamlScalar && (typeof pair.key.value === "string" && pair.key.value.includes("\n") || pair.key.style === "block-literal" || pair.key.style === "block-folded");
609
758
  if ((pair.key instanceof YamlMap || pair.key instanceof YamlSeq) && pair.key.items.length > 0 || keyIsScalarWithNewline) {
610
759
  const keyLines = stringifyNodeLines(pair.key, ctx, depth + 1);
611
760
  lines.push(`? ${keyLines[0]}`);
761
+ const explicitKeyLineIdx = lines.length - 1;
612
762
  const firstTokens = keyLines[0].trim().split(/\s+/).filter(Boolean);
613
763
  const firstIsMetaOnly = firstTokens.length > 0 && firstTokens.every((t) => t.startsWith("&") || t.startsWith("!"));
614
764
  const keyIsBlockScalar = pair.key instanceof YamlScalar && (pair.key.style === "block-literal" || pair.key.style === "block-folded");
615
- const contPad = firstIsMetaOnly || keyIsBlockScalar ? "" : pad;
765
+ const contPad = firstIsMetaOnly || keyIsBlockScalar ? "" : EXPLICIT_COMPACT_PAD;
616
766
  for (let k = 1; k < keyLines.length; k++) lines.push(`${contPad}${keyLines[k]}`);
617
- const valNode = pair.value;
618
- if (!valNode) lines.push(":");
619
- else {
620
- const valLines = stringifyNodeLines(valNode, ctx, depth + 1);
621
- if (valLines.length === 1) lines.push(`: ${valLines[0]}`);
622
- else {
623
- const first = valLines[0];
624
- const firstStripped = stripScalarMetadataPrefix(first);
625
- const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
626
- const isInlineQuoted = valNode instanceof YamlScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""));
627
- if (isBlockScalarHeader || isInlineQuoted) {
628
- lines.push(`: ${first}`);
629
- for (let v = 1; v < valLines.length; v++) lines.push(valLines[v]);
630
- } else if (first.startsWith("-")) {
631
- lines.push(`: ${first}`);
632
- for (let v = 1; v < valLines.length; v++) lines.push(`${pad}${valLines[v]}`);
633
- } else if (valNode instanceof YamlMap && valNode.items.length > 0 && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block") {
634
- lines.push(`: ${first}`);
635
- for (let v = 1; v < valLines.length; v++) lines.push(`${pad}${valLines[v]}`);
636
- } else {
637
- lines.push(":");
638
- for (const vl of valLines) lines.push(`${pad}${vl}`);
639
- }
640
- }
641
- }
767
+ const inlineIdx = pushExplicitKeyValueLines(lines, pair.value, ctx, depth, pad);
768
+ appendTrailing(inlineIdx >= 0 ? inlineIdx : keyLines.length === 1 ? explicitKeyLineIdx : -1, pair.comment);
642
769
  continue;
643
770
  }
644
771
  let resolvedKeyStr;
645
772
  if (ctx.forceDefaultStyles && node.style === "flow" && node.sourceMultiline !== true && pair.key instanceof YamlScalar && (pair.key.style === "single-quoted" || pair.key.style === "double-quoted") && typeof pair.key.value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(pair.key.value)) resolvedKeyStr = pair.key.value;
646
773
  else resolvedKeyStr = pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null";
647
774
  const keyStr = resolvedKeyStr;
775
+ if (keyStr.length > IMPLICIT_KEY_MAX_LENGTH) {
776
+ lines.push(`? ${keyStr}`);
777
+ const spillKeyLineIdx = lines.length - 1;
778
+ const inlineIdx = pushExplicitKeyValueLines(lines, pair.value, ctx, depth, pad);
779
+ appendTrailing(inlineIdx >= 0 ? inlineIdx : spillKeyLineIdx, pair.comment);
780
+ continue;
781
+ }
648
782
  const keyIsAnchoredOrTaggedEmpty = pair.key instanceof YamlScalar && pair.key.length === 0 && (pair.key.value === null || pair.key.value === void 0 || pair.key.value === "") && (pair.key.anchor !== void 0 || pair.key.tag !== void 0);
649
783
  const sep = pair.key instanceof YamlAlias || keyIsAnchoredOrTaggedEmpty ? " :" : ":";
650
784
  const valNode = pair.value;
@@ -654,16 +788,20 @@ function stringifyMapNodeLines(node, ctx, depth) {
654
788
  const isRootFlowMap = ctx.parentPosition === void 0 && node.style === "flow" && node.sourceMultiline === true;
655
789
  if (ctx.forceDefaultStyles && isRootFlowMap && isPlainKey && keyIsNonEmpty) lines.push(`${keyStr}${sep} null`);
656
790
  else lines.push(`${keyStr}${sep}`);
791
+ appendTrailing(lines.length - 1, pair.comment);
657
792
  continue;
658
793
  }
659
794
  const valLines = stringifyNodeLines(valNode, {
660
795
  ...ctx,
661
796
  parentPosition: "block-map-value"
662
797
  }, depth + 1);
798
+ const valLeading = valueLeading(valNode);
663
799
  if (valNode instanceof YamlSeq && valNode.items.length > 0 && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block") {
664
800
  const seqMeta = buildMetadataPrefix(valNode.tag, valNode.anchor);
665
801
  const startIdx = seqMeta ? 1 : 0;
666
802
  lines.push(seqMeta ? `${keyStr}${sep} ${seqMeta}` : `${keyStr}${sep}`);
803
+ appendTrailing(lines.length - 1, pair.comment);
804
+ if (valLeading !== void 0) for (const cl of commentBlockLines(valLeading)) lines.push(ctx.indentSequences ? `${pad}${cl}` : cl);
667
805
  for (let i = startIdx; i < valLines.length; i++) {
668
806
  const vl = valLines[i];
669
807
  lines.push(ctx.indentSequences && vl !== "" ? `${pad}${vl}` : vl);
@@ -672,28 +810,54 @@ function stringifyMapNodeLines(node, ctx, depth) {
672
810
  const mapMeta = buildMetadataPrefix(valNode.tag, valNode.anchor);
673
811
  const startIdx = mapMeta ? 1 : 0;
674
812
  lines.push(mapMeta ? `${keyStr}${sep} ${mapMeta}` : `${keyStr}${sep}`);
675
- for (let i = startIdx; i < valLines.length; i++) lines.push(`${pad}${valLines[i]}`);
676
- } else if (valLines.length === 1) lines.push(valLines[0] === "" ? `${keyStr}${sep}` : `${keyStr}${sep} ${valLines[0]}`);
677
- else {
813
+ appendTrailing(lines.length - 1, pair.comment);
814
+ if (valLeading !== void 0) for (const cl of commentBlockLines(valLeading)) lines.push(`${pad}${cl}`);
815
+ for (let i = startIdx; i < valLines.length; i++) lines.push(valLines[i] === "" ? "" : `${pad}${valLines[i]}`);
816
+ } else if (valLines.length === 1 && valLeading !== void 0) {
817
+ lines.push(`${keyStr}${sep}`);
818
+ appendTrailing(lines.length - 1, pair.comment);
819
+ for (const cl of commentBlockLines(valLeading)) lines.push(`${pad}${cl}`);
820
+ lines.push(valLines[0] === "" ? "" : `${pad}${valLines[0]}`);
821
+ } else if (valLines.length === 1) {
822
+ lines.push(valLines[0] === "" ? `${keyStr}${sep}` : `${keyStr}${sep} ${valLines[0]}`);
823
+ appendTrailing(lines.length - 1, pair.comment);
824
+ } else {
678
825
  const first = valLines[0];
679
826
  const firstStripped = stripScalarMetadataPrefix(first);
680
827
  const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
681
828
  const isInlineQuoted = valNode instanceof YamlScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""));
682
829
  if (isBlockScalarHeader || isInlineQuoted) {
683
- lines.push(`${keyStr}${sep} ${first}`);
684
- for (let i = 1; i < valLines.length; i++) lines.push(valLines[i]);
830
+ const scalarComment = isBlockScalarHeader && valNode instanceof YamlScalar ? valNode.comment : void 0;
831
+ if (scalarComment !== void 0 && pair.comment !== void 0) {
832
+ lines.push(`${keyStr}${sep}`);
833
+ appendTrailing(lines.length - 1, pair.comment);
834
+ const headerIdx = lines.length;
835
+ lines.push(`${pad}${first}`);
836
+ for (let i = 1; i < valLines.length; i++) lines.push(valLines[i]);
837
+ appendTrailing(headerIdx, scalarComment);
838
+ } else {
839
+ const headerIdx = lines.length;
840
+ lines.push(`${keyStr}${sep} ${first}`);
841
+ for (let i = 1; i < valLines.length; i++) lines.push(valLines[i]);
842
+ if (isBlockScalarHeader) appendTrailing(headerIdx, scalarComment ?? pair.comment);
843
+ else appendTrailing(lines.length - 1, pair.comment);
844
+ }
685
845
  } else {
686
846
  const mapMeta = valNode instanceof YamlMap && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block" ? buildMetadataPrefix(valNode.tag, valNode.anchor) : void 0;
687
847
  if (mapMeta) {
688
848
  lines.push(`${keyStr}${sep} ${mapMeta}`);
689
- for (let i = 1; i < valLines.length; i++) lines.push(`${pad}${valLines[i]}`);
849
+ appendTrailing(lines.length - 1, pair.comment);
850
+ for (let i = 1; i < valLines.length; i++) lines.push(valLines[i] === "" ? "" : `${pad}${valLines[i]}`);
690
851
  } else {
691
852
  lines.push(`${keyStr}${sep}`);
692
- for (const vl of valLines) lines.push(`${pad}${vl}`);
853
+ appendTrailing(lines.length - 1, pair.comment);
854
+ if (valLeading !== void 0) for (const cl of commentBlockLines(valLeading)) lines.push(`${pad}${cl}`);
855
+ for (const vl of valLines) lines.push(vl === "" ? "" : `${pad}${vl}`);
693
856
  }
694
857
  }
695
858
  }
696
859
  }
860
+ if (emitComments && node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) lines.push(cl);
697
861
  const prefix = buildMetadataPrefix(node.tag, node.anchor);
698
862
  if (prefix) lines.unshift(prefix);
699
863
  return lines;
@@ -740,9 +904,32 @@ function stringifySeqNodeLines(node, ctx, depth) {
740
904
  if (emptyPrefix) line = `${emptyPrefix} ${line}`;
741
905
  return [line];
742
906
  }
907
+ const emitComments = !ctx.forceDefaultStyles;
908
+ const itemFields = (item) => {
909
+ if (!emitComments || item instanceof YamlAlias) return void 0;
910
+ if (item.commentBefore === void 0 && item.comment === void 0 && item.spaceBefore !== true) return void 0;
911
+ return item;
912
+ };
743
913
  if (style === "flow") {
744
- let line = `[${items.map((item) => stringifyNodeLines(item, ctx, depth + 1).join(" ")).join(", ")}]`;
745
914
  const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
915
+ if (emitComments && (node.comment !== void 0 || items.some((it) => itemFields(it) !== void 0))) {
916
+ const flowPad = " ".repeat(ctx.indent);
917
+ const flowLines = [flowPrefix ? `${flowPrefix} [` : "["];
918
+ for (let fi = 0; fi < items.length; fi++) {
919
+ const item = items[fi];
920
+ const fields = itemFields(item);
921
+ if (fields?.spaceBefore === true) flowLines.push("");
922
+ if (fields?.commentBefore !== void 0) for (const cl of commentBlockLines(fields.commentBefore)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
923
+ const itemStr = stringifyNodeLines(item, ctx, depth + 1).join(" ");
924
+ const comma = fi < items.length - 1 ? "," : "";
925
+ const trailing = fields?.comment !== void 0 ? renderTrailingComment(fields.comment) : "";
926
+ flowLines.push(`${flowPad}${itemStr}${comma}${trailing}`);
927
+ }
928
+ if (node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
929
+ flowLines.push("]");
930
+ return flowLines;
931
+ }
932
+ let line = `[${items.map((item) => stringifyNodeLines(item, ctx, depth + 1).join(" ")).join(", ")}]`;
746
933
  if (flowPrefix) line = `${flowPrefix} ${line}`;
747
934
  return [line];
748
935
  }
@@ -753,21 +940,37 @@ function stringifySeqNodeLines(node, ctx, depth) {
753
940
  parentPosition: "block-seq-item"
754
941
  };
755
942
  for (const item of items) {
943
+ const fields = itemFields(item);
944
+ if (fields?.spaceBefore === true) lines.push("");
945
+ if (fields?.commentBefore !== void 0) for (const cl of commentBlockLines(fields.commentBefore)) lines.push(cl);
946
+ /** Append the item's trailing comment to `lines[idx]`; `-1` drops it. */
947
+ const appendItemTrailing = (idx) => {
948
+ if (fields?.comment === void 0 || idx < 0 || idx >= lines.length) return;
949
+ lines[idx] = `${lines[idx]}${renderTrailingComment(fields.comment)}`;
950
+ };
756
951
  const itemLines = stringifyNodeLines(item, itemCtx, depth + 1);
757
- if (itemLines.length === 1) lines.push(itemLines[0] === "" ? "-" : `- ${itemLines[0]}`);
758
- else {
952
+ if (itemLines.length === 1) {
953
+ lines.push(itemLines[0] === "" ? "-" : `- ${itemLines[0]}`);
954
+ appendItemTrailing(lines.length - 1);
955
+ } else {
759
956
  const first = itemLines[0];
760
957
  const firstStripped = stripScalarMetadataPrefix(first);
761
958
  const itemIsScalar = item instanceof YamlScalar;
762
- if (firstStripped.startsWith("|") || firstStripped.startsWith(">") || itemIsScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""))) {
959
+ const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
960
+ if (isBlockScalarHeader || itemIsScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""))) {
961
+ const headerIdx = lines.length;
763
962
  lines.push(`- ${first}`);
764
963
  for (let i = 1; i < itemLines.length; i++) lines.push(itemLines[i]);
964
+ appendItemTrailing(isBlockScalarHeader ? headerIdx : lines.length - 1);
765
965
  } else {
766
966
  lines.push(first === "" ? "-" : `- ${first}`);
767
- for (let i = 1; i < itemLines.length; i++) lines.push(`${pad}${itemLines[i]}`);
967
+ const dashLineIdx = lines.length - 1;
968
+ for (let i = 1; i < itemLines.length; i++) lines.push(itemLines[i] === "" ? "" : `${pad}${itemLines[i]}`);
969
+ appendItemTrailing(dashLineIdx);
768
970
  }
769
971
  }
770
972
  }
973
+ if (emitComments && node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) lines.push(cl);
771
974
  const prefix = buildMetadataPrefix(node.tag, node.anchor);
772
975
  if (prefix) lines.unshift(prefix);
773
976
  return lines;
@@ -795,7 +998,8 @@ function stringifyDocument(doc, options) {
795
998
  const ctx = createContext(options);
796
999
  const finalNewline = options?.finalNewline ?? true;
797
1000
  let contents = doc.contents;
798
- const docComment = ctx.forceDefaultStyles ? void 0 : doc.comment;
1001
+ const docCommentRaw = ctx.forceDefaultStyles ? void 0 : doc.comment;
1002
+ const docComment = docCommentRaw !== void 0 ? commentBlockLines(docCommentRaw).join("\n") : void 0;
799
1003
  if (ctx.forceDefaultStyles && contents) {
800
1004
  contents = stripNodeComments(contents);
801
1005
  const tagMap = buildTagMap(doc.directives);
@@ -806,16 +1010,28 @@ function stringifyDocument(doc, options) {
806
1010
  if (doc.hasDocumentStart) return `---\n${doc.hasDocumentEnd ? "...\n" : ""}`;
807
1011
  return "";
808
1012
  }
1013
+ if (docComment !== void 0 || doc.commentAfter !== void 0 || doc.hasDocumentStart === true || doc.hasDocumentEnd === true) {
1014
+ const parts = [];
1015
+ if (docComment !== void 0) parts.push(`${docComment}\n`);
1016
+ if (doc.hasDocumentStart) parts.push("---\n");
1017
+ if (doc.hasDocumentEnd) parts.push("...\n");
1018
+ if (doc.commentAfter !== void 0) parts.push(`${commentBlockLines(doc.commentAfter).join("\n")}\n`);
1019
+ const out = parts.join("");
1020
+ return finalNewline ? out : out.replace(/\n$/, "");
1021
+ }
809
1022
  return finalNewline ? "null\n" : "null";
810
1023
  }
811
1024
  const result = stringifyNodeLines(contents, ctx, 0).join("\n");
812
- const body = finalNewline ? `${result}\n` : result;
813
1025
  const needsTerminatorForKeepChomp = ctx.forceDefaultStyles && endsWithKeepChomp(result);
814
1026
  const needsTerminatorForAnchoredPlainScalar = ctx.forceDefaultStyles && doc.hasDocumentStart && contents instanceof YamlScalar && contents.style === "plain" && contents.anchor !== void 0 && !contents.tag;
815
1027
  const looksLikeDirectiveContinuation = contents instanceof YamlScalar && typeof contents.value === "string" && / %[A-Z]/.test(contents.value);
816
1028
  const needsTerminatorForMultilinePlainScalar = ctx.forceDefaultStyles && doc.hasDocumentStart && contents instanceof YamlScalar && contents.style === "plain" && contents.sourceMultiline === true && looksLikeDirectiveContinuation;
817
1029
  const needsTerminatorForDocStartTab = ctx.forceDefaultStyles && doc.hasDocumentStartTab === true;
818
- const docEnd = doc.hasDocumentEnd || needsTerminatorForKeepChomp || needsTerminatorForAnchoredPlainScalar || needsTerminatorForMultilinePlainScalar || needsTerminatorForDocStartTab ? "...\n" : "";
1030
+ const docEndMarker = doc.hasDocumentEnd || needsTerminatorForKeepChomp || needsTerminatorForAnchoredPlainScalar || needsTerminatorForMultilinePlainScalar || needsTerminatorForDocStartTab ? "...\n" : "";
1031
+ const docCommentAfter = ctx.forceDefaultStyles || doc.commentAfter === void 0 ? void 0 : commentBlockLines(doc.commentAfter).join("\n");
1032
+ const docEndFull = docCommentAfter !== void 0 ? `${docEndMarker}${docCommentAfter}\n` : docEndMarker;
1033
+ const body = finalNewline || docEndFull !== "" ? `${result}\n` : result;
1034
+ const docEnd = finalNewline || docEndFull === "" ? docEndFull : docEndFull.replace(/\n$/, "");
819
1035
  if (doc.hasDocumentStart) {
820
1036
  const rootTag = contents && "tag" in contents ? contents.tag : void 0;
821
1037
  const rootAnchor = contents && "anchor" in contents ? contents.anchor : void 0;
@@ -833,12 +1049,12 @@ function stringifyDocument(doc, options) {
833
1049
  else if (bodyClean.startsWith(metaInlinePrefix)) bodyClean = bodyClean.slice(metaInlinePrefix.length);
834
1050
  const docStart = `--- ${metaStr}`;
835
1051
  const sep = isCollection ? "\n" : " ";
836
- return docComment ? `# ${docComment}\n${docStart}${sep}${bodyClean}${docEnd}` : `${docStart}${sep}${bodyClean}${docEnd}`;
1052
+ return docComment ? `${docComment}\n${docStart}${sep}${bodyClean}${docEnd}` : `${docStart}${sep}${bodyClean}${docEnd}`;
837
1053
  }
838
- if (isScalar) return docComment ? `# ${docComment}\n--- ${body}${docEnd}` : `--- ${body}${docEnd}`;
839
- return docComment ? `# ${docComment}\n---\n${body}${docEnd}` : `---\n${body}${docEnd}`;
1054
+ if (isScalar) return docComment ? `${docComment}\n--- ${body}${docEnd}` : `--- ${body}${docEnd}`;
1055
+ return docComment ? `${docComment}\n---\n${body}${docEnd}` : `---\n${body}${docEnd}`;
840
1056
  }
841
- return docComment ? `# ${docComment}\n${body}${docEnd}` : `${body}${docEnd}`;
1057
+ return docComment ? `${docComment}\n${body}${docEnd}` : `${body}${docEnd}`;
842
1058
  }
843
1059
 
844
1060
  //#endregion
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@effected/yaml",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
- "description": "Zero-dependency YAML parsing, editing and formatting as Effect schemas.",
5
+ "description": "Zero-dependency YAML parsing, editing, formatting and linting as Effect schemas.",
6
6
  "keywords": [
7
7
  "yaml",
8
8
  "parser",
@@ -12,6 +12,7 @@
12
12
  "cst",
13
13
  "editing",
14
14
  "formatting",
15
+ "linting",
15
16
  "effected"
16
17
  ],
17
18
  "homepage": "https://github.com/spencerbeggs/effected/tree/main/packages/yaml#readme",
@@ -40,7 +41,7 @@
40
41
  "./package.json": "./package.json"
41
42
  },
42
43
  "peerDependencies": {
43
- "effect": "4.0.0-beta.101"
44
+ "effect": "4.0.0-beta.107"
44
45
  },
45
46
  "engines": {
46
47
  "node": ">=24.11.0"