yaml 2.0.0-5 → 2.0.0-6

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 (43) hide show
  1. package/browser/dist/compose/compose-doc.js +1 -1
  2. package/browser/dist/compose/composer.js +1 -1
  3. package/browser/dist/compose/resolve-block-map.js +5 -2
  4. package/browser/dist/compose/resolve-block-scalar.js +1 -1
  5. package/browser/dist/compose/resolve-block-seq.js +1 -1
  6. package/browser/dist/compose/resolve-end.js +2 -2
  7. package/browser/dist/compose/resolve-flow-collection.js +10 -5
  8. package/browser/dist/compose/resolve-props.js +30 -7
  9. package/browser/dist/compose/util-map-includes.js +17 -0
  10. package/browser/dist/options.js +1 -0
  11. package/browser/dist/parse/parser.js +53 -52
  12. package/browser/dist/stringify/stringifyCollection.js +23 -9
  13. package/browser/dist/stringify/stringifyComment.js +14 -0
  14. package/browser/dist/stringify/stringifyDocument.js +8 -5
  15. package/browser/dist/stringify/stringifyPair.js +3 -5
  16. package/dist/compose/compose-doc.js +1 -1
  17. package/dist/compose/composer.js +1 -1
  18. package/dist/compose/resolve-block-map.d.ts +2 -1
  19. package/dist/compose/resolve-block-map.js +5 -2
  20. package/dist/compose/resolve-block-scalar.js +1 -1
  21. package/dist/compose/resolve-block-seq.js +1 -1
  22. package/dist/compose/resolve-end.js +2 -2
  23. package/dist/compose/resolve-flow-collection.js +10 -5
  24. package/dist/compose/resolve-props.d.ts +3 -4
  25. package/dist/compose/resolve-props.js +30 -7
  26. package/dist/compose/util-map-includes.d.ts +4 -0
  27. package/dist/compose/util-map-includes.js +19 -0
  28. package/dist/doc/Document.d.ts +4 -2
  29. package/dist/errors.d.ts +1 -1
  30. package/dist/nodes/Node.d.ts +4 -2
  31. package/dist/options.d.ts +15 -0
  32. package/dist/options.js +1 -0
  33. package/dist/parse/parser.d.ts +1 -0
  34. package/dist/parse/parser.js +53 -52
  35. package/dist/stringify/stringifyCollection.d.ts +1 -1
  36. package/dist/stringify/stringifyCollection.js +24 -10
  37. package/dist/stringify/stringifyComment.d.ts +2 -0
  38. package/dist/stringify/{addComment.js → stringifyComment.js} +5 -1
  39. package/dist/stringify/stringifyDocument.js +9 -6
  40. package/dist/stringify/stringifyPair.js +7 -9
  41. package/package.json +5 -5
  42. package/browser/dist/stringify/addComment.js +0 -11
  43. package/dist/stringify/addComment.d.ts +0 -2
@@ -7,6 +7,7 @@ var YAMLSeq = require('../nodes/YAMLSeq.js');
7
7
  var resolveEnd = require('./resolve-end.js');
8
8
  var resolveProps = require('./resolve-props.js');
9
9
  var utilContainsNewline = require('./util-contains-newline.js');
10
+ var utilMapIncludes = require('./util-map-includes.js');
10
11
 
11
12
  const blockMsg = 'Block collections are not allowed within flow collections';
12
13
  const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq');
