yaml 2.8.2 → 2.8.4

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.
@@ -28,19 +28,26 @@ function composeNode(ctx, token, props, onError) {
28
28
  case 'block-map':
29
29
  case 'block-seq':
30
30
  case 'flow-collection':
31
- node = composeCollection(CN, ctx, token, props, onError);
32
- if (anchor)
33
- node.anchor = anchor.source.substring(1);
31
+ try {
32
+ node = composeCollection(CN, ctx, token, props, onError);
33
+ if (anchor)
34
+ node.anchor = anchor.source.substring(1);
35
+ }
36
+ catch (error) {
37
+ // Almost certainly here due to a stack overflow
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ onError(token, 'RESOURCE_EXHAUSTION', message);
40
+ }
34
41
  break;
35
42
  default: {
36
43
  const message = token.type === 'error'
37
44
  ? token.message
38
45
  : `Unsupported token (type: ${token.type})`;
39
46
  onError(token, 'UNEXPECTED_TOKEN', message);
40
- node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
41
47
  isSrcToken = false;
42
48
  }
43
49
  }
50
+ node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
44
51
  if (anchor && node.anchor === '')
45
52
  onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
46
53
  if (atKey &&
@@ -142,7 +142,7 @@ function doubleQuotedValue(source, onError) {
142
142
  next = source[++i + 1];
143
143
  }
144
144
  else if (next === 'x' || next === 'u' || next === 'U') {
145
- const length = { x: 2, u: 4, U: 8 }[next];
145
+ const length = next === 'x' ? 2 : next === 'u' ? 4 : 8;
146
146
  res += parseCharCode(source, i + 1, length, onError);
147
147
  i += length;
148
148
  }
@@ -212,12 +212,14 @@ function parseCharCode(source, offset, length, onError) {
212
212
  const cc = source.substr(offset, length);
213
213
  const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
214
214
  const code = ok ? parseInt(cc, 16) : NaN;
215
- if (isNaN(code)) {
215
+ try {
216
+ return String.fromCodePoint(code);
217
+ }
218
+ catch {
216
219
  const raw = source.substr(offset - 2, length + 2);
217
220
  onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
218
221
  return raw;
219
222
  }
220
- return String.fromCodePoint(code);
221
223
  }
222
224
 
223
225
  export { resolveFlowScalar };
@@ -19,6 +19,8 @@ class Alias extends NodeBase {
19
19
  * instance of the `source` anchor before this node.
20
20
  */
21
21
  resolve(doc, ctx) {
22
+ if (ctx?.maxAliasCount === 0)
23
+ throw new ReferenceError('Alias resolution is disabled');
22
24
  let nodes;
23
25
  if (ctx?.aliasResolveCache) {
24
26
  nodes = ctx.aliasResolveCache;
@@ -1,4 +1,4 @@
1
- import { isScalar, isAlias, isSeq, isMap } from '../../nodes/identity.js';
1
+ import { isScalar, isSeq, isAlias, isMap } from '../../nodes/identity.js';
2
2
  import { Scalar } from '../../nodes/Scalar.js';
3
3
 
4
4
  // If the value associated with a merge key is a single mapping node, each of
@@ -26,18 +26,18 @@ const isMergeKey = (ctx, key) => (merge.identify(key) ||
26
26
  merge.identify(key.value))) &&
27
27
  ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
28
28
  function addMergeToJSMap(ctx, map, value) {
29
- value = ctx && isAlias(value) ? value.resolve(ctx.doc) : value;
30
- if (isSeq(value))
31
- for (const it of value.items)
29
+ const source = resolveAliasValue(ctx, value);
30
+ if (isSeq(source))
31
+ for (const it of source.items)
32
32
  mergeValue(ctx, map, it);
33
- else if (Array.isArray(value))
34
- for (const it of value)
33
+ else if (Array.isArray(source))
34
+ for (const it of source)
35
35
  mergeValue(ctx, map, it);
36
36
  else
37
- mergeValue(ctx, map, value);
37
+ mergeValue(ctx, map, source);
38
38
  }
39
39
  function mergeValue(ctx, map, value) {
40
- const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value;
40
+ const source = resolveAliasValue(ctx, value);
41
41
  if (!isMap(source))
42
42
  throw new Error('Merge sources must be maps or map aliases');
43
43
  const srcMap = source.toJSON(null, ctx, Map);
@@ -60,5 +60,8 @@ function mergeValue(ctx, map, value) {
60
60
  }
61
61
  return map;
62
62
  }
63
+ function resolveAliasValue(ctx, value) {
64
+ return ctx && isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
65
+ }
63
66
 
64
67
  export { addMergeToJSMap, isMergeKey, merge };
@@ -20,6 +20,7 @@ function createStringifyContext(doc, options) {
20
20
  nullStr: 'null',
21
21
  simpleKeys: false,
22
22
  singleQuote: null,
23
+ trailingComma: false,
23
24
  trueStr: 'true',
24
25
  verifyAliasOrder: true
25
26
  }, doc.schema.toStringOptions, options);
@@ -102,12 +102,22 @@ function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
102
102
  if (comment)
103
103
  reqNewline = true;
104
104
  let str = stringify(item, itemCtx, () => (comment = null));
105
- if (i < items.length - 1)
105
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes('\n'));
106
+ if (i < items.length - 1) {
106
107
  str += ',';
108
+ }
109
+ else if (ctx.options.trailingComma) {
110
+ if (ctx.options.lineWidth > 0) {
111
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) +
112
+ (str.length + 2) >
113
+ ctx.options.lineWidth);
114
+ }
115
+ if (reqNewline) {
116
+ str += ',';
117
+ }
118
+ }
107
119
  if (comment)
