yaml 2.0.0-9 → 2.1.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 (120) hide show
  1. package/README.md +1 -1
  2. package/browser/dist/compose/compose-collection.js +1 -1
  3. package/browser/dist/compose/compose-doc.js +4 -3
  4. package/browser/dist/compose/compose-node.js +13 -4
  5. package/browser/dist/compose/compose-scalar.js +18 -13
  6. package/browser/dist/compose/composer.js +8 -10
  7. package/browser/dist/compose/resolve-block-map.js +15 -8
  8. package/browser/dist/compose/resolve-block-scalar.js +12 -3
  9. package/browser/dist/compose/resolve-block-seq.js +5 -0
  10. package/browser/dist/compose/resolve-flow-collection.js +13 -5
  11. package/browser/dist/compose/resolve-flow-scalar.js +1 -1
  12. package/browser/dist/compose/resolve-props.js +9 -1
  13. package/browser/dist/compose/util-empty-scalar-position.js +1 -1
  14. package/browser/dist/compose/util-flow-indent-check.js +15 -0
  15. package/browser/dist/doc/Document.js +53 -20
  16. package/browser/dist/doc/anchors.js +2 -2
  17. package/browser/dist/doc/createNode.js +8 -7
  18. package/browser/dist/doc/directives.js +7 -4
  19. package/browser/dist/index.js +1 -2
  20. package/browser/dist/nodes/Collection.js +2 -1
  21. package/browser/dist/nodes/Pair.js +2 -2
  22. package/browser/dist/nodes/Scalar.js +1 -1
  23. package/browser/dist/nodes/YAMLMap.js +7 -7
  24. package/browser/dist/nodes/YAMLSeq.js +2 -10
  25. package/browser/dist/nodes/addPairToJSMap.js +1 -1
  26. package/browser/dist/nodes/toJS.js +3 -1
  27. package/browser/dist/parse/cst-scalar.js +1 -2
  28. package/browser/dist/parse/cst-visit.js +2 -2
  29. package/browser/dist/parse/lexer.js +20 -8
  30. package/browser/dist/parse/parser.js +142 -68
  31. package/browser/dist/public-api.js +5 -7
  32. package/browser/dist/schema/Schema.js +14 -4
  33. package/browser/dist/schema/common/null.js +3 -1
  34. package/browser/dist/schema/core/float.js +4 -1
  35. package/browser/dist/schema/tags.js +9 -9
  36. package/browser/dist/schema/yaml-1.1/float.js +4 -1
  37. package/browser/dist/schema/yaml-1.1/omap.js +1 -1
  38. package/browser/dist/schema/yaml-1.1/pairs.js +1 -1
  39. package/browser/dist/schema/yaml-1.1/set.js +4 -0
  40. package/browser/dist/stringify/stringify.js +49 -22
  41. package/browser/dist/stringify/stringifyCollection.js +114 -85
  42. package/browser/dist/stringify/stringifyComment.js +18 -12
  43. package/browser/dist/stringify/stringifyDocument.js +36 -15
  44. package/browser/dist/stringify/stringifyPair.js +34 -20
  45. package/browser/dist/stringify/stringifyString.js +8 -11
  46. package/browser/dist/visit.js +149 -62
  47. package/dist/compose/compose-collection.js +1 -1
  48. package/dist/compose/compose-doc.js +4 -3
  49. package/dist/compose/compose-node.d.ts +1 -0
  50. package/dist/compose/compose-node.js +13 -4
  51. package/dist/compose/compose-scalar.js +17 -12
  52. package/dist/compose/composer.d.ts +1 -1
  53. package/dist/compose/composer.js +10 -12
  54. package/dist/compose/resolve-block-map.js +15 -8
  55. package/dist/compose/resolve-block-scalar.js +12 -3
  56. package/dist/compose/resolve-block-seq.js +5 -0
  57. package/dist/compose/resolve-flow-collection.js +13 -5
  58. package/dist/compose/resolve-flow-scalar.js +1 -1
  59. package/dist/compose/resolve-props.d.ts +2 -1
  60. package/dist/compose/resolve-props.js +9 -1
  61. package/dist/compose/util-empty-scalar-position.js +1 -1
  62. package/dist/compose/util-flow-indent-check.d.ts +3 -0
  63. package/dist/compose/util-flow-indent-check.js +17 -0
  64. package/dist/doc/Document.d.ts +17 -14
  65. package/dist/doc/Document.js +58 -25
  66. package/dist/doc/anchors.d.ts +3 -3
  67. package/dist/doc/anchors.js +2 -2
  68. package/dist/doc/createNode.d.ts +1 -1
  69. package/dist/doc/createNode.js +8 -7
  70. package/dist/doc/directives.d.ts +4 -2
  71. package/dist/doc/directives.js +7 -4
  72. package/dist/index.d.ts +4 -2
  73. package/dist/index.js +1 -2
  74. package/dist/nodes/Collection.d.ts +2 -2
  75. package/dist/nodes/Collection.js +2 -1
  76. package/dist/nodes/Node.d.ts +14 -10
  77. package/dist/nodes/Pair.d.ts +1 -1
  78. package/dist/nodes/Pair.js +2 -2
  79. package/dist/nodes/Scalar.js +1 -1
  80. package/dist/nodes/YAMLMap.d.ts +9 -4
  81. package/dist/nodes/YAMLMap.js +7 -7
  82. package/dist/nodes/YAMLSeq.d.ts +4 -1
  83. package/dist/nodes/YAMLSeq.js +2 -10
  84. package/dist/nodes/addPairToJSMap.js +1 -1
  85. package/dist/nodes/toJS.js +3 -1
  86. package/dist/options.d.ts +44 -13
  87. package/dist/parse/cst-scalar.js +1 -2
  88. package/dist/parse/cst-visit.js +2 -2
  89. package/dist/parse/lexer.js +20 -8
  90. package/dist/parse/parser.js +142 -68
  91. package/dist/public-api.js +5 -7
  92. package/dist/schema/Schema.d.ts +5 -4
  93. package/dist/schema/Schema.js +13 -3
  94. package/dist/schema/common/null.js +3 -1
  95. package/dist/schema/core/float.js +4 -1
  96. package/dist/schema/json-schema.d.ts +69 -0
  97. package/dist/schema/tags.d.ts +1 -2
  98. package/dist/schema/tags.js +9 -9
  99. package/dist/schema/yaml-1.1/float.js +4 -1
  100. package/dist/schema/yaml-1.1/omap.d.ts +7 -3
  101. package/dist/schema/yaml-1.1/omap.js +1 -1
  102. package/dist/schema/yaml-1.1/pairs.js +1 -1
  103. package/dist/schema/yaml-1.1/set.d.ts +6 -2
  104. package/dist/schema/yaml-1.1/set.js +4 -0
  105. package/dist/stringify/stringify.d.ts +5 -3
  106. package/dist/stringify/stringify.js +49 -22
  107. package/dist/stringify/stringifyCollection.d.ts +2 -6
  108. package/dist/stringify/stringifyCollection.js +113 -84
  109. package/dist/stringify/stringifyComment.d.ts +10 -2
  110. package/dist/stringify/stringifyComment.js +19 -12
  111. package/dist/stringify/stringifyDocument.js +35 -14
  112. package/dist/stringify/stringifyPair.js +33 -19
  113. package/dist/stringify/stringifyString.js +8 -11
  114. package/dist/test-events.js +14 -14
  115. package/dist/visit.d.ts +49 -10
  116. package/dist/visit.js +148 -60
  117. package/package.json +11 -10
  118. package/browser/dist/options.js +0 -18
  119. package/dist/node_modules/tslib/tslib.es6.js +0 -76
  120. package/dist/options.js +0 -20
