@effected/yaml 0.7.0 → 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 (39) hide show
  1. package/README.md +78 -7
  2. package/Yaml.js +80 -32
  3. package/YamlDocument.js +22 -3
  4. package/YamlFormat.js +170 -19
  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 +48 -11
  18. package/internal/composer/state.js +16 -2
  19. package/internal/cst-parser.js +2 -1
  20. package/internal/diagnostics.js +12 -3
  21. package/internal/fold.js +22 -10
  22. package/internal/rules/catalog.js +39 -0
  23. package/internal/rules/colon-spacing.js +77 -0
  24. package/internal/rules/comments-spacing.js +68 -0
  25. package/internal/rules/document-end.js +65 -0
  26. package/internal/rules/document-start.js +64 -0
  27. package/internal/rules/empty-lines.js +70 -0
  28. package/internal/rules/eof-newline.js +33 -0
  29. package/internal/rules/hyphen-spacing.js +52 -0
  30. package/internal/rules/indentation.js +103 -0
  31. package/internal/rules/key-duplicates.js +49 -0
  32. package/internal/rules/line-length.js +36 -0
  33. package/internal/rules/parse-validity.js +33 -0
  34. package/internal/rules/quoted-strings.js +81 -0
  35. package/internal/rules/trailing-spaces.js +39 -0
  36. package/internal/rules/truthy.js +84 -0
  37. package/internal/rules/util.js +92 -0
  38. package/internal/stringifier.js +266 -54
  39. package/package.json +3 -2
@@ -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)$/;
@@ -176,7 +197,9 @@ function renderSingleQuoted(s) {
176
197
  * Block literal and block folded styles are always accepted for single-line
177
198
  * strings even though the output is unusual.
178
199
  */
179
- 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;
180
203
  if (s.includes("\n")) {
181
204
  let hasControl = false;
182
205
  for (let i = 0; i < s.length; i++) {
@@ -192,14 +215,14 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
192
215
  if (hasInteriorTrailingWhitespace(s)) return renderDoubleQuoted(s, canonical);
193
216
  if (hasNewlineSpacesTab(s)) return renderDoubleQuoted(s, canonical);
194
217
  }
195
- if (style === "block-literal") return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
196
- 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);
197
220
  if (style === "plain" || style === "single-quoted") {
198
221
  if (canonical) {
199
222
  const sq = renderSingleQuotedMultiline(s, indent);
200
223
  if (sq !== null) return sq;
201
224
  }
202
- return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
225
+ return renderBlockLiteral(s, indent, explicitChomp, parentPosition, fidelity, fidelityIndent);
203
226
  }
204
227
  return renderDoubleQuoted(s, canonical);
205
228
  }
@@ -215,8 +238,8 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
215
238
  return s;
216
239
  case "single-quoted": return renderSingleQuoted(s);
217
240
  case "double-quoted": return renderDoubleQuoted(s, canonical);
218
- case "block-literal": return renderBlockLiteral(s, indent, explicitChomp, parentPosition);
219
- 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);
220
243
  }
221
244
  }