108
120
  str += lineComment(str, itemIndent, commentString(comment));
109
- if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
110
- reqNewline = true;
111
121
  lines.push(str);
112
122
  linesAtValue = lines.length;
113
123
  }
@@ -8,7 +8,8 @@ function stringifyNumber({ format, minFractionDigits, tag, value }) {
8
8
  if (!format &&
9
9
  minFractionDigits &&
10
10
  (!tag || tag === 'tag:yaml.org,2002:float') &&
11
- /^\d/.test(n)) {
11
+ /^-?\d/.test(n) &&
12
+ !n.includes('e')) {
12
13
  let i = n.indexOf('.');
13
14
  if (i < 0) {
14
15
  i = n.length;
@@ -30,19 +30,26 @@ function composeNode(ctx, token, props, onError) {
30
30
  case 'block-map':
31
31
  case 'block-seq':
32
32
  case 'flow-collection':
33
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
34
- if (anchor)
35
- node.anchor = anchor.source.substring(1);
33
+ try {
34
+ node = composeCollection.composeCollection(CN, ctx, token, props, onError);
35
+ if (anchor)
36
+ node.anchor = anchor.source.substring(1);
37
+ }
38
+ catch (error) {
39
+ // Almost certainly here due to a stack overflow
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ onError(token, 'RESOURCE_EXHAUSTION', message);
42
+ }
36
43
  break;
37
44
  default: {
38
45
  const message = token.type === 'error'
39
46
  ? token.message
40
47
  : `Unsupported token (type: ${token.type})`;
41
48
  onError(token, 'UNEXPECTED_TOKEN', message);
42
- node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
43
49
  isSrcToken = false;
44
50
  }
45
51
  }
52
+ node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
46
53
  if (anchor && node.anchor === '')
47
54
  onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
48
55
  if (atKey &&
@@ -144,7 +144,7 @@ function doubleQuotedValue(source, onError) {
144
144
  next = source[++i + 1];
145
145
  }
146
146
  else if (next === 'x' || next === 'u' || next === 'U') {
147
- const length = { x: 2, u: 4, U: 8 }[next];
147
+ const length = next === 'x' ? 2 : next === 'u' ? 4 : 8;
148
148
  res += parseCharCode(source, i + 1, length, onError);
149
149
  i += length;
150
150
  }
@@ -214,12 +214,14 @@ function parseCharCode(source, offset, length, onError) {
214
214
  const cc = source.substr(offset, length);
215
215
  const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
216
216
  const code = ok ? parseInt(cc, 16) : NaN;
217
- if (isNaN(code)) {
217
+ try {
218
+ return String.fromCodePoint(code);
219
+ }
220
+ catch {
218
221
  const raw = source.substr(offset - 2, length + 2);
219
222
  onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
220
223
  return raw;
221
224
  }
222
- return String.fromCodePoint(code);
223
225
  }
224
226
 
225
227
  exports.resolveFlowScalar = resolveFlowScalar;
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { LineCounter } from './parse/line-counter';
2
- export type ErrorCode = 'ALIAS_PROPS' | 'BAD_ALIAS' | '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_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'NON_STRING_KEY' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN' | 'BAD_COLLECTION_TYPE';
2
+ export type ErrorCode = 'ALIAS_PROPS' | 'BAD_ALIAS' | '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_CHAR' | 'MULTILINE_IMPLICIT_KEY' | 'MULTIPLE_ANCHORS' | 'MULTIPLE_DOCS' | 'MULTIPLE_TAGS' | 'NON_STRING_KEY' | 'RESOURCE_EXHAUSTION' | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN' | 'BAD_COLLECTION_TYPE';
3
3
  export type LinePos = {
4
4
  line: number;
5
5
  col: number;
@@ -21,6 +21,8 @@ class Alias extends Node.NodeBase {
21
21
  * instance of the `source` anchor before this node.
22
22
  */
23
23
  resolve(doc, ctx) {
24
+ if (ctx?.maxAliasCount === 0)
25
+ throw new ReferenceError('Alias resolution is disabled');
24
26
  let nodes;
25
27
  if (ctx?.aliasResolveCache) {
26
28
  nodes = ctx.aliasResolveCache;
@@ -31,7 +31,11 @@ export declare class Scalar<T = unknown> extends NodeBase {
31
31
  * The YAML 1.1 schema also supports 'BIN' and 'TIME'
32
32
  */
33
33
  format?: string;
34
- /** If `value` is a number, use this value when stringifying this node. */
34
+ /**
35
+ * If `value` is a number that is serialized as a decimal string
36
+ * (i.e. not using exponential notation),
37
+ * use this value when stringifying this node.
38
+ */
35
39
  minFractionDigits?: number;
36
40
  /** Set during parsing to the source string value */
37
41
  source?: string;
package/dist/options.d.ts CHANGED
@@ -326,6 +326,12 @@ export type ToStringOptions = {
326
326
  * Default: `null`
327
327
  */
328
328
  singleQuote?: boolean | null;
329
+ /**
330
+ * Add a trailing comma after the last entry in a flow map or flow sequence that's split across multiple lines.
331
+ *
332
+ * Default: `'false'`
333
+ */
334
+ trailingComma?: boolean;
329
335
  /**
330
336
  * String representation for `true`.
331
337
  * With the core schema, use `'true'`, `'True'`, or `'TRUE'`.
@@ -28,18 +28,18 @@ const isMergeKey = (ctx, key) => (merge.identify(key) ||
28
28
  merge.identify(key.value))) &&
29
29
  ctx?.doc.schema.tags.some(tag => tag.tag === merge.tag && tag.default);
30
30
  function addMergeToJSMap(ctx, map, value) {
31
- value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
32
- if (identity.isSeq(value))
33
- for (const it of value.items)
31
+ const source = resolveAliasValue(ctx, value);
32
+ if (identity.isSeq(source))
33
+ for (const it of source.items)
34
34
  mergeValue(ctx, map, it);
35
- else if (Array.isArray(value))
36
- for (const it of value)
35
+ else if (Array.isArray(source))
36
+ for (const it of source)
37
37
  mergeValue(ctx, map, it);
38
38
  else
39
- mergeValue(ctx, map, value);
39
+ mergeValue(ctx, map, source);
40
40
  }
41
41
  function mergeValue(ctx, map, value) {
42
- const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
42
+ const source = resolveAliasValue(ctx, value);
43
43
  if (!identity.isMap(source))
44
44
  throw new Error('Merge sources must be maps or map aliases');
45
45
  const srcMap = source.toJSON(null, ctx, Map);
@@ -62,6 +62,9 @@ function mergeValue(ctx, map, value) {
62
62
  }
63
63
  return map;
64
64
  }
65
+ function resolveAliasValue(ctx, value) {
66
+ return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
67
+ }
65
68
 
66
69
  exports.addMergeToJSMap = addMergeToJSMap;
67
70
  exports.isMergeKey = isMergeKey;
@@ -22,6 +22,7 @@ function createStringifyContext(doc, options) {
22
22
  nullStr: 'null',
23
23
  simpleKeys: false,
24
24
  singleQuote: null,
25
+ trailingComma: false,
25
26
  trueStr: 'true',
26
27
  verifyAliasOrder: true
27
28
  }, doc.schema.toStringOptions, options);
@@ -104,12 +104,22 @@ function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
104
104
  if (comment)
105
105
  reqNewline = true;
106
106
  let str = stringify.stringify(item, itemCtx, () => (comment = null));
107
- if (i < items.length - 1)
107
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes('\n'));
108
+ if (i < items.length - 1) {
108
109
  str += ',';
110
+ }
111
+ else if (ctx.options.trailingComma) {
112
+ if (ctx.options.lineWidth > 0) {
113
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) +
114
+ (str.length + 2) >
115
+ ctx.options.lineWidth);
116
+ }
117
+ if (reqNewline) {
118
+ str += ',';
119
+ }
120
+ }
109
121
  if (comment)
110
122
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
111
- if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
112
- reqNewline = true;
113
123
  lines.push(str);
114
124
  linesAtValue = lines.length;
115
125
  }
@@ -10,7 +10,8 @@ function stringifyNumber({ format, minFractionDigits, tag, value }) {
10
10
  if (!format &&
11
11
  minFractionDigits &&
12
12
  (!tag || tag === 'tag:yaml.org,2002:float') &&
13
- /^\d/.test(n)) {
13
+ /^-?\d/.test(n) &&
14
+ !n.includes('e')) {
14
15
  let i = n.indexOf('.');
15
16
  if (i < 0) {
16
17
  i = n.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "license": "ISC",
5
5
  "author": "Eemeli Aro <eemeli@gmail.com>",
6
6
  "funding": "https://github.com/sponsors/eemeli",