package/README.md CHANGED
@@ -19,7 +19,7 @@ For more information, see the project's documentation site: [**eemeli.org/yaml**
19
19
  To install:
20
20
 
21
21
  ```sh
22
- npm install --save-exact yaml@next
22
+ npm install yaml
23
23
  ```
24
24
 
25
25
  **Note:** These docs are for `yaml@2`. For v1, see the [v1.10.0 tag](https://github.com/eemeli/yaml/tree/v1.10.0) for the source and [eemeli.org/yaml/v1](https://eemeli.org/yaml/v1/) for the documentation.
@@ -51,7 +51,7 @@ function composeCollection(CN, ctx, token, tagToken, onError) {
51
51
  : new Scalar(res);
52
52
  node.range = coll.range;
53
53
  node.tag = tagName;
54
- if (tag === null || tag === void 0 ? void 0 : tag.format)
54
+ if (tag?.format)
55
55
  node.format = tag.format;
56
56
  return node;
57
57
  }
@@ -4,22 +4,23 @@ import { resolveEnd } from './resolve-end.js';
4
4
  import { resolveProps } from './resolve-props.js';
5
5
 
6
6
  function composeDoc(options, directives, { offset, start, value, end }, onError) {
7
- const opts = Object.assign({ directives }, options);
7
+ const opts = Object.assign({ _directives: directives }, options);
8
8
  const doc = new Document(undefined, opts);
9
9
  const ctx = {
10
+ atRoot: true,
10
11
  directives: doc.directives,
11
12
  options: doc.options,
12
13
  schema: doc.schema
13
14
  };
14
15
  const props = resolveProps(start, {
15
16
  indicator: 'doc-start',
16
- next: value || (end === null || end === void 0 ? void 0 : end[0]),
17
+ next: value ?? end?.[0],
17
18
  offset,
18
19
  onError,
19
20
  startOnNewline: true
20
21
  });
21
22
  if (props.found) {
22
- doc.directives.marker = true;
23
+ doc.directives.docStart = true;
23
24
  if (value &&
24
25
  (value.type === 'block-map' || value.type === 'block-seq') &&
25
26
  !props.hasNewline)
@@ -8,6 +8,7 @@ const CN = { composeNode, composeEmptyNode };
8
8
  function composeNode(ctx, token, props, onError) {
9
9
  const { spaceBefore, comment, anchor, tag } = props;
10
10
  let node;
11
+ let isSrcToken = true;
11
12
  switch (token.type) {
12
13
  case 'alias':
13
14
  node = composeAlias(ctx, token, onError);
@@ -29,9 +30,14 @@ function composeNode(ctx, token, props, onError) {
29
30
  if (anchor)
30
31
  node.anchor = anchor.source.substring(1);
31
32
  break;
32
- default:
33
- console.log(token);
34
- throw new Error(`Unsupporten token type: ${token.type}`);
33
+ default: {
34
+ const message = token.type === 'error'
35
+ ? token.message
36
+ : `Unsupported token (type: ${token.type})`;
37
+ onError(token, 'UNEXPECTED_TOKEN', message);
38
+ node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
39
+ isSrcToken = false;
40
+ }
35
41
  }
36
42
  if (anchor && node.anchor === '')
37
43
  onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
@@ -43,7 +49,8 @@ function composeNode(ctx, token, props, onError) {
43
49
  else
44
50
  node.commentBefore = comment;
45
51
  }
46
- if (ctx.options.keepSourceTokens)
52
+ // @ts-expect-error Type checking misses meaning of isSrcToken
53
+ if (ctx.options.keepSourceTokens && isSrcToken)
47
54
  node.srcToken = token;
48
55
  return node;
49
56
  }
@@ -70,6 +77,8 @@ function composeAlias({ options }, { offset, source, end }, onError) {
70
77
  const alias = new Alias(source.substring(1));
71
78
  if (alias.source === '')
72
79
  onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string');
80
+ if (alias.source.endsWith(':'))
81
+ onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true);
73
82
  const valueEnd = offset + source.length;
74
83
  const re = resolveEnd(end, valueEnd, options.strict, onError);
75
84
  alias.range = [offset, valueEnd, re.offset];
@@ -1,4 +1,4 @@
1
- import { isScalar, SCALAR } from '../nodes/Node.js';
1
+ import { SCALAR, isScalar } from '../nodes/Node.js';
2
2
  import { Scalar } from '../nodes/Scalar.js';
3
3
  import { resolveBlockScalar } from './resolve-block-scalar.js';
4
4
  import { resolveFlowScalar } from './resolve-flow-scalar.js';
@@ -12,15 +12,17 @@ function composeScalar(ctx, token, tagToken, onError) {
12
12
  : null;
13
13
  const tag = tagToken && tagName
14
14
  ? findScalarTagByName(ctx.schema, value, tagName, tagToken, onError)
15
- : findScalarTagByTest(ctx.schema, value, token.type === 'scalar');
15
+ : token.type === 'scalar'
16
+ ? findScalarTagByTest(ctx, value, token, onError)
17
+ : ctx.schema[SCALAR];
16
18
  let scalar;
17
19
  try {
18
- const res = tag.resolve(value, msg => onError(tagToken || token, 'TAG_RESOLVE_FAILED', msg), ctx.options);
20
+ const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options);
19
21
  scalar = isScalar(res) ? res : new Scalar(res);
20
22
  }
21
23
  catch (error) {
22
24
  const msg = error instanceof Error ? error.message : String(error);
23
- onError(tagToken || token, 'TAG_RESOLVE_FAILED', msg);
25
+ onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg);
24
26
  scalar = new Scalar(value);
25
27
  }
26
28
  scalar.range = range;
@@ -36,7 +38,6 @@ function composeScalar(ctx, token, tagToken, onError) {
36
38
  return scalar;
37
39
  }
38
40
  function findScalarTagByName(schema, value, tagName, tagToken, onError) {
39
- var _a;
40
41
  if (tagName === '!')
41
42
  return schema[SCALAR]; // non-specific tag
42
43
  const matchWithTest = [];
@@ -49,7 +50,7 @@ function findScalarTagByName(schema, value, tagName, tagToken, onError) {
49
50
  }
50
51
  }
51
52
  for (const tag of matchWithTest)
52
- if ((_a = tag.test) === null || _a === void 0 ? void 0 : _a.test(value))
53
+ if (tag.test?.test(value))
53
54
  return tag;
54
55
  const kt = schema.knownTags[tagName];
55
56
  if (kt && !kt.collection) {
@@ -61,15 +62,19 @@ function findScalarTagByName(schema, value, tagName, tagToken, onError) {
61
62
  onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str');
62
63
  return schema[SCALAR];
63
64
  }
64
- function findScalarTagByTest(schema, value, apply) {
65
- var _a;
66
- if (apply) {
67
- for (const tag of schema.tags) {
68
- if (tag.default && ((_a = tag.test) === null || _a === void 0 ? void 0 : _a.test(value)))
69
- return tag;
65
+ function findScalarTagByTest({ directives, schema }, value, token, onError) {
66
+ const tag = schema.tags.find(tag => tag.default && tag.test?.test(value)) || schema[SCALAR];
67
+ if (schema.compat) {
68
+ const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ??
69
+ schema[SCALAR];
70
+ if (tag.tag !== compat.tag) {
71
+ const ts = directives.tagString(tag.tag);
72
+ const cs = directives.tagString(compat.tag);
73
+ const msg = `Value may be parsed as either ${ts} or ${cs}`;
74
+ onError(token, 'TAG_RESOLVE_FAILED', msg, true);
70
75
  }
71
76
  }
72
- return schema[SCALAR];
77
+ return tag;
73
78
  }
74
79
 
75
80
  export { composeScalar };
@@ -2,7 +2,6 @@ import { Directives } from '../doc/directives.js';
2
2
  import { Document } from '../doc/Document.js';
3
3
  import { YAMLWarning, YAMLParseError } from '../errors.js';
4
4
  import { isCollection, isPair } from '../nodes/Node.js';
5
- import { defaultOptions } from '../options.js';
6
5
  import { composeDoc } from './compose-doc.js';
7
6
  import { resolveEnd } from './resolve-end.js';
8
7
 
@@ -15,7 +14,6 @@ function getErrorPos(src) {
15
14
  return [offset, offset + (typeof source === 'string' ? source.length : 1)];
16
15
  }
17
16
  function parsePrelude(prelude) {
18
- var _a;
19
17
  let comment = '';
20
18
  let atComment = false;
21
19
  let afterEmptyLine = false;
@@ -30,7 +28,7 @@ function parsePrelude(prelude) {
30
28
  afterEmptyLine = false;
31
29
  break;
32
30
  case '%':
33
- if (((_a = prelude[i + 1]) === null || _a === void 0 ? void 0 : _a[0]) !== '#')
31
+ if (prelude[i + 1]?.[0] !== '#')
34
32
  i += 1;
35
33
  atComment = false;
36
34
  break;
@@ -68,9 +66,8 @@ class Composer {
68
66
  else
69
67
  this.errors.push(new YAMLParseError(pos, code, message));
70
68
  };
71
- this.directives = new Directives({
72
- version: options.version || defaultOptions.version
73
- });
69
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
70
+ this.directives = new Directives({ version: options.version || '1.2' });
74
71
  this.options = options;
75
72
  }
76
73
  decorate(doc, afterDoc) {
@@ -81,7 +78,7 @@ class Composer {
81
78
  if (afterDoc) {
82
79
  doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment;
83
80
  }
84
- else if (afterEmptyLine || doc.directives.marker || !dc) {
81
+ else if (afterEmptyLine || doc.directives.docStart || !dc) {
85
82
  doc.commentBefore = comment;
86
83
  }
87
84
  else if (isCollection(dc) && !dc.flow && dc.items.length > 0) {
@@ -146,8 +143,8 @@ class Composer {
146
143
  break;
147
144
  case 'document': {
148
145
  const doc = composeDoc(this.options, this.directives, token, this.onError);
149
- if (this.atDirectives && !doc.directives.marker)
150
- this.onError(token, 'MISSING_CHAR', 'Missing directives-end indicator line');
146
+ if (this.atDirectives && !doc.directives.docStart)
147
+ this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line');
151
148
  this.decorate(doc, false);
152
149
  if (this.doc)
153
150
  yield this.doc;
@@ -179,6 +176,7 @@ class Composer {
179
176
  this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg));
180
177
  break;
181
178
  }
179
+ this.doc.directives.docEnd = true;
182
180
  const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError);
183
181
  this.decorate(this.doc, true);
184
182
  if (end.comment) {
@@ -205,7 +203,7 @@ class Composer {
205
203
  this.doc = null;
206
204
  }
207
205
  else if (forceDoc) {
208
- const opts = Object.assign({ directives: this.directives }, this.options);
206
+ const opts = Object.assign({ _directives: this.directives }, this.options);
209
207
  const doc = new Document(undefined, opts);
210
208
  if (this.atDirectives)
211
209
  this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line');
@@ -2,19 +2,21 @@ 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 { flowIndentCheck } from './util-flow-indent-check.js';
5
6
  import { mapIncludes } from './util-map-includes.js';
6
7
 
7
8
  const startColMsg = 'All mapping items must start at the same column';
8
9
  function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
9
- var _a;
10
10
  const map = new YAMLMap(ctx.schema);
11
+ if (ctx.atRoot)
12
+ ctx.atRoot = false;
11
13
  let offset = bm.offset;
12
14
  for (const collItem of bm.items) {
13
15
  const { start, key, sep, value } = collItem;
14
16
  // key properties
15
17
  const keyProps = resolveProps(start, {
16
18
  indicator: 'explicit-key-ind',
17
- next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
19
+ next: key ?? sep?.[0],
18
20
  offset,
19
21
  onError,
20
22
  startOnNewline: true
@@ -37,21 +39,24 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
37
39
  }
38
40
  continue;
39
41
  }
42
+ if (keyProps.hasNewlineAfterProp || containsNewline(key)) {
43
+ onError(key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line');
44
+ }
40
45
  }
41
- else if (((_a = keyProps.found) === null || _a === void 0 ? void 0 : _a.indent) !== bm.indent)
46
+ else if (keyProps.found?.indent !== bm.indent) {
42
47
  onError(offset, 'BAD_INDENT', startColMsg);
43
- if (implicitKey && containsNewline(key))
44
- onError(key, // checked by containsNewline()
45
- 'MULTILINE_IMPLICIT_KEY', 'Implicit keys need to be on a single line');
48
+ }
46
49
  // key value
47
50
  const keyStart = keyProps.end;
48
51
  const keyNode = key
49
52
  ? composeNode(ctx, key, keyProps, onError)
50
53
  : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError);
54
+ if (ctx.schema.compat)
55
+ flowIndentCheck(bm.indent, key, onError);
51
56
  if (mapIncludes(ctx, map.items, keyNode))
52
57
  onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique');
53
58
  // value properties
54
- const valueProps = resolveProps(sep || [], {
59
+ const valueProps = resolveProps(sep ?? [], {
55
60
  indicator: 'map-value-ind',
56
61
  next: value,
57
62
  offset: keyNode.range[2],
@@ -61,7 +66,7 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
61
66
  offset = valueProps.end;
62
67
  if (valueProps.found) {
63
68
  if (implicitKey) {
64
- if ((value === null || value === void 0 ? void 0 : value.type) === 'block-map' && !valueProps.hasNewline)
69
+ if (value?.type === 'block-map' && !valueProps.hasNewline)
65
70
  onError(offset, 'BLOCK_AS_IMPLICIT_KEY', 'Nested mappings are not allowed in compact mappings');
66
71
  if (ctx.options.strict &&
67
72
  keyProps.start < valueProps.found.offset - 1024)
@@ -71,6 +76,8 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError) {
71
76
  const valueNode = value
72
77
  ? composeNode(ctx, value, valueProps, onError)
73
78
  : composeEmptyNode(ctx, offset, sep, null, valueProps, onError);
79
+ if (ctx.schema.compat)
80
+ flowIndentCheck(bm.indent, value, onError);
74
81
  offset = valueNode.range[2];
75
82
  const pair = new Pair(keyNode, valueNode);
76
83
  if (ctx.options.keepSourceTokens)
@@ -17,8 +17,10 @@ function resolveBlockScalar(scalar, strict, onError) {
17
17
  break;
18
18
  }
19
19
  // shortcut for empty contents
20
- if (!scalar.source || chompStart === 0) {
21
- const value = header.chomp === '+' ? '\n'.repeat(Math.max(0, lines.length - 1)) : '';
20
+ if (chompStart === 0) {
21
+ const value = header.chomp === '+' && lines.length > 0
22
+ ? '\n'.repeat(Math.max(1, lines.length - 1))
23
+ : '';
22
24
  let end = start + header.length;
23
25
  if (scalar.source)
24
26
  end += scalar.source.length;
@@ -46,6 +48,11 @@ function resolveBlockScalar(scalar, strict, onError) {
46
48
  }
47
49
  offset += indent.length + content.length + 1;
48
50
  }
51
+ // include trailing more-indented empty lines in content
52
+ for (let i = lines.length - 1; i >= chompStart; --i) {
53
+ if (lines[i][0].length > trimIndent)
54
+ chompStart = i + 1;
55
+ }
49
56
  let value = '';
50
57
  let sep = '';
51
58
  let prevMoreIndented = false;
@@ -175,7 +182,9 @@ function splitLines(source) {
175
182
  const split = source.split(/\n( *)/);
176
183
  const first = split[0];
177
184
  const m = first.match(/^( *)/);
178
- const line0 = m && m[1] ? [m[1], first.slice(m[1].length)] : ['', first];
185
+ const line0 = m?.[1]
186
+ ? [m[1], first.slice(m[1].length)]
187
+ : ['', first];
179
188
  const lines = [line0];
180
189
  for (let i = 1; i < split.length; i += 2)
181
190
  lines.push([split[i], split[i + 1]]);
@@ -1,8 +1,11 @@
1
1
  import { YAMLSeq } from '../nodes/YAMLSeq.js';
2
2
  import { resolveProps } from './resolve-props.js';
3
+ import { flowIndentCheck } from './util-flow-indent-check.js';
3
4
 
4
5
  function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError) {
5
6
  const seq = new YAMLSeq(ctx.schema);
7
+ if (ctx.atRoot)
8
+ ctx.atRoot = false;
6
9
  let offset = bs.offset;
7
10
  for (const { start, value } of bs.items) {
8
11
  const props = resolveProps(start, {
@@ -30,6 +33,8 @@ function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError) {
30
33
  const node = value
31
34
  ? composeNode(ctx, value, props, onError)
32
35
  : composeEmptyNode(ctx, offset, start, null, props, onError);
36
+ if (ctx.schema.compat)
37
+ flowIndentCheck(bs.indent, value, onError);
33
38
  offset = node.range[2];
34
39
  seq.items.push(node);
35
40
  }
@@ -16,14 +16,17 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
16
16
  ? new YAMLMap(ctx.schema)
17
17
  : new YAMLSeq(ctx.schema);
18
18
  coll.flow = true;
19
- let offset = fc.offset;
19
+ const atRoot = ctx.atRoot;
20
+ if (atRoot)
21
+ ctx.atRoot = false;
22
+ let offset = fc.offset + fc.start.source.length;
20
23
  for (let i = 0; i < fc.items.length; ++i) {
21
24
  const collItem = fc.items[i];
22
25
  const { start, key, sep, value } = collItem;
23
26
  const props = resolveProps(start, {
24
27
  flow: fcName,
25
28
  indicator: 'explicit-key-ind',
26
- next: key || (sep === null || sep === void 0 ? void 0 : sep[0]),
29
+ next: key ?? sep?.[0],
27
30
  offset,
28
31
  onError,
29
32
  startOnNewline: false
@@ -40,6 +43,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
40
43
  else
41
44
  coll.comment = props.comment;
42
45
  }
46
+ offset = props.end;
43
47
  continue;
44
48
  }
45
49
  if (!isMap && ctx.options.strict && containsNewline(key))
@@ -70,7 +74,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
70
74
  if (prevItemComment) {
71
75
  let prev = coll.items[coll.items.length - 1];
72
76
  if (isPair(prev))
73
- prev = prev.value || prev.key;
77
+ prev = prev.value ?? prev.key;
74
78
  if (prev.comment)
75
79
  prev.comment += '\n' + prevItemComment;
76
80
  else
@@ -100,7 +104,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
100
104
  if (isBlock(key))
101
105
  onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg);
102
106
  // value properties
103
- const valueProps = resolveProps(sep || [], {
107
+ const valueProps = resolveProps(sep ?? [], {
104
108
  flow: fcName,
105
109
  indicator: 'map-value-ind',
106
110
  next: value,
@@ -169,7 +173,11 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
169
173
  if (ce && ce.source === expectedEnd)
170
174
  cePos = ce.offset + ce.source.length;
171
175
  else {
172
- onError(offset + 1, 'MISSING_CHAR', `Expected ${fcName} to end with ${expectedEnd}`);
176
+ const name = fcName[0].toUpperCase() + fcName.substring(1);
177
+ const msg = atRoot
178
+ ? `${name} must end with a ${expectedEnd}`
179
+ : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`;
180
+ onError(offset, atRoot ? 'MISSING_CHAR' : 'BAD_INDENT', msg);
173
181
  if (ce && ce.source.length !== 1)
174
182
  ee.unshift(ce);
175
183
  }
@@ -111,7 +111,7 @@ function foldLines(source) {
111
111
  const last = /[ \t]*(.*)/sy;
112
112
  last.lastIndex = pos;
113
113
  match = last.exec(source);
114
- return res + sep + ((match && match[1]) || '');
114
+ return res + sep + (match?.[1] ?? '');
115
115
  }
116
116
  function doubleQuotedValue(source, onError) {
117
117
  let res = '';
@@ -5,6 +5,7 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN
5
5
  let comment = '';
6
6
  let commentSep = '';
7
7
  let hasNewline = false;
8
+ let hasNewlineAfterProp = false;
8
9
  let reqSpace = false;
9
10
  let anchor = null;
10
11
  let tag = null;
@@ -54,11 +55,15 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN
54
55
  commentSep += token.source;
55
56
  atNewline = true;
56
57
  hasNewline = true;
58
+ if (anchor || tag)
59
+ hasNewlineAfterProp = true;
57
60
  hasSpace = true;
58
61
  break;
59
62
  case 'anchor':
60
63
  if (anchor)
61
64
  onError(token, 'MULTIPLE_ANCHORS', 'A node can have at most one anchor');
65
+ if (token.source.endsWith(':'))
66
+ onError(token.offset + token.source.length - 1, 'BAD_ALIAS', 'Anchor ending in : is ambiguous', true);
62
67
  anchor = token;
63
68
  if (start === null)
64
69
  start = token.offset;
@@ -81,6 +86,8 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN
81
86
  // Could here handle preceding comments differently
82
87
  if (anchor || tag)
83
88
  onError(token, 'BAD_PROP_ORDER', `Anchors and tags must be after the ${token.source} indicator`);
89
+ if (found)
90
+ onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`);
84
91
  found = token;
85
92
  atNewline = false;
86
93
  hasSpace = false;
@@ -116,10 +123,11 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN
116
123
  spaceBefore,
117
124
  comment,
118
125
  hasNewline,
126
+ hasNewlineAfterProp,
119
127
  anchor,
120
128
  tag,
121
129
  end,
122
- start: start !== null && start !== void 0 ? start : end
130
+ start: start ?? end
123
131
  };
124
132
  }
125
133
 
@@ -14,7 +14,7 @@ function emptyScalarPosition(offset, before, pos) {
14
14
  // Technically, an empty scalar is immediately after the last non-empty
15
15
  // node, but it's more useful to place it after any whitespace.
16
16
  st = before[++i];
17
- while ((st === null || st === void 0 ? void 0 : st.type) === 'space') {
17
+ while (st?.type === 'space') {
18
18
  offset += st.source.length;
19
19
  st = before[++i];
20
20
  }
@@ -0,0 +1,15 @@
1
+ import { containsNewline } from './util-contains-newline.js';
2
+
3
+ function flowIndentCheck(indent, fc, onError) {
4
+ if (fc?.type === 'flow-collection') {
5
+ const end = fc.end[0];
6
+ if (end.indent === indent &&
7
+ (end.source === ']' || end.source === '}') &&
8
+ containsNewline(fc)) {
9
+ const msg = 'Flow end indicator should be more indented than parent';
10
+ onError(end, 'BAD_INDENT', msg, true);
11
+ }
12
+ }
13
+ }
14
+
15
+ export { flowIndentCheck };
@@ -3,7 +3,6 @@ import { isEmptyPath, collectionFromPath } from '../nodes/Collection.js';
3
3
  import { NODE_TYPE, DOC, isNode, isCollection, isScalar } from '../nodes/Node.js';
4
4
  import { Pair } from '../nodes/Pair.js';
5
5
  import { toJS } from '../nodes/toJS.js';
6
- import { defaultOptions } from '../options.js';
7
6
  import { Schema } from '../schema/Schema.js';
8
7
  import { stringify } from '../stringify/stringify.js';
9
8
  import { stringifyDocument } from '../stringify/stringifyDocument.js';
@@ -31,11 +30,19 @@ class Document {
31
30
  options = replacer;
32
31
  replacer = undefined;
33
32
  }
34
- const opt = Object.assign({}, defaultOptions, options);
33
+ const opt = Object.assign({
34
+ intAsBigInt: false,
35
+ keepSourceTokens: false,
36
+ logLevel: 'warn',
37
+ prettyErrors: true,
38
+ strict: true,
39
+ uniqueKeys: true,
40
+ version: '1.2'
41
+ }, options);
35
42
  this.options = opt;
36
43
  let { version } = opt;
37
- if (options === null || options === void 0 ? void 0 : options.directives) {
38
- this.directives = options.directives.atDocument();
44
+ if (options?._directives) {
45
+ this.directives = options._directives.atDocument();
39
46
  if (this.directives.yaml.explicit)
40
47
  version = this.directives.yaml.version;
41
48
  }
@@ -62,7 +69,8 @@ class Document {
62
69
  copy.errors = this.errors.slice();
63
70
  copy.warnings = this.warnings.slice();
64
71
  copy.options = Object.assign({}, this.options);
65
- copy.directives = this.directives.clone();
72
+ if (this.directives)
73
+ copy.directives = this.directives.clone();
66
74
  copy.schema = this.schema.clone();
67
75
  copy.contents = isNode(this.contents)
68
76
  ? this.contents.clone(copy.schema)
@@ -94,6 +102,7 @@ class Document {
94
102
  if (!node.anchor) {
95
103
  const prev = anchorNames(this);
96
104
  node.anchor =
105
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
97
106
  !name || prev.has(name) ? findNewAnchor(name || 'a', prev) : name;
98
107
  }
99
108
  return new Alias(node.anchor);
@@ -115,11 +124,13 @@ class Document {
115
124
  options = replacer;
116
125
  replacer = undefined;
117
126
  }
118
- const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options || {};
119
- const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this, anchorPrefix || 'a');
127
+ const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {};
128
+ const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors(this,
129
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
130
+ anchorPrefix || 'a');
120
131
  const ctx = {
121
- aliasDuplicateObjects: aliasDuplicateObjects !== null && aliasDuplicateObjects !== void 0 ? aliasDuplicateObjects : true,
122
- keepUndefined: keepUndefined !== null && keepUndefined !== void 0 ? keepUndefined : false,
132
+ aliasDuplicateObjects: aliasDuplicateObjects ?? true,
133
+ keepUndefined: keepUndefined ?? false,
123
134
  onAnchor,
124
135
  onTagObj,
125
136
  replacer: _replacer,
@@ -229,26 +240,48 @@ class Document {
229
240
  }
230
241
  /**
231
242
  * Change the YAML version and schema used by the document.
243
+ * A `null` version disables support for directives, explicit tags, anchors, and aliases.
244
+ * It also requires the `schema` option to be given as a `Schema` instance value.
232
245
  *
233
- * Overrides all previously set schema options
246
+ * Overrides all previously set schema options.
234
247
  */
235
- setSchema(version, options) {
236
- let _options;
237
- switch (String(version)) {
248
+ setSchema(version, options = {}) {
249
+ if (typeof version === 'number')
250
+ version = String(version);
251
+ let opt;
252
+ switch (version) {
238
253
  case '1.1':
239
- this.directives.yaml.version = '1.1';
240
- _options = Object.assign({ merge: true, resolveKnownTags: false, schema: 'yaml-1.1' }, options);
254
+ if (this.directives)
255
+ this.directives.yaml.version = '1.1';
256
+ else
257
+ this.directives = new Directives({ version: '1.1' });
258
+ opt = { merge: true, resolveKnownTags: false, schema: 'yaml-1.1' };
241
259
  break;
242
260
  case '1.2':
243
- this.directives.yaml.version = '1.2';
244
- _options = Object.assign({ merge: false, resolveKnownTags: true, schema: 'core' }, options);
261
+ case 'next':
262
+ if (this.directives)
263
+ this.directives.yaml.version = version;
264
+ else
265
+ this.directives = new Directives({ version });
266
+ opt = { merge: false, resolveKnownTags: true, schema: 'core' };
267
+ break;
268
+ case null:
269
+ if (this.directives)
270
+ delete this.directives;
271
+ opt = null;
245
272
  break;
246
273
  default: {
247
274
  const sv = JSON.stringify(version);
248
- throw new Error(`Expected '1.1' or '1.2' as version, but found: ${sv}`);
275
+ throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`);
249
276
  }
250
277
  }
251
- this.schema = new Schema(_options);
278
+ // Not using `instanceof Schema` to allow for duck typing
279
+ if (options.schema instanceof Object)
280
+ this.schema = options.schema;
281
+ else if (opt)
282
+ this.schema = new Schema(Object.assign(opt, options));
283
+ else
284
+ throw new Error(`With a null YAML version, the { schema: Schema } option is required`);
252
285
  }
253
286
  // json & jsonArg are only used from toJSON()
254
287
  toJS({ json, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) {
@@ -261,7 +294,7 @@ class Document {
261
294
  maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100,
262
295
  stringify
263
296
  };
264
- const res = toJS(this.contents, jsonArg || '', ctx);
297
+ const res = toJS(this.contents, jsonArg ?? '', ctx);
265
298
  if (typeof onAnchor === 'function')
266
299
  for (const { count, res } of ctx.anchors.values())
267
300
  onAnchor(res, count);
@@ -37,7 +37,7 @@ function createNodeAnchors(doc, prefix) {
37
37
  const sourceObjects = new Map();
38
38
  let prevAnchors = null;
39
39
  return {
40
- onAnchor(source) {
40
+ onAnchor: (source) => {
41
41
  aliasObjects.push(source);
42
42
  if (!prevAnchors)
43
43
  prevAnchors = anchorNames(doc);
@@ -50,7 +50,7 @@ function createNodeAnchors(doc, prefix) {
50
50
  * of its child nodes are. This is why anchors are set only after all of
51
51
  * the nodes have been created.
52
52
  */
53
- setAnchors() {
53
+ setAnchors: () => {
54
54
  for (const source of aliasObjects) {
55
55
  const ref = sourceObjects.get(source);
56
56
  if (typeof ref === 'object' &&