222
245
  /**
@@ -361,6 +384,18 @@ function stringifyObjectLines(obj, ctx, depth) {
361
384
  const keyStr = renderKey(k);
362
385
  const val = obj[k];
363
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
+ }
364
399
  if (valLines.length === 1 && !isBlockCollection(val, ctx)) lines.push(`${keyStr}: ${valLines[0]}`);
365
400
  else {
366
401
  const first = valLines[0];
@@ -439,8 +474,11 @@ function normalizeNodeTags(node, tagMap) {
439
474
  style: node.style,
440
475
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
441
476
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
477
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
442
478
  ...node.comment !== void 0 ? { comment: node.comment } : {},
479
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
443
480
  ...node.chomp !== void 0 ? { chomp: node.chomp } : {},
481
+ ...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
444
482
  ...node.raw !== void 0 ? { raw: node.raw } : {},
445
483
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
446
484
  offset: node.offset,
@@ -450,12 +488,16 @@ function normalizeNodeTags(node, tagMap) {
450
488
  items: node.items.map((pair) => new YamlPair({
451
489
  key: normalizeNodeTags(pair.key, tagMap),
452
490
  value: pair.value ? normalizeNodeTags(pair.value, tagMap) : null,
453
- ...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 } : {}
454
494
  })),
455
495
  style: node.style,
456
496
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
457
497
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
498
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
458
499
  ...node.comment !== void 0 ? { comment: node.comment } : {},
500
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
459
501
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
460
502
  offset: node.offset,
461
503
  length: node.length
@@ -465,7 +507,9 @@ function normalizeNodeTags(node, tagMap) {
465
507
  style: node.style,
466
508
  ...node.tag ? { tag: normalizeTag(node.tag, tagMap) } : {},
467
509
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
510
+ ...node.commentBefore !== void 0 ? { commentBefore: node.commentBefore } : {},
468
511
  ...node.comment !== void 0 ? { comment: node.comment } : {},
512
+ ...node.spaceBefore !== void 0 ? { spaceBefore: node.spaceBefore } : {},
469
513
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
470
514
  offset: node.offset,
471
515
  length: node.length
@@ -483,6 +527,7 @@ function stripNodeComments(node) {
483
527
  ...node.tag !== void 0 ? { tag: node.tag } : {},
484
528
  ...node.anchor !== void 0 ? { anchor: node.anchor } : {},
485
529
  ...node.chomp !== void 0 ? { chomp: node.chomp } : {},
530
+ ...node.blockIndent !== void 0 ? { blockIndent: node.blockIndent } : {},
486
531
  ...node.raw !== void 0 ? { raw: node.raw } : {},
487
532
  ...node.sourceMultiline !== void 0 ? { sourceMultiline: node.sourceMultiline } : {},
488
533
  offset: node.offset,
@@ -542,7 +587,7 @@ function stringifyScalarNodeLines(node, ctx) {
542
587
  else lines = ["null"];
543
588
  } else if (typeof val === "boolean") lines = [val ? "true" : "false"];
544
589
  else if (typeof val === "number") lines = [node.raw !== void 0 ? node.raw : renderNumber(val)];
545
- 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");
546
591
  else lines = [renderDoubleQuoted(String(val))];
547
592
  if (node.tag) lines[0] = `${node.tag} ${lines[0]}`;
548
593
  if (node.anchor) lines[0] = `&${node.anchor} ${lines[0]}`;
@@ -582,6 +627,79 @@ function stringifyMappingKeyLines(node, ctx, depth) {
582
627
  return stringifyNodeLines(node, ctx, depth);
583
628
  }
584
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
+ /**
585
703
  * Stringifies a YamlMap node into lines, using the node's collection style.
586
704
  */