@@ -21,9 +22,9 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
21
22
  for (let i = 0; i < fc.items.length; ++i) {
22
23
  const { start, key, sep, value } = fc.items[i];
23
24
  const props = resolveProps.resolveProps(start, {
24
- ctx,
25
25
  flow: fcName,
26
26
  indicator: 'explicit-key-ind',
27
+ next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
27
28
  offset,
28
29
  onError,
29
30
  startOnNewline: false
@@ -101,9 +102,9 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
101
102
  onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
102
103
  // value properties
103
104
  const valueProps = resolveProps.resolveProps(sep || [], {
104
- ctx,
105
105
  flow: fcName,
106
106
  indicator: 'map-value-ind',
107
+ next: value,
107
108
  offset: keyNode.range[2],
108
109
  onError,
109
110
  startOnNewline: false
@@ -146,8 +147,12 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
146
147
  keyNode.comment = valueProps.comment;
147
148
  }
148
149
  const pair = new Pair.Pair(keyNode, valueNode);
149
- if (isMap)
150
- coll.items.push(pair);
150
+ if (isMap) {
151
+ const map = coll;
152
+ if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
153
+ onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
154
+ map.items.push(pair);
155
+ }
151
156
  else {
152
157
  const map = new YAMLMap.YAMLMap(ctx.schema);
153
158
  map.flow = true;
@@ -161,7 +166,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
161
166
  const [ce, ...ee] = fc.end;
162
167
  let cePos = offset;
163
168
  if (ce && ce.source === expectedEnd)
164
- cePos += ce.source.length;
169
+ cePos = ce.offset + ce.source.length;
165
170
  else {
166
171
  onError(offset + 1, 'MISSING_CHAR', `Expected ${fcName} to end with ${expectedEnd}`);
167
172
  if (ce && ce.source.length !== 1)
@@ -1,15 +1,14 @@
1
- import type { SourceToken } from '../parse/cst.js';
2
- import type { ComposeContext } from './compose-node.js';
1
+ import type { SourceToken, Token } from '../parse/cst.js';
3
2
  import type { ComposeErrorHandler } from './composer.js';
4
3
  export interface ResolvePropsArg {
5
- ctx: ComposeContext;
6
4
  flow?: string;
7
5
  indicator: 'doc-start' | 'explicit-key-ind' | 'map-value-ind' | 'seq-item-ind';
6
+ next: Token | null | undefined;
8
7
  offset: number;
9
8
  onError: ComposeErrorHandler;
10
9
  startOnNewline: boolean;
11
10
  }
12
- export declare function resolveProps(tokens: SourceToken[], { ctx, flow, indicator, offset, onError, startOnNewline }: ResolvePropsArg): {
11
+ export declare function resolveProps(tokens: SourceToken[], { flow, indicator, next, offset, onError, startOnNewline }: ResolvePropsArg): {
13
12
  comma: SourceToken | null;
14
13
  found: SourceToken | null;
15
14
  spaceBefore: boolean;
@@ -1,18 +1,26 @@
1
1
  'use strict';
2
2
 
3
- function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNewline }) {
3
+ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnNewline }) {
4
4
  let spaceBefore = false;
5
5
  let atNewline = startOnNewline;
6
6
  let hasSpace = startOnNewline;
7
7
  let comment = '';
8
8
  let commentSep = '';
9
9
  let hasNewline = false;
10
+ let reqSpace = false;
10
11
  let anchor = null;
11
12
  let tag = null;
12
13
  let comma = null;
13
14
  let found = null;
14
15
  let start = null;
15
16
  for (const token of tokens) {
17
+ if (reqSpace) {
18
+ if (token.type !== 'space' &&
19
+ token.type !== 'newline' &&
20
+ token.type !== 'comma')
21
+ onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
22
+ reqSpace = false;
23
+ }
16
24
  switch (token.type) {
17
25
  case 'space':
18
26
  // At the doc level, tabs at line start may be parsed
@@ -26,23 +34,29 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
26
34
  hasSpace = true;
27
35
  break;
28
36
  case 'comment': {
29
- if (ctx.options.strict && !hasSpace)
30
- onError(token, 'COMMENT_SPACE', 'Comments must be separated from other tokens by white space characters');
31
- const cb = token.source.substring(1);
37
+ if (!hasSpace)
38
+ onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters');
39
+ const cb = token.source.substring(1) || ' ';
32
40
  if (!comment)
33
41
  comment = cb;
34
42
  else
35
43
  comment += commentSep + cb;
36
44
  commentSep = '';
45
+ atNewline = false;
37
46
  break;
38
47
  }
39
48
  case 'newline':
40
- if (atNewline && !comment)
41
- spaceBefore = true;
49
+ if (atNewline) {
50
+ if (comment)
51
+ comment += token.source;
52
+ else
53
+ spaceBefore = true;
54
+ }
55
+ else
56
+ commentSep += token.source;
42
57
  atNewline = true;
43
58
  hasNewline = true;
44
59
  hasSpace = true;
45
- commentSep += token.source;
46
60
  break;
47
61
  case 'anchor':
48
62
  if (anchor)
@@ -52,6 +66,7 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
52
66
  start = token.offset;
53
67
  atNewline = false;
54
68
  hasSpace = false;
69
+ reqSpace = true;
55
70
  break;
56
71
  case 'tag': {
57
72
  if (tag)
@@ -61,6 +76,7 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
61
76
  start = token.offset;
62
77
  atNewline = false;
63
78
  hasSpace = false;
79
+ reqSpace = true;
64
80
  break;
65
81
  }
66
82
  case indicator:
@@ -89,6 +105,13 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
89
105
  }
90
106
  const last = tokens[tokens.length - 1];
91
107
  const end = last ? last.offset + last.source.length : offset;
108
+ if (reqSpace &&
109
+ next &&
110
+ next.type !== 'space' &&
111
+ next.type !== 'newline' &&
112
+ next.type !== 'comma' &&
113
+ (next.type !== 'scalar' || next.source !== ''))
114
+ onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
92
115
  return {
93
116
  comma,
94
117
  found,
@@ -0,0 +1,4 @@
1
+ import { ParsedNode } from '../nodes/Node';
2
+ import { Pair } from '../nodes/Pair';
3
+ import { ComposeContext } from './compose-node';
4
+ export declare function mapIncludes(ctx: ComposeContext, items: Pair<ParsedNode>[], search: ParsedNode): boolean;
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ var Node = require('../nodes/Node.js');
4
+
5
+ function mapIncludes(ctx, items, search) {
6
+ const { uniqueKeys } = ctx.options;
7
+ if (uniqueKeys === false)
8
+ return false;
9
+ const isEqual = typeof uniqueKeys === 'function'
10
+ ? uniqueKeys
11
+ : (a, b) => a === b ||
12
+ (Node.isScalar(a) &&
13
+ Node.isScalar(b) &&
14
+ a.value === b.value &&
15
+ !(a.value === '<<' && ctx.schema.merge));
16
+ return items.some(pair => isEqual(pair.key, search));
17
+ }
18
+
19
+ exports.mapIncludes = mapIncludes;
@@ -27,8 +27,10 @@ export declare class Document<T = unknown> {
27
27
  errors: YAMLError[];
28
28
  options: Required<Omit<ParseOptions & DocumentOptions, 'lineCounter' | 'directives' | 'version'>>;
29
29
  /**
30
- * The [start, value-end, node-end] character offsets for the part of the
31
- * source parsed into this document (undefined if not parsed).
30
+ * The `[start, value-end, node-end]` character offsets for the part of the
31
+ * source parsed into this document (undefined if not parsed). The `value-end`
32
+ * and `node-end` positions are themselves not included in their respective
33
+ * ranges.
32
34
  */
33
35
  range?: Range;
34
36
  /** The schema used with the document. Use `setSchema()` to change. */
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { LineCounter } from './parse/line-counter';
2
- export declare type ErrorCode = 'ALIAS_PROPS' | 'BAD_DIRECTIVE' | 'BAD_DQ_ESCAPE' | 'BAD_INDENT' | 'BAD_PROP_ORDER' | 'BAD_SCALAR_START' | 'BLOCK_AS_IMPLICIT_KEY' | 'BLOCK_IN_FLOW' | 'COMMENT_SPACE' | 'IMPOSSIBLE' | 'KEY_OVER_1024_CHARS' | 'MISSING_ANCHOR' | 'MISSING_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN';
2
+ export declare type ErrorCode = 'ALIAS_PROPS' | 'BAD_DIRECTIVE' | 'BAD_DQ_ESCAPE' | 'BAD_INDENT' | 'BAD_PROP_ORDER' | 'BAD_SCALAR_START' | 'BLOCK_AS_IMPLICIT_KEY' | 'BLOCK_IN_FLOW' | 'DUPLICATE_KEY' | 'IMPOSSIBLE' | 'KEY_OVER_1024_CHARS' | 'MISSING_ANCHOR' | 'MISSING_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN';
3
3
  export declare type LinePos = {
4
4
  line: number;
5
5
  col: number;
@@ -31,8 +31,10 @@ export declare abstract class NodeBase {
31
31
  /** A comment before this */
32
32
  commentBefore?: string | null;
33
33
  /**
34
- * The [start, value-end, node-end] character offsets for the part of the
35
- * source parsed into this node (undefined if not parsed).
34
+ * The `[start, value-end, node-end]` character offsets for the part of the
35
+ * source parsed into this node (undefined if not parsed). The `value-end`
36
+ * and `node-end` positions are themselves not included in their respective
37
+ * ranges.
36
38
  */
37
39
  range?: Range | null;
38
40
  /** A blank line before this node and its commentBefore */
package/dist/options.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Reviver } from './doc/applyReviver.js';
2
2
  import type { Directives } from './doc/directives.js';
3
3
  import type { LogLevelId } from './log.js';
4
+ import type { ParsedNode } from './nodes/Node.js';
4
5
  import type { Pair } from './nodes/Pair.js';
5
6
  import type { Scalar } from './nodes/Scalar.js';
6
7
  import type { LineCounter } from './parse/line-counter.js';
@@ -34,6 +35,19 @@ export declare type ParseOptions = {
34
35
  * Default: `true`
35
36
  */
36
37
  strict?: boolean;
38
+ /**
39
+ * YAML requires map keys to be unique. By default, this is checked by
40
+ * comparing scalar values with `===`; deep equality is not checked for
41
+ * aliases or collections. If merge keys are enabled by the schema,
42
+ * multiple `<<` keys are allowed.
43
+ *
44
+ * Set `false` to disable, or provide your own comparator function to
45
+ * customise. The comparator will be passed two `ParsedNode` values, and
46
+ * is expected to return a `boolean` indicating their equality.
47
+ *
48
+ * Default: `true`
49
+ */
50
+ uniqueKeys?: boolean | ((a: ParsedNode, b: ParsedNode) => boolean);
37
51
  };
38
52
  export declare type DocumentOptions = {
39
53
  /**
@@ -84,6 +98,7 @@ export declare type SchemaOptions = {
84
98
  /**
85
99
  * When adding to or stringifying a map, sort the entries.
86
100
  * If `true`, sort by comparing key values with `<`.
101
+ * Does not affect item order when parsing.
87
102
  *
88
103
  * Default: `false`
89
104
  */
package/dist/options.js CHANGED
@@ -12,6 +12,7 @@ const defaultOptions = {
12
12
  logLevel: 'warn',
13
13
  prettyErrors: true,
14
14
  strict: true,
15
+ uniqueKeys: true,
15
16
  version: '1.2'
16
17
  };
17
18
 
@@ -78,6 +78,7 @@ export declare class Parser {
78
78
  private flowCollection;
79
79
  private flowScalar;
80
80
  private startBlockValue;
81
+ private atIndentedComment;
81
82
  private documentEnd;
82
83
  private lineEnd;
83
84
  }
@@ -22,38 +22,6 @@ function includesNonEmpty(list) {
22
22
  }
23
23
  return false;
24
24
  }
25
- function atFirstEmptyLineAfterComments(start) {
26
- let hasComment = false;
27
- for (let i = 0; i < start.length; ++i) {
28
- switch (start[i].type) {
29
- case 'space':
30
- break;
31
- case 'comment':
32
- hasComment = true;
33
- break;
34
- case 'newline':
35
- if (!hasComment)
36
- return false;
37
- break;
38
- default:
39
- return false;
40
- }
41
- }
42
- if (hasComment) {
43
- for (let i = start.length - 1; i >= 0; --i) {
44
- switch (start[i].type) {
45
- /* istanbul ignore next */
46
- case 'space':
47
- break;
48
- case 'newline':
49
- return true;
50
- default:
51
- return false;
52
- }
53
- }
54
- }
55
- return false;
56
- }
57
25
  function isFlowToken(token) {
58
26
  switch (token === null || token === void 0 ? void 0 : token.type) {
59
27
  case 'alias':
@@ -506,24 +474,38 @@ class Parser {
506
474
  switch (this.type) {
507
475
  case 'newline':
508
476
  this.onKeyLine = false;
509
- if (!it.sep && atFirstEmptyLineAfterComments(it.start)) {
510
- const prev = map.items[map.items.length - 2];
511
- const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
512
- if (Array.isArray(end)) {
513
- Array.prototype.push.apply(end, it.start);
514
- it.start = [this.sourceToken];
515
- return;
516
- }
477
+ if (it.value) {
478
+ const end = 'end' in it.value ? it.value.end : undefined;
479
+ const last = Array.isArray(end) ? end[end.length - 1] : undefined;
480
+ if ((last === null || last === void 0 ? void 0 : last.type) === 'comment')
481
+ end === null || end === void 0 ? void 0 : end.push(this.sourceToken);
482
+ else
483
+ map.items.push({ start: [this.sourceToken] });
517
484
  }
518
- // fallthrough
485
+ else if (it.sep)
486
+ it.sep.push(this.sourceToken);
487
+ else
488
+ it.start.push(this.sourceToken);
489
+ return;
519
490
  case 'space':
520
491
  case 'comment':
521
492
  if (it.value)
522
493
  map.items.push({ start: [this.sourceToken] });
523
494
  else if (it.sep)
524
495
  it.sep.push(this.sourceToken);
525
- else
496
+ else {
497
+ if (this.atIndentedComment(it.start, map.indent)) {
498
+ const prev = map.items[map.items.length - 2];
499
+ const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
500
+ if (Array.isArray(end)) {
501
+ Array.prototype.push.apply(end, it.start);
502
+ end.push(this.sourceToken);
503
+ map.items.pop();
504
+ return;
505
+ }
506
+ }
526
507
  it.start.push(this.sourceToken);
508
+ }
527
509
  return;
528
510
  }
529
511
  if (this.indent >= map.indent) {
@@ -628,22 +610,34 @@ class Parser {
628
610
  const it = seq.items[seq.items.length - 1];
629
611
  switch (this.type) {
630
612
  case 'newline':
631
- if (!it.value && atFirstEmptyLineAfterComments(it.start)) {
632
- const prev = seq.items[seq.items.length - 2];
633
- const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
634
- if (Array.isArray(end)) {
635
- Array.prototype.push.apply(end, it.start);
636
- it.start = [this.sourceToken];
637
- return;
638
- }
613
+ if (it.value) {
614
+ const end = 'end' in it.value ? it.value.end : undefined;
615
+ const last = Array.isArray(end) ? end[end.length - 1] : undefined;
616
+ if ((last === null || last === void 0 ? void 0 : last.type) === 'comment')
617
+ end === null || end === void 0 ? void 0 : end.push(this.sourceToken);
618
+ else
619
+ seq.items.push({ start: [this.sourceToken] });
639
620
  }
640
- // fallthrough
621
+ else
622
+ it.start.push(this.sourceToken);
623
+ return;
641
624
  case 'space':
642
625
  case 'comment':
643
626
  if (it.value)
644
627
  seq.items.push({ start: [this.sourceToken] });
645
- else
628
+ else {
629
+ if (this.atIndentedComment(it.start, seq.indent)) {
630
+ const prev = seq.items[seq.items.length - 2];
631
+ const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
632
+ if (Array.isArray(end)) {
633
+ Array.prototype.push.apply(end, it.start);
634
+ end.push(this.sourceToken);
635
+ seq.items.pop();
636
+ return;
637
+ }
638
+ }
646
639
  it.start.push(this.sourceToken);
640
+ }
647
641
  return;
648
642
  case 'anchor':
649
643
  case 'tag':
@@ -838,6 +832,13 @@ class Parser {
838
832
  }
839
833
  return null;
840
834
  }
835
+ atIndentedComment(start, indent) {
836
+ if (this.type !== 'comment')
837
+ return false;
838
+ if (this.indent <= indent)
839
+ return false;
840
+ return start.every(st => st.type === 'newline' || st.type === 'space');
841
+ }
841
842
  *documentEnd(docEnd) {
842
843
  if (this.type !== 'doc-mode') {
843
844
  if (docEnd.end)
@@ -1,5 +1,5 @@
1
1
  import { Collection } from '../nodes/Collection.js';
2
- import { StringifyContext } from '../stringify/stringify.js';
2
+ import { StringifyContext } from './stringify.js';
3
3
  declare type StringifyNode = {
4
4
  comment: boolean;
5
5
  str: string;
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var Collection = require('../nodes/Collection.js');
4
- var addComment = require('./addComment.js');
5
- var stringify = require('./stringify.js');
6
4
  var Node = require('../nodes/Node.js');
5
+ var stringify = require('./stringify.js');
6
+ var stringifyComment = require('./stringifyComment.js');
7
7
 
8
8
  function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowChars, itemIndent, onChompKeep, onComment }) {
9
9
  const { indent, indentStep } = ctx;
@@ -18,10 +18,17 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
18
18
  if (Node.isNode(item)) {
19
19
  if (!chompKeep && item.spaceBefore)
20
20
  nodes.push({ comment: true, str: '' });
21
- if (item.commentBefore) {
21
+ let cb = item.commentBefore;
22
+ if (cb && chompKeep)
23
+ cb = cb.replace(/^\n+/, '');
24
+ if (cb) {
25
+ if (/^\n+$/.test(cb))
26
+ cb = cb.substring(1);
22
27
  // This match will always succeed on a non-empty string
23
- for (const line of item.commentBefore.match(/^.*$/gm))
24
- nodes.push({ comment: true, str: `#${line}` });
28
+ for (const line of cb.match(/^.*$/gm)) {
29
+ const str = line === ' ' ? '#' : line ? `#${line}` : '';
30
+ nodes.push({ comment: true, str });
31
+ }
25
32
  }
26
33
  if (item.comment) {
27
34
  comment = item.comment;
@@ -33,10 +40,17 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
33
40
  if (ik) {
34
41
  if (!chompKeep && ik.spaceBefore)
35
42
  nodes.push({ comment: true, str: '' });
36
- if (ik.commentBefore) {
43
+ let cb = ik.commentBefore;
44
+ if (cb && chompKeep)
45
+ cb = cb.replace(/^\n+/, '');
46
+ if (cb) {
47
+ if (/^\n+$/.test(cb))
48
+ cb = cb.substring(1);
37
49
  // This match will always succeed on a non-empty string
38
- for (const line of ik.commentBefore.match(/^.*$/gm))
39
- nodes.push({ comment: true, str: `#${line}` });
50
+ for (const line of cb.match(/^.*$/gm)) {
51
+ const str = line === ' ' ? '#' : line ? `#${line}` : '';
52
+ nodes.push({ comment: true, str });
53
+ }
40
54
  }
41
55
  if (ik.comment)
42
56
  singleLineOutput = false;
@@ -58,7 +72,7 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
58
72
  let str = stringify.stringify(item, ctx, () => (comment = null), () => (chompKeep = true));
59
73
  if (inFlow && i < items.length - 1)
60
74
  str += ',';
61
- str = addComment.addComment(str, itemIndent, comment);
75
+ str = stringifyComment.addComment(str, itemIndent, comment);
62
76
  if (chompKeep && (comment || inFlow))
63
77
  chompKeep = false;
64
78
  nodes.push({ comment: false, str });
@@ -98,7 +112,7 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
98
112
  str += s ? `\n${indent}${s}` : '\n';
99
113
  }
100
114
  if (comment) {
101
- str += '\n' + comment.replace(/^/gm, `${indent}#`);
115
+ str += '\n' + stringifyComment.stringifyComment(comment, indent);
102
116
  if (onComment)
103
117
  onComment();
104
118
  }
@@ -0,0 +1,2 @@
1
+ export declare const stringifyComment: (comment: string, indent: string) => string;
2
+ export declare function addComment(str: string, indent: string, comment?: string | null): string;
@@ -1,13 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ const stringifyComment = (comment, indent) => /^\n+$/.test(comment)
4
+ ? comment.substring(1)
5
+ : comment.replace(/^(?!$)(?: $)?/gm, `${indent}#`);
3
6
  function addComment(str, indent, comment) {
4
7
  return !comment
5
8
  ? str
6
9
  : comment.includes('\n')
7
- ? `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`)
10
+ ? `${str}\n` + stringifyComment(comment, indent)
8
11
  : str.endsWith(' ')
9
12
  ? `${str}#${comment}`
10
13
  : `${str} #${comment}`;
11
14
  }
12
15
 
13
16
  exports.addComment = addComment;
17
+ exports.stringifyComment = stringifyComment;
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var Node = require('../nodes/Node.js');
4
- var addComment = require('./addComment.js');
5
4
  var stringify = require('./stringify.js');
5
+ var stringifyComment = require('./stringifyComment.js');
6
6
 
7
7
  function stringifyDocument(doc, options) {
8
8
  const lines = [];
@@ -21,7 +21,7 @@ function stringifyDocument(doc, options) {
21
21
  if (doc.commentBefore) {
22
22
  if (lines.length !== 1)
23
23
  lines.unshift('');
24
- lines.unshift(doc.commentBefore.replace(/^/gm, '#'));
24
+ lines.unshift(stringifyComment.stringifyComment(doc.commentBefore, ''));
25
25
  }
26
26
  const ctx = stringify.createStringifyContext(doc, options);
27
27
  let chompKeep = false;
@@ -31,7 +31,7 @@ function stringifyDocument(doc, options) {
31
31
  if (doc.contents.spaceBefore && hasDirectives)
32
32
  lines.push('');
33
33
  if (doc.contents.commentBefore)
34
- lines.push(doc.contents.commentBefore.replace(/^/gm, '#'));
34
+ lines.push(stringifyComment.stringifyComment(doc.contents.commentBefore, ''));
35
35
  // top-level block scalars need to be indented if followed by a comment
36
36
  ctx.forceBlockIndent = !!doc.comment;
37
37
  contentComment = doc.contents.comment;
@@ -39,7 +39,7 @@ function stringifyDocument(doc, options) {
39
39
  const onChompKeep = contentComment ? undefined : () => (chompKeep = true);
40
40
  let body = stringify.stringify(doc.contents, ctx, () => (contentComment = null), onChompKeep);
41
41
  if (contentComment)
42
- body = addComment.addComment(body, '', contentComment);
42
+ body = stringifyComment.addComment(body, '', contentComment);
43
43
  if ((body[0] === '|' || body[0] === '>') &&
44
44
  lines[lines.length - 1] === '---') {
45
45
  // Top-level block scalars with a preceding doc marker ought to use the
@@ -52,10 +52,13 @@ function stringifyDocument(doc, options) {
52
52
  else {
53
53
  lines.push(stringify.stringify(doc.contents, ctx));
54
54
  }
55
- if (doc.comment) {
55
+ let dc = doc.comment;
56
+ if (dc && chompKeep)
57
+ dc = dc.replace(/^\n+/, '');
58
+ if (dc) {
56
59
  if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '')
57
60
  lines.push('');
58
- lines.push(doc.comment.replace(/^/gm, '#'));
61
+ lines.push(stringifyComment.stringifyComment(dc, ''));
59
62
  }
60
63
  return lines.join('\n') + '\n';
61
64
  }
@@ -2,8 +2,8 @@
2
2
 
3
3
  var Node = require('../nodes/Node.js');
4
4
  var Scalar = require('../nodes/Scalar.js');
5
- var addComment = require('./addComment.js');
6
5
  var stringify = require('./stringify.js');
6
+ var stringifyComment = require('./stringifyComment.js');
7
7
 
8
8
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
9
9
  const { allNullValues, doc, indent, indentStep, options: { indentSeq, simpleKeys } } = ctx;
@@ -49,22 +49,20 @@ function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
49
49
  keyComment = null;
50
50
  if (chompKeep && !keyComment && onChompKeep)
51
51
  onChompKeep();
52
- return addComment.addComment(`? ${str}`, ctx.indent, keyComment);
52
+ return stringifyComment.addComment(`? ${str}`, ctx.indent, keyComment);
53
53
  }
54
54
  if (keyCommentDone)
55
55
  keyComment = null;
56
56
  str = explicitKey
57
- ? `? ${addComment.addComment(str, ctx.indent, keyComment)}\n${indent}:`
58
- : addComment.addComment(`${str}:`, ctx.indent, keyComment);
57
+ ? `? ${stringifyComment.addComment(str, ctx.indent, keyComment)}\n${indent}:`
58
+ : stringifyComment.addComment(`${str}:`, ctx.indent, keyComment);
59
59
  let vcb = '';
60
60
  let valueComment = null;
61
61
  if (Node.isNode(value)) {
62
62
  if (value.spaceBefore)
63
63
  vcb = '\n';
64
- if (value.commentBefore) {
65
- const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
66
- vcb += `\n${cs}`;
67
- }
64
+ if (value.commentBefore)
65
+ vcb += `\n${stringifyComment.stringifyComment(value.commentBefore, ctx.indent)}`;
68
66
  valueComment = value.comment;
69
67
  }
70
68
  else if (value && typeof value === 'object') {
@@ -108,7 +106,7 @@ function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
108
106
  valueComment = null;
109
107
  if (chompKeep && !valueComment && onChompKeep)
110
108
  onChompKeep();
111
- return addComment.addComment(str + ws + valueStr, ctx.indent, valueComment);
109
+ return stringifyComment.addComment(str + ws + valueStr, ctx.indent, valueComment);
112
110
  }
113
111
  }
114
112