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
@@ -12,8 +12,8 @@ function composeDoc(options, directives, { offset, start, value, end }, onError)
12
12
  schema: doc.schema
13
13
  };
14
14
  const props = resolveProps(start, {
15
- ctx,
16
15
  indicator: 'doc-start',
16
+ next: value || (end === null || end === void 0 ? void 0 : end[0]),
17
17
  offset,
18
18
  onError,
19
19
  startOnNewline: true
@@ -24,7 +24,7 @@ function parsePrelude(prelude) {
24
24
  case '#':
25
25
  comment +=
26
26
  (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') +
27
- source.substring(1);
27
+ (source.substring(1) || ' ');
28
28
  atComment = true;
29
29
  afterEmptyLine = false;
30
30
  break;
@@ -2,6 +2,7 @@ import { Pair } from '../nodes/Pair.js';
2
2
  import { YAMLMap } from '../nodes/YAMLMap.js';
3
3
  import { resolveProps } from './resolve-props.js';
4
4
  import { containsNewline } from './util-contains-newline.js';
5
+ import { mapIncludes } from './util-map-includes.js';
5
6
 
6
7
  const startColMsg = 'All mapping items must start at the same column';
7
8
  function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
@@ -11,8 +12,8 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
11
12
  for (const { start, key, sep, value } of bm.items) {
12
13
  // key properties
13
14
  const keyProps = resolveProps(start, {
14
- ctx,
15
15
  indicator: 'explicit-key-ind',
16
+ next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
16
17
  offset,
17
18
  onError,
18
19
  startOnNewline: true
@@ -46,10 +47,12 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
46
47
  const keyNode = key
47
48
  ? composeNode(ctx, key, keyProps, onError)
48
49
  : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError);
50
+ if (mapIncludes(ctx, map.items, keyNode))
51
+ onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
49
52
  // value properties
50
53
  const valueProps = resolveProps(sep || [], {
51
- ctx,
52
54
  indicator: 'map-value-ind',
55
+ next: value,
53
56
  offset: keyNode.range[2],
54
57
  onError,
55
58
  startOnNewline: !key || key.type === 'block-scalar'
@@ -149,7 +149,7 @@ function parseBlockScalarHeader({ offset, props }, strict, onError) {
149
149
  case 'comment':
150
150
  if (strict && !hasSpace) {
151
151
  const message = 'Comments must be separated from other tokens by white space characters';
152
- onError(token, 'COMMENT_SPACE', message);
152
+ onError(token, 'MISSING_CHAR', message);
153
153
  }
154
154
  length += token.source.length;
155
155
  comment = token.source.substring(1);
@@ -6,8 +6,8 @@ function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError) {
6
6
  let offset = bs.offset;
7
7
  for (const { start, value } of bs.items) {
8
8
  const props = resolveProps(start, {
9
- ctx,
10
9
  indicator: 'seq-item-ind',
10
+ next: value,
11
11
  offset,
12
12
  onError,
13
13
  startOnNewline: true
@@ -11,8 +11,8 @@ function resolveEnd(end, offset, reqSpace, onError) {
11
11
  break;
12
12
  case 'comment': {
13
13
  if (reqSpace && !hasSpace)
14
- onError(token, 'COMMENT_SPACE', 'Comments must be separated from other tokens by white space characters');
15
- const cb = source.substring(1);
14
+ onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters');
15
+ const cb = source.substring(1) || ' ';
16
16
  if (!comment)
17
17
  comment = cb;
18
18
  else
@@ -5,6 +5,7 @@ import { YAMLSeq } from '../nodes/YAMLSeq.js';
5
5
  import { resolveEnd } from './resolve-end.js';
6
6
  import { resolveProps } from './resolve-props.js';
7
7
  import { containsNewline } from './util-contains-newline.js';
8
+ import { mapIncludes } from './util-map-includes.js';
8
9
 
9
10
  const blockMsg = 'Block collections are not allowed within flow collections';
10
11
  const isBlock = (token) => token && (token.type === 'block-map' || token.type === 'block-seq');
@@ -19,9 +20,9 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
19
20
  for (let i = 0; i < fc.items.length; ++i) {
20
21
  const { start, key, sep, value } = fc.items[i];
21
22
  const props = resolveProps(start, {
22
- ctx,
23
23
  flow: fcName,
24
24
  indicator: 'explicit-key-ind',
25
+ next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
25
26
  offset,
26
27
  onError,
27
28
  startOnNewline: false
@@ -99,9 +100,9 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
99
100
  onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
100
101
  // value properties
101
102
  const valueProps = resolveProps(sep || [], {
102
- ctx,
103
103
  flow: fcName,
104
104
  indicator: 'map-value-ind',
105
+ next: value,
105
106
  offset: keyNode.range[2],
106
107
  onError,
107
108
  startOnNewline: false
@@ -144,8 +145,12 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
144
145
  keyNode.comment = valueProps.comment;
145
146
  }
146
147
  const pair = new Pair(keyNode, valueNode);
147
- if (isMap)
148
- coll.items.push(pair);
148
+ if (isMap) {
149
+ const map = coll;
150
+ if (mapIncludes(ctx, map.items, keyNode))
151
+ onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
152
+ map.items.push(pair);
153
+ }
149
154
  else {
150
155
  const map = new YAMLMap(ctx.schema);
151
156
  map.flow = true;
@@ -159,7 +164,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
159
164
  const [ce, ...ee] = fc.end;
160
165
  let cePos = offset;
161
166
  if (ce && ce.source === expectedEnd)
162
- cePos += ce.source.length;
167
+ cePos = ce.offset + ce.source.length;
163
168
  else {
164
169
  onError(offset + 1, 'MISSING_CHAR', `Expected ${fcName} to end with ${expectedEnd}`);
165
170
  if (ce && ce.source.length !== 1)
@@ -1,16 +1,24 @@
1
- function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNewline }) {
1
+ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnNewline }) {
2
2
  let spaceBefore = false;
3
3
  let atNewline = startOnNewline;
4
4
  let hasSpace = startOnNewline;
5
5
  let comment = '';
6
6
  let commentSep = '';
7
7
  let hasNewline = false;
8
+ let reqSpace = false;
8
9
  let anchor = null;
9
10
  let tag = null;
10
11
  let comma = null;
11
12
  let found = null;
12
13
  let start = null;
13
14
  for (const token of tokens) {
15
+ if (reqSpace) {
16
+ if (token.type !== 'space' &&
17
+ token.type !== 'newline' &&
18
+ token.type !== 'comma')
19
+ onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
20
+ reqSpace = false;
21
+ }
14
22
  switch (token.type) {
15
23
  case 'space':
16
24
  // At the doc level, tabs at line start may be parsed
@@ -24,23 +32,29 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
24
32
  hasSpace = true;
25
33
  break;
26
34
  case 'comment': {
27
- if (ctx.options.strict && !hasSpace)
28
- onError(token, 'COMMENT_SPACE', 'Comments must be separated from other tokens by white space characters');
29
- const cb = token.source.substring(1);
35
+ if (!hasSpace)
36
+ onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters');
37
+ const cb = token.source.substring(1) || ' ';
30
38
  if (!comment)
31
39
  comment = cb;
32
40
  else
33
41
  comment += commentSep + cb;
34
42
  commentSep = '';
43
+ atNewline = false;
35
44
  break;
36
45
  }
37
46
  case 'newline':
38
- if (atNewline && !comment)
39
- spaceBefore = true;
47
+ if (atNewline) {
48
+ if (comment)
49
+ comment += token.source;
50
+ else
51
+ spaceBefore = true;
52
+ }
53
+ else
54
+ commentSep += token.source;
40
55
  atNewline = true;
41
56
  hasNewline = true;
42
57
  hasSpace = true;
43
- commentSep += token.source;
44
58
  break;
45
59
  case 'anchor':
46
60
  if (anchor)
@@ -50,6 +64,7 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
50
64
  start = token.offset;
51
65
  atNewline = false;
52
66
  hasSpace = false;
67
+ reqSpace = true;
53
68
  break;
54
69
  case 'tag': {
55
70
  if (tag)
@@ -59,6 +74,7 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
59
74
  start = token.offset;
60
75
  atNewline = false;
61
76
  hasSpace = false;
77
+ reqSpace = true;
62
78
  break;
63
79
  }
64
80
  case indicator:
@@ -87,6 +103,13 @@ function resolveProps(tokens, { ctx, flow, indicator, offset, onError, startOnNe
87
103
  }
88
104
  const last = tokens[tokens.length - 1];
89
105
  const end = last ? last.offset + last.source.length : offset;
106
+ if (reqSpace &&
107
+ next &&
108
+ next.type !== 'space' &&
109
+ next.type !== 'newline' &&
110
+ next.type !== 'comma' &&
111
+ (next.type !== 'scalar' || next.source !== ''))
112
+ onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space');
90
113
  return {
91
114
  comma,
92
115
  found,
@@ -0,0 +1,17 @@
1
+ import { isScalar } from '../nodes/Node.js';
2
+
3
+ function mapIncludes(ctx, items, search) {
4
+ const { uniqueKeys } = ctx.options;
5
+ if (uniqueKeys === false)
6
+ return false;
7
+ const isEqual = typeof uniqueKeys === 'function'
8
+ ? uniqueKeys
9
+ : (a, b) => a === b ||
10
+ (isScalar(a) &&
11
+ isScalar(b) &&
12
+ a.value === b.value &&
13
+ !(a.value === '<<' && ctx.schema.merge));
14
+ return items.some(pair => isEqual(pair.key, search));
15
+ }
16
+
17
+ export { mapIncludes };
@@ -10,6 +10,7 @@ const defaultOptions = {
10
10
  logLevel: 'warn',
11
11
  prettyErrors: true,
12
12
  strict: true,
13
+ uniqueKeys: true,
13
14
  version: '1.2'
14
15
  };
15
16
 
@@ -20,38 +20,6 @@ function includesNonEmpty(list) {
20
20
  }
21
21
  return false;
22
22
  }
23
- function atFirstEmptyLineAfterComments(start) {
24
- let hasComment = false;
25
- for (let i = 0; i < start.length; ++i) {
26
- switch (start[i].type) {
27
- case 'space':
28
- break;
29
- case 'comment':
30
- hasComment = true;
31
- break;
32
- case 'newline':
33
- if (!hasComment)
34
- return false;
35
- break;
36
- default:
37
- return false;
38
- }
39
- }
40
- if (hasComment) {
41
- for (let i = start.length - 1; i >= 0; --i) {
42
- switch (start[i].type) {
43
- /* istanbul ignore next */
44
- case 'space':
45
- break;
46
- case 'newline':
47
- return true;
48
- default:
49
- return false;
50
- }
51
- }
52
- }
53
- return false;
54
- }
55
23
  function isFlowToken(token) {
56
24
  switch (token === null || token === void 0 ? void 0 : token.type) {
57
25
  case 'alias':
@@ -502,24 +470,38 @@ class Parser {
502
470
  switch (this.type) {
503
471
  case 'newline':
504
472
  this.onKeyLine = false;
505
- if (!it.sep && atFirstEmptyLineAfterComments(it.start)) {
506
- const prev = map.items[map.items.length - 2];
507
- const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
508
- if (Array.isArray(end)) {
509
- Array.prototype.push.apply(end, it.start);
510
- it.start = [this.sourceToken];
511
- return;
512
- }
473
+ if (it.value) {
474
+ const end = 'end' in it.value ? it.value.end : undefined;
475
+ const last = Array.isArray(end) ? end[end.length - 1] : undefined;
476
+ if ((last === null || last === void 0 ? void 0 : last.type) === 'comment')
477
+ end === null || end === void 0 ? void 0 : end.push(this.sourceToken);
478
+ else
479
+ map.items.push({ start: [this.sourceToken] });
513
480
  }
514
- // fallthrough
481
+ else if (it.sep)
482
+ it.sep.push(this.sourceToken);
483
+ else
484
+ it.start.push(this.sourceToken);
485
+ return;
515
486
  case 'space':
516
487
  case 'comment':
517
488
  if (it.value)
518
489
  map.items.push({ start: [this.sourceToken] });
519
490
  else if (it.sep)
520
491
  it.sep.push(this.sourceToken);
521
- else
492
+ else {
493
+ if (this.atIndentedComment(it.start, map.indent)) {
494
+ const prev = map.items[map.items.length - 2];
495
+ const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
496
+ if (Array.isArray(end)) {
497
+ Array.prototype.push.apply(end, it.start);
498
+ end.push(this.sourceToken);
499
+ map.items.pop();
500
+ return;
501
+ }
502
+ }
522
503
  it.start.push(this.sourceToken);
504
+ }
523
505
  return;
524
506
  }
525
507
  if (this.indent >= map.indent) {
@@ -624,22 +606,34 @@ class Parser {
624
606
  const it = seq.items[seq.items.length - 1];
625
607
  switch (this.type) {
626
608
  case 'newline':
627
- if (!it.value && atFirstEmptyLineAfterComments(it.start)) {
628
- const prev = seq.items[seq.items.length - 2];
629
- const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
630
- if (Array.isArray(end)) {
631
- Array.prototype.push.apply(end, it.start);
632
- it.start = [this.sourceToken];
633
- return;
634
- }
609
+ if (it.value) {
610
+ const end = 'end' in it.value ? it.value.end : undefined;
611
+ const last = Array.isArray(end) ? end[end.length - 1] : undefined;
612
+ if ((last === null || last === void 0 ? void 0 : last.type) === 'comment')
613
+ end === null || end === void 0 ? void 0 : end.push(this.sourceToken);
614
+ else
615
+ seq.items.push({ start: [this.sourceToken] });
635
616
  }
636
- // fallthrough
617
+ else
618
+ it.start.push(this.sourceToken);
619
+ return;
637
620
  case 'space':
638
621
  case 'comment':
639
622
  if (it.value)
640
623
  seq.items.push({ start: [this.sourceToken] });
641
- else
624
+ else {
625
+ if (this.atIndentedComment(it.start, seq.indent)) {
626
+ const prev = seq.items[seq.items.length - 2];
627
+ const end = (_a = prev === null || prev === void 0 ? void 0 : prev.value) === null || _a === void 0 ? void 0 : _a.end;
628
+ if (Array.isArray(end)) {
629
+ Array.prototype.push.apply(end, it.start);
630
+ end.push(this.sourceToken);
631
+ seq.items.pop();
632
+ return;
633
+ }
634
+ }
642
635
  it.start.push(this.sourceToken);
636
+ }
643
637
  return;
644
638
  case 'anchor':
645
639
  case 'tag':
@@ -834,6 +828,13 @@ class Parser {
834
828
  }
835
829
  return null;
836
830
  }
831
+ atIndentedComment(start, indent) {
832
+ if (this.type !== 'comment')
833
+ return false;
834
+ if (this.indent <= indent)
835
+ return false;
836
+ return start.every(st => st.type === 'newline' || st.type === 'space');
837
+ }
837
838
  *documentEnd(docEnd) {
838
839
  if (this.type !== 'doc-mode') {
839
840
  if (docEnd.end)
@@ -1,7 +1,7 @@
1
1
  import { Collection } from '../nodes/Collection.js';
2
- import { addComment } from './addComment.js';
3
- import { stringify } from './stringify.js';
4
2
  import { isNode, isPair } from '../nodes/Node.js';
3
+ import { stringify } from './stringify.js';
4
+ import { addComment, stringifyComment } from './stringifyComment.js';
5
5
 
6
6
  function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowChars, itemIndent, onChompKeep, onComment }) {
7
7
  const { indent, indentStep } = ctx;
@@ -16,10 +16,17 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
16
16
  if (isNode(item)) {
17
17
  if (!chompKeep && item.spaceBefore)
18
18
  nodes.push({ comment: true, str: '' });
19
- if (item.commentBefore) {
19
+ let cb = item.commentBefore;
20
+ if (cb && chompKeep)
21
+ cb = cb.replace(/^\n+/, '');
22
+ if (cb) {
23
+ if (/^\n+$/.test(cb))
24
+ cb = cb.substring(1);
20
25
  // This match will always succeed on a non-empty string
21
- for (const line of item.commentBefore.match(/^.*$/gm))
22
- nodes.push({ comment: true, str: `#${line}` });
26
+ for (const line of cb.match(/^.*$/gm)) {
27
+ const str = line === ' ' ? '#' : line ? `#${line}` : '';
28
+ nodes.push({ comment: true, str });
29
+ }
23
30
  }
24
31
  if (item.comment) {
25
32
  comment = item.comment;
@@ -31,10 +38,17 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
31
38
  if (ik) {
32
39
  if (!chompKeep && ik.spaceBefore)
33
40
  nodes.push({ comment: true, str: '' });
34
- if (ik.commentBefore) {
41
+ let cb = ik.commentBefore;
42
+ if (cb && chompKeep)
43
+ cb = cb.replace(/^\n+/, '');
44
+ if (cb) {
45
+ if (/^\n+$/.test(cb))
46
+ cb = cb.substring(1);
35
47
  // This match will always succeed on a non-empty string
36
- for (const line of ik.commentBefore.match(/^.*$/gm))
37
- nodes.push({ comment: true, str: `#${line}` });
48
+ for (const line of cb.match(/^.*$/gm)) {
49
+ const str = line === ' ' ? '#' : line ? `#${line}` : '';
50
+ nodes.push({ comment: true, str });
51
+ }
38
52
  }
39
53
  if (ik.comment)
40
54
  singleLineOutput = false;
@@ -96,7 +110,7 @@ function stringifyCollection({ comment, flow, items }, ctx, { blockItem, flowCha
96
110
  str += s ? `\n${indent}${s}` : '\n';
97
111
  }
98
112
  if (comment) {
99
- str += '\n' + comment.replace(/^/gm, `${indent}#`);
113
+ str += '\n' + stringifyComment(comment, indent);
100
114
  if (onComment)
101
115
  onComment();
102
116
  }
@@ -0,0 +1,14 @@
1
+ const stringifyComment = (comment, indent) => /^\n+$/.test(comment)
2
+ ? comment.substring(1)
3
+ : comment.replace(/^(?!$)(?: $)?/gm, `${indent}#`);
4
+ function addComment(str, indent, comment) {
5
+ return !comment
6
+ ? str
7
+ : comment.includes('\n')
8
+ ? `${str}\n` + stringifyComment(comment, indent)
9
+ : str.endsWith(' ')
10
+ ? `${str}#${comment}`
11
+ : `${str} #${comment}`;
12
+ }
13
+
14
+ export { addComment, stringifyComment };
@@ -1,6 +1,6 @@
1
1
  import { isNode } from '../nodes/Node.js';
2
- import { addComment } from './addComment.js';
3
2
  import { createStringifyContext, stringify } from './stringify.js';
3
+ import { stringifyComment, addComment } from './stringifyComment.js';
4
4
 
5
5
  function stringifyDocument(doc, options) {
6
6
  const lines = [];
@@ -19,7 +19,7 @@ function stringifyDocument(doc, options) {
19
19
  if (doc.commentBefore) {
20
20
  if (lines.length !== 1)
21
21
  lines.unshift('');
22
- lines.unshift(doc.commentBefore.replace(/^/gm, '#'));
22
+ lines.unshift(stringifyComment(doc.commentBefore, ''));
23
23
  }
24
24
  const ctx = createStringifyContext(doc, options);
25
25
  let chompKeep = false;
@@ -29,7 +29,7 @@ function stringifyDocument(doc, options) {
29
29
  if (doc.contents.spaceBefore && hasDirectives)
30
30
  lines.push('');
31
31
  if (doc.contents.commentBefore)
32
- lines.push(doc.contents.commentBefore.replace(/^/gm, '#'));
32
+ lines.push(stringifyComment(doc.contents.commentBefore, ''));
33
33
  // top-level block scalars need to be indented if followed by a comment
34
34
  ctx.forceBlockIndent = !!doc.comment;
35
35
  contentComment = doc.contents.comment;
@@ -50,10 +50,13 @@ function stringifyDocument(doc, options) {
50
50
  else {
51
51
  lines.push(stringify(doc.contents, ctx));
52
52
  }
53
- if (doc.comment) {
53
+ let dc = doc.comment;
54
+ if (dc && chompKeep)
55
+ dc = dc.replace(/^\n+/, '');
56
+ if (dc) {
54
57
  if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '')
55
58
  lines.push('');
56
- lines.push(doc.comment.replace(/^/gm, '#'));
59
+ lines.push(stringifyComment(dc, ''));
57
60
  }
58
61
  return lines.join('\n') + '\n';
59
62
  }
@@ -1,7 +1,7 @@
1
1
  import { isCollection, isNode, isScalar, isSeq } from '../nodes/Node.js';
2
2
  import { Scalar } from '../nodes/Scalar.js';
3
- import { addComment } from './addComment.js';
4
3
  import { stringify } from './stringify.js';
4
+ import { addComment, stringifyComment } from './stringifyComment.js';
5
5
 
6
6
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
7
7
  const { allNullValues, doc, indent, indentStep, options: { indentSeq, simpleKeys } } = ctx;
@@ -59,10 +59,8 @@ function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
59
59
  if (isNode(value)) {
60
60
  if (value.spaceBefore)
61
61
  vcb = '\n';
62
- if (value.commentBefore) {
63
- const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
64
- vcb += `\n${cs}`;
65
- }
62
+ if (value.commentBefore)
63
+ vcb += `\n${stringifyComment(value.commentBefore, ctx.indent)}`;
66
64
  valueComment = value.comment;
67
65
  }
68
66
  else if (value && typeof value === 'object') {
@@ -14,8 +14,8 @@ function composeDoc(options, directives, { offset, start, value, end }, onError)
14
14
  schema: doc.schema
15
15
  };
16
16
  const props = resolveProps.resolveProps(start, {
17
- ctx,
18
17
  indicator: 'doc-start',
18
+ next: value || (end === null || end === void 0 ? void 0 : end[0]),
19
19
  offset,
20
20
  onError,
21
21
  startOnNewline: true
@@ -26,7 +26,7 @@ function parsePrelude(prelude) {
26
26
  case '#':
27
27
  comment +=
28
28
  (comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') +
29
- source.substring(1);
29
+ (source.substring(1) || ' ');
30
30
  atComment = true;
31
31
  afterEmptyLine = false;
32
32
  break;
@@ -1,5 +1,6 @@
1
+ import type { ParsedNode } from '../nodes/Node.js';
1
2
  import { YAMLMap } from '../nodes/YAMLMap.js';
2
3
  import type { BlockMap } from '../parse/cst.js';
3
4
  import type { ComposeContext, ComposeNode } from './compose-node.js';
4
5
  import type { ComposeErrorHandler } from './composer.js';
5
- export declare function resolveBlockMap({ composeNode, composeEmptyNode }: ComposeNode, ctx: ComposeContext, bm: BlockMap, onError: ComposeErrorHandler): YAMLMap.Parsed<import("../index.js").ParsedNode, import("../index.js").ParsedNode | null>;
6
+ export declare function resolveBlockMap({ composeNode, composeEmptyNode }: ComposeNode, ctx: ComposeContext, bm: BlockMap, onError: ComposeErrorHandler): YAMLMap.Parsed<ParsedNode, ParsedNode | null>;
@@ -4,6 +4,7 @@ var Pair = require('../nodes/Pair.js');
4
4
  var YAMLMap = require('../nodes/YAMLMap.js');
5
5
  var resolveProps = require('./resolve-props.js');
6
6
  var utilContainsNewline = require('./util-contains-newline.js');
7
+ var utilMapIncludes = require('./util-map-includes.js');
7
8
 
8
9
  const startColMsg = 'All mapping items must start at the same column';
9
10
  function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
@@ -13,8 +14,8 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
13
14
  for (const { start, key, sep, value } of bm.items) {
14
15
  // key properties
15
16
  const keyProps = resolveProps.resolveProps(start, {
16
- ctx,
17
17
  indicator: 'explicit-key-ind',
18
+ next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
18
19
  offset,
19
20
  onError,
20
21
  startOnNewline: true
@@ -48,10 +49,12 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
48
49
  const keyNode = key
49
50
  ? composeNode(ctx, key, keyProps, onError)
50
51
  : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError);
52
+ if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
53
+ onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
51
54
  // value properties
52
55
  const valueProps = resolveProps.resolveProps(sep || [], {
53
- ctx,
54
56
  indicator: 'map-value-ind',
57
+ next: value,
55
58
  offset: keyNode.range[2],
56
59
  onError,
57
60
  startOnNewline: !key || key.type === 'block-scalar'
@@ -151,7 +151,7 @@ function parseBlockScalarHeader({ offset, props }, strict, onError) {
151
151
  case 'comment':
152
152
  if (strict && !hasSpace) {
153
153
  const message = 'Comments must be separated from other tokens by white space characters';
154
- onError(token, 'COMMENT_SPACE', message);
154
+ onError(token, 'MISSING_CHAR', message);
155
155
  }
156
156
  length += token.source.length;
157
157
  comment = token.source.substring(1);
@@ -8,8 +8,8 @@ function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError) {
8
8
  let offset = bs.offset;
9
9
  for (const { start, value } of bs.items) {
10
10
  const props = resolveProps.resolveProps(start, {
11
- ctx,
12
11
  indicator: 'seq-item-ind',
12
+ next: value,
13
13
  offset,
14
14
  onError,
15
15
  startOnNewline: true
@@ -13,8 +13,8 @@ function resolveEnd(end, offset, reqSpace, onError) {
13
13
  break;
14
14
  case 'comment': {
15
15
  if (reqSpace && !hasSpace)
16
- onError(token, 'COMMENT_SPACE', 'Comments must be separated from other tokens by white space characters');
17
- const cb = source.substring(1);
16
+ onError(token, 'MISSING_CHAR', 'Comments must be separated from other tokens by white space characters');
17
+ const cb = source.substring(1) || ' ';
18
18
  if (!comment)
19
19
  comment = cb;
20
20
  else