587
705
  function stringifyMapNodeLines(node, ctx, depth) {
@@ -599,56 +717,68 @@ function stringifyMapNodeLines(node, ctx, depth) {
599
717
  return [line];
600
718
  }
601
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
+ }
602
738
  let line = `{${items.map((pair) => {
603
739
  return `${pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null"}`;
604
740
  }).join(", ")}}`;
605
- const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
606
741
  if (flowPrefix) line = `${flowPrefix} ${line}`;
607
742
  return [line];
608
743
  }
609
744
  const pad = " ".repeat(ctx.indent);
745
+ const emitComments = !ctx.forceDefaultStyles;
610
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;
611
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);
612
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");
613
758
  if ((pair.key instanceof YamlMap || pair.key instanceof YamlSeq) && pair.key.items.length > 0 || keyIsScalarWithNewline) {
614
759
  const keyLines = stringifyNodeLines(pair.key, ctx, depth + 1);
615
760
  lines.push(`? ${keyLines[0]}`);
761
+ const explicitKeyLineIdx = lines.length - 1;
616
762
  const firstTokens = keyLines[0].trim().split(/\s+/).filter(Boolean);
617
763
  const firstIsMetaOnly = firstTokens.length > 0 && firstTokens.every((t) => t.startsWith("&") || t.startsWith("!"));
618
764
  const keyIsBlockScalar = pair.key instanceof YamlScalar && (pair.key.style === "block-literal" || pair.key.style === "block-folded");
619
- const contPad = firstIsMetaOnly || keyIsBlockScalar ? "" : pad;
765
+ const contPad = firstIsMetaOnly || keyIsBlockScalar ? "" : EXPLICIT_COMPACT_PAD;
620
766
  for (let k = 1; k < keyLines.length; k++) lines.push(`${contPad}${keyLines[k]}`);
621
- const valNode = pair.value;
622
- if (!valNode) lines.push(":");
623
- else {
624
- const valLines = stringifyNodeLines(valNode, ctx, depth + 1);
625
- if (valLines.length === 1) lines.push(`: ${valLines[0]}`);
626
- else {
627
- const first = valLines[0];
628
- const firstStripped = stripScalarMetadataPrefix(first);
629
- const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
630
- const isInlineQuoted = valNode instanceof YamlScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""));
631
- if (isBlockScalarHeader || isInlineQuoted) {
632
- lines.push(`: ${first}`);
633
- for (let v = 1; v < valLines.length; v++) lines.push(valLines[v]);
634
- } else if (first.startsWith("-")) {
635
- lines.push(`: ${first}`);
636
- for (let v = 1; v < valLines.length; v++) lines.push(`${pad}${valLines[v]}`);
637
- } else if (valNode instanceof YamlMap && valNode.items.length > 0 && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block") {
638
- lines.push(`: ${first}`);
639
- for (let v = 1; v < valLines.length; v++) lines.push(`${pad}${valLines[v]}`);
640
- } else {
641
- lines.push(":");
642
- for (const vl of valLines) lines.push(`${pad}${vl}`);
643
- }
644
- }
645
- }
767
+ const inlineIdx = pushExplicitKeyValueLines(lines, pair.value, ctx, depth, pad);
768
+ appendTrailing(inlineIdx >= 0 ? inlineIdx : keyLines.length === 1 ? explicitKeyLineIdx : -1, pair.comment);
646
769
  continue;
647
770
  }
648
771
  let resolvedKeyStr;
649
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;
650
773
  else resolvedKeyStr = pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null";
651
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
+ }
652
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);
653
783
  const sep = pair.key instanceof YamlAlias || keyIsAnchoredOrTaggedEmpty ? " :" : ":";
654
784
  const valNode = pair.value;
@@ -658,16 +788,20 @@ function stringifyMapNodeLines(node, ctx, depth) {
658
788
  const isRootFlowMap = ctx.parentPosition === void 0 && node.style === "flow" && node.sourceMultiline === true;
659
789
  if (ctx.forceDefaultStyles && isRootFlowMap && isPlainKey && keyIsNonEmpty) lines.push(`${keyStr}${sep} null`);
660
790
  else lines.push(`${keyStr}${sep}`);
791
+ appendTrailing(lines.length - 1, pair.comment);
661
792
  continue;
662
793
  }
663
794
  const valLines = stringifyNodeLines(valNode, {
664
795
  ...ctx,
665
796
  parentPosition: "block-map-value"
666
797
  }, depth + 1);
798
+ const valLeading = valueLeading(valNode);
667
799
  if (valNode instanceof YamlSeq && valNode.items.length > 0 && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block") {
668
800
  const seqMeta = buildMetadataPrefix(valNode.tag, valNode.anchor);
669
801
  const startIdx = seqMeta ? 1 : 0;
670
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);
671
805
  for (let i = startIdx; i < valLines.length; i++) {
672
806
  const vl = valLines[i];
673
807
  lines.push(ctx.indentSequences && vl !== "" ? `${pad}${vl}` : vl);
@@ -676,28 +810,54 @@ function stringifyMapNodeLines(node, ctx, depth) {
676
810
  const mapMeta = buildMetadataPrefix(valNode.tag, valNode.anchor);
677
811
  const startIdx = mapMeta ? 1 : 0;
678
812
  lines.push(mapMeta ? `${keyStr}${sep} ${mapMeta}` : `${keyStr}${sep}`);
679
- for (let i = startIdx; i < valLines.length; i++) lines.push(`${pad}${valLines[i]}`);
680
- } else if (valLines.length === 1) lines.push(valLines[0] === "" ? `${keyStr}${sep}` : `${keyStr}${sep} ${valLines[0]}`);
681
- 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 {
682
825
  const first = valLines[0];
683
826
  const firstStripped = stripScalarMetadataPrefix(first);
684
827
  const isBlockScalarHeader = firstStripped.startsWith("|") || firstStripped.startsWith(">");
685
828
  const isInlineQuoted = valNode instanceof YamlScalar && (firstStripped.startsWith("'") || firstStripped.startsWith("\""));
686
829
  if (isBlockScalarHeader || isInlineQuoted) {
687
- lines.push(`${keyStr}${sep} ${first}`);
688
- 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
+ }
689
845
  } else {
690
846
  const mapMeta = valNode instanceof YamlMap && (ctx.forceDefaultStyles ? ctx.defaultCollectionStyle : valNode.style ?? ctx.defaultCollectionStyle) === "block" ? buildMetadataPrefix(valNode.tag, valNode.anchor) : void 0;
691
847
  if (mapMeta) {
692
848
  lines.push(`${keyStr}${sep} ${mapMeta}`);
693
- 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]}`);
694
851
  } else {
695
852
  lines.push(`${keyStr}${sep}`);
696
- 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}`);
697
856
  }
698
857
  }
699
858
  }
700
859
  }
860
+ if (emitComments && node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) lines.push(cl);
701
861
  const prefix = buildMetadataPrefix(node.tag, node.anchor);
702
862
  if (prefix) lines.unshift(prefix);
703
863
  return lines;
@@ -744,9 +904,32 @@ function stringifySeqNodeLines(node, ctx, depth) {
744
904
  if (emptyPrefix) line = `${emptyPrefix} ${line}`;
745
905
  return [line];
746
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
+ };
747
913
  if (style === "flow") {
748
- let line = `[${items.map((item) => stringifyNodeLines(item, ctx, depth + 1).join(" ")).join(", ")}]`;
749
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(", ")}]`;
750
933
  if (flowPrefix) line = `${flowPrefix} ${line}`;
751
934
  return [line];
752
935
  }
@@ -757,21 +940,37 @@ function stringifySeqNodeLines(node, ctx, depth) {
757
940
  parentPosition: "block-seq-item"
758
941
  };
759
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
+ };
760
951
  const itemLines = stringifyNodeLines(item, itemCtx, depth + 1);
761
- if (itemLines.length === 1) lines.push(itemLines[0] === "" ? "-" : `- ${itemLines[0]}`);
762
- else {
952
+ if (itemLines.length === 1) {
953
+ lines.push(itemLines[0] === "" ? "-" : `- ${itemLines[0]}`);
954
+ appendItemTrailing(lines.length - 1);
955
+ } else {
763
956
  const first = itemLines[0];
764
957
  const firstStripped = stripScalarMetadataPrefix(first);
765
958
  const itemIsScalar = item instanceof YamlScalar;
766
- 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;
767
962
  lines.push(`- ${first}`);
768
963
  for (let i = 1; i < itemLines.length; i++) lines.push(itemLines[i]);
964
+ appendItemTrailing(isBlockScalarHeader ? headerIdx : lines.length - 1);
769
965
  } else {
770
966
  lines.push(first === "" ? "-" : `- ${first}`);
771
- 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);
772
970
  }
773
971
  }
774
972
  }
973
+ if (emitComments && node.comment !== void 0) for (const cl of commentBlockLines(node.comment)) lines.push(cl);
775
974
  const prefix = buildMetadataPrefix(node.tag, node.anchor);
776
975
  if (prefix) lines.unshift(prefix);
777
976
  return lines;
@@ -799,7 +998,8 @@ function stringifyDocument(doc, options) {
799
998
  const ctx = createContext(options);
800
999
  const finalNewline = options?.finalNewline ?? true;
801
1000
  let contents = doc.contents;
802
- 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;
803
1003
  if (ctx.forceDefaultStyles && contents) {
804
1004
  contents = stripNodeComments(contents);
805
1005
  const tagMap = buildTagMap(doc.directives);
@@ -810,16 +1010,28 @@ function stringifyDocument(doc, options) {
810
1010
  if (doc.hasDocumentStart) return `---\n${doc.hasDocumentEnd ? "...\n" : ""}`;
811
1011
  return "";
812
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
+ }
813
1022
  return finalNewline ? "null\n" : "null";
814
1023
  }
815
1024
  const result = stringifyNodeLines(contents, ctx, 0).join("\n");
816
- const body = finalNewline ? `${result}\n` : result;
817
1025
  const needsTerminatorForKeepChomp = ctx.forceDefaultStyles && endsWithKeepChomp(result);
818
1026
  const needsTerminatorForAnchoredPlainScalar = ctx.forceDefaultStyles && doc.hasDocumentStart && contents instanceof YamlScalar && contents.style === "plain" && contents.anchor !== void 0 && !contents.tag;
819
1027
  const looksLikeDirectiveContinuation = contents instanceof YamlScalar && typeof contents.value === "string" && / %[A-Z]/.test(contents.value);
820
1028
  const needsTerminatorForMultilinePlainScalar = ctx.forceDefaultStyles && doc.hasDocumentStart && contents instanceof YamlScalar && contents.style === "plain" && contents.sourceMultiline === true && looksLikeDirectiveContinuation;
821
1029
  const needsTerminatorForDocStartTab = ctx.forceDefaultStyles && doc.hasDocumentStartTab === true;
822
- 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$/, "");
823
1035
  if (doc.hasDocumentStart) {
824
1036
  const rootTag = contents && "tag" in contents ? contents.tag : void 0;
825
1037
  const rootAnchor = contents && "anchor" in contents ? contents.anchor : void 0;
@@ -837,12 +1049,12 @@ function stringifyDocument(doc, options) {
837
1049
  else if (bodyClean.startsWith(metaInlinePrefix)) bodyClean = bodyClean.slice(metaInlinePrefix.length);
838
1050
  const docStart = `--- ${metaStr}`;
839
1051
  const sep = isCollection ? "\n" : " ";
840
- 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}`;
841
1053
  }
842
- if (isScalar) return docComment ? `# ${docComment}\n--- ${body}${docEnd}` : `--- ${body}${docEnd}`;
843
- 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}`;
844
1056
  }
845
- return docComment ? `# ${docComment}\n${body}${docEnd}` : `${body}${docEnd}`;
1057
+ return docComment ? `${docComment}\n${body}${docEnd}` : `${body}${docEnd}`;
846
1058
  }
847
1059
 
848
1060
  //#endregion
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@effected/yaml",
3
- "version": "0.7.0",
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",