source: trip-planner-front/node_modules/yaml/dist/resolveSeq-d03cb037.js@ e29cc2e

Last change on this file since e29cc2e was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 59.5 KB
RevLine 
[6a3a178]1'use strict';
2
3var PlainValue = require('./PlainValue-ec8e588e.js');
4
5function addCommentBefore(str, indent, comment) {
6 if (!comment) return str;
7 const cc = comment.replace(/[\s\S]^/gm, `$&${indent}#`);
8 return `#${cc}\n${indent}${str}`;
9}
10function addComment(str, indent, comment) {
11 return !comment ? str : comment.indexOf('\n') === -1 ? `${str} #${comment}` : `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`);
12}
13
14class Node {}
15
16function toJSON(value, arg, ctx) {
17 if (Array.isArray(value)) return value.map((v, i) => toJSON(v, String(i), ctx));
18
19 if (value && typeof value.toJSON === 'function') {
20 const anchor = ctx && ctx.anchors && ctx.anchors.get(value);
21 if (anchor) ctx.onCreate = res => {
22 anchor.res = res;
23 delete ctx.onCreate;
24 };
25 const res = value.toJSON(arg, ctx);
26 if (anchor && ctx.onCreate) ctx.onCreate(res);
27 return res;
28 }
29
30 if ((!ctx || !ctx.keep) && typeof value === 'bigint') return Number(value);
31 return value;
32}
33
34class Scalar extends Node {
35 constructor(value) {
36 super();
37 this.value = value;
38 }
39
40 toJSON(arg, ctx) {
41 return ctx && ctx.keep ? this.value : toJSON(this.value, arg, ctx);
42 }
43
44 toString() {
45 return String(this.value);
46 }
47
48}
49
50function collectionFromPath(schema, path, value) {
51 let v = value;
52
53 for (let i = path.length - 1; i >= 0; --i) {
54 const k = path[i];
55
56 if (Number.isInteger(k) && k >= 0) {
57 const a = [];
58 a[k] = v;
59 v = a;
60 } else {
61 const o = {};
62 Object.defineProperty(o, k, {
63 value: v,
64 writable: true,
65 enumerable: true,
66 configurable: true
67 });
68 v = o;
69 }
70 }
71
72 return schema.createNode(v, false);
73} // null, undefined, or an empty non-string iterable (e.g. [])
74
75
76const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
77class Collection extends Node {
78 constructor(schema) {
79 super();
80
81 PlainValue._defineProperty(this, "items", []);
82
83 this.schema = schema;
84 }
85
86 addIn(path, value) {
87 if (isEmptyPath(path)) this.add(value);else {
88 const [key, ...rest] = path;
89 const node = this.get(key, true);
90 if (node instanceof Collection) node.addIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
91 }
92 }
93
94 deleteIn([key, ...rest]) {
95 if (rest.length === 0) return this.delete(key);
96 const node = this.get(key, true);
97 if (node instanceof Collection) return node.deleteIn(rest);else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
98 }
99
100 getIn([key, ...rest], keepScalar) {
101 const node = this.get(key, true);
102 if (rest.length === 0) return !keepScalar && node instanceof Scalar ? node.value : node;else return node instanceof Collection ? node.getIn(rest, keepScalar) : undefined;
103 }
104
105 hasAllNullValues() {
106 return this.items.every(node => {
107 if (!node || node.type !== 'PAIR') return false;
108 const n = node.value;
109 return n == null || n instanceof Scalar && n.value == null && !n.commentBefore && !n.comment && !n.tag;
110 });
111 }
112
113 hasIn([key, ...rest]) {
114 if (rest.length === 0) return this.has(key);
115 const node = this.get(key, true);
116 return node instanceof Collection ? node.hasIn(rest) : false;
117 }
118
119 setIn([key, ...rest], value) {
120 if (rest.length === 0) {
121 this.set(key, value);
122 } else {
123 const node = this.get(key, true);
124 if (node instanceof Collection) node.setIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
125 }
126 } // overridden in implementations
127
128 /* istanbul ignore next */
129
130
131 toJSON() {
132 return null;
133 }
134
135 toString(ctx, {
136 blockItem,
137 flowChars,
138 isMap,
139 itemIndent
140 }, onComment, onChompKeep) {
141 const {
142 indent,
143 indentStep,
144 stringify
145 } = ctx;
146 const inFlow = this.type === PlainValue.Type.FLOW_MAP || this.type === PlainValue.Type.FLOW_SEQ || ctx.inFlow;
147 if (inFlow) itemIndent += indentStep;
148 const allNullValues = isMap && this.hasAllNullValues();
149 ctx = Object.assign({}, ctx, {
150 allNullValues,
151 indent: itemIndent,
152 inFlow,
153 type: null
154 });
155 let chompKeep = false;
156 let hasItemWithNewLine = false;
157 const nodes = this.items.reduce((nodes, item, i) => {
158 let comment;
159
160 if (item) {
161 if (!chompKeep && item.spaceBefore) nodes.push({
162 type: 'comment',
163 str: ''
164 });
165 if (item.commentBefore) item.commentBefore.match(/^.*$/gm).forEach(line => {
166 nodes.push({
167 type: 'comment',
168 str: `#${line}`
169 });
170 });
171 if (item.comment) comment = item.comment;
172 if (inFlow && (!chompKeep && item.spaceBefore || item.commentBefore || item.comment || item.key && (item.key.commentBefore || item.key.comment) || item.value && (item.value.commentBefore || item.value.comment))) hasItemWithNewLine = true;
173 }
174
175 chompKeep = false;
176 let str = stringify(item, ctx, () => comment = null, () => chompKeep = true);
177 if (inFlow && !hasItemWithNewLine && str.includes('\n')) hasItemWithNewLine = true;
178 if (inFlow && i < this.items.length - 1) str += ',';
179 str = addComment(str, itemIndent, comment);
180 if (chompKeep && (comment || inFlow)) chompKeep = false;
181 nodes.push({
182 type: 'item',
183 str
184 });
185 return nodes;
186 }, []);
187 let str;
188
189 if (nodes.length === 0) {
190 str = flowChars.start + flowChars.end;
191 } else if (inFlow) {
192 const {
193 start,
194 end
195 } = flowChars;
196 const strings = nodes.map(n => n.str);
197
198 if (hasItemWithNewLine || strings.reduce((sum, str) => sum + str.length + 2, 2) > Collection.maxFlowStringSingleLineLength) {
199 str = start;
200
201 for (const s of strings) {
202 str += s ? `\n${indentStep}${indent}${s}` : '\n';
203 }
204
205 str += `\n${indent}${end}`;
206 } else {
207 str = `${start} ${strings.join(' ')} ${end}`;
208 }
209 } else {
210 const strings = nodes.map(blockItem);
211 str = strings.shift();
212
213 for (const s of strings) str += s ? `\n${indent}${s}` : '\n';
214 }
215
216 if (this.comment) {
217 str += '\n' + this.comment.replace(/^/gm, `${indent}#`);
218 if (onComment) onComment();
219 } else if (chompKeep && onChompKeep) onChompKeep();
220
221 return str;
222 }
223
224}
225
226PlainValue._defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
227
228function asItemIndex(key) {
229 let idx = key instanceof Scalar ? key.value : key;
230 if (idx && typeof idx === 'string') idx = Number(idx);
231 return Number.isInteger(idx) && idx >= 0 ? idx : null;
232}
233
234class YAMLSeq extends Collection {
235 add(value) {
236 this.items.push(value);
237 }
238
239 delete(key) {
240 const idx = asItemIndex(key);
241 if (typeof idx !== 'number') return false;
242 const del = this.items.splice(idx, 1);
243 return del.length > 0;
244 }
245
246 get(key, keepScalar) {
247 const idx = asItemIndex(key);
248 if (typeof idx !== 'number') return undefined;
249 const it = this.items[idx];
250 return !keepScalar && it instanceof Scalar ? it.value : it;
251 }
252
253 has(key) {
254 const idx = asItemIndex(key);
255 return typeof idx === 'number' && idx < this.items.length;
256 }
257
258 set(key, value) {
259 const idx = asItemIndex(key);
260 if (typeof idx !== 'number') throw new Error(`Expected a valid index, not ${key}.`);
261 this.items[idx] = value;
262 }
263
264 toJSON(_, ctx) {
265 const seq = [];
266 if (ctx && ctx.onCreate) ctx.onCreate(seq);
267 let i = 0;
268
269 for (const item of this.items) seq.push(toJSON(item, String(i++), ctx));
270
271 return seq;
272 }
273
274 toString(ctx, onComment, onChompKeep) {
275 if (!ctx) return JSON.stringify(this);
276 return super.toString(ctx, {
277 blockItem: n => n.type === 'comment' ? n.str : `- ${n.str}`,
278 flowChars: {
279 start: '[',
280 end: ']'
281 },
282 isMap: false,
283 itemIndent: (ctx.indent || '') + ' '
284 }, onComment, onChompKeep);
285 }
286
287}
288
289const stringifyKey = (key, jsKey, ctx) => {
290 if (jsKey === null) return '';
291 if (typeof jsKey !== 'object') return String(jsKey);
292 if (key instanceof Node && ctx && ctx.doc) return key.toString({
293 anchors: Object.create(null),
294 doc: ctx.doc,
295 indent: '',
296 indentStep: ctx.indentStep,
297 inFlow: true,
298 inStringifyKey: true,
299 stringify: ctx.stringify
300 });
301 return JSON.stringify(jsKey);
302};
303
304class Pair extends Node {
305 constructor(key, value = null) {
306 super();
307 this.key = key;
308 this.value = value;
309 this.type = Pair.Type.PAIR;
310 }
311
312 get commentBefore() {
313 return this.key instanceof Node ? this.key.commentBefore : undefined;
314 }
315
316 set commentBefore(cb) {
317 if (this.key == null) this.key = new Scalar(null);
318 if (this.key instanceof Node) this.key.commentBefore = cb;else {
319 const msg = 'Pair.commentBefore is an alias for Pair.key.commentBefore. To set it, the key must be a Node.';
320 throw new Error(msg);
321 }
322 }
323
324 addToJSMap(ctx, map) {
325 const key = toJSON(this.key, '', ctx);
326
327 if (map instanceof Map) {
328 const value = toJSON(this.value, key, ctx);
329 map.set(key, value);
330 } else if (map instanceof Set) {
331 map.add(key);
332 } else {
333 const stringKey = stringifyKey(this.key, key, ctx);
334 const value = toJSON(this.value, stringKey, ctx);
335 if (stringKey in map) Object.defineProperty(map, stringKey, {
336 value,
337 writable: true,
338 enumerable: true,
339 configurable: true
340 });else map[stringKey] = value;
341 }
342
343 return map;
344 }
345
346 toJSON(_, ctx) {
347 const pair = ctx && ctx.mapAsMap ? new Map() : {};
348 return this.addToJSMap(ctx, pair);
349 }
350
351 toString(ctx, onComment, onChompKeep) {
352 if (!ctx || !ctx.doc) return JSON.stringify(this);
353 const {
354 indent: indentSize,
355 indentSeq,
356 simpleKeys
357 } = ctx.doc.options;
358 let {
359 key,
360 value
361 } = this;
362 let keyComment = key instanceof Node && key.comment;
363
364 if (simpleKeys) {
365 if (keyComment) {
366 throw new Error('With simple keys, key nodes cannot have comments');
367 }
368
369 if (key instanceof Collection) {
370 const msg = 'With simple keys, collection cannot be used as a key value';
371 throw new Error(msg);
372 }
373 }
374
375 let explicitKey = !simpleKeys && (!key || keyComment || (key instanceof Node ? key instanceof Collection || key.type === PlainValue.Type.BLOCK_FOLDED || key.type === PlainValue.Type.BLOCK_LITERAL : typeof key === 'object'));
376 const {
377 doc,
378 indent,
379 indentStep,
380 stringify
381 } = ctx;
382 ctx = Object.assign({}, ctx, {
383 implicitKey: !explicitKey,
384 indent: indent + indentStep
385 });
386 let chompKeep = false;
387 let str = stringify(key, ctx, () => keyComment = null, () => chompKeep = true);
388 str = addComment(str, ctx.indent, keyComment);
389
390 if (!explicitKey && str.length > 1024) {
391 if (simpleKeys) throw new Error('With simple keys, single line scalar must not span more than 1024 characters');
392 explicitKey = true;
393 }
394
395 if (ctx.allNullValues && !simpleKeys) {
396 if (this.comment) {
397 str = addComment(str, ctx.indent, this.comment);
398 if (onComment) onComment();
399 } else if (chompKeep && !keyComment && onChompKeep) onChompKeep();
400
401 return ctx.inFlow && !explicitKey ? str : `? ${str}`;
402 }
403
404 str = explicitKey ? `? ${str}\n${indent}:` : `${str}:`;
405
406 if (this.comment) {
407 // expected (but not strictly required) to be a single-line comment
408 str = addComment(str, ctx.indent, this.comment);
409 if (onComment) onComment();
410 }
411
412 let vcb = '';
413 let valueComment = null;
414
415 if (value instanceof Node) {
416 if (value.spaceBefore) vcb = '\n';
417
418 if (value.commentBefore) {
419 const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
420 vcb += `\n${cs}`;
421 }
422
423 valueComment = value.comment;
424 } else if (value && typeof value === 'object') {
425 value = doc.schema.createNode(value, true);
426 }
427
428 ctx.implicitKey = false;
429 if (!explicitKey && !this.comment && value instanceof Scalar) ctx.indentAtStart = str.length + 1;
430 chompKeep = false;
431
432 if (!indentSeq && indentSize >= 2 && !ctx.inFlow && !explicitKey && value instanceof YAMLSeq && value.type !== PlainValue.Type.FLOW_SEQ && !value.tag && !doc.anchors.getName(value)) {
433 // If indentSeq === false, consider '- ' as part of indentation where possible
434 ctx.indent = ctx.indent.substr(2);
435 }
436
437 const valueStr = stringify(value, ctx, () => valueComment = null, () => chompKeep = true);
438 let ws = ' ';
439
440 if (vcb || this.comment) {
441 ws = `${vcb}\n${ctx.indent}`;
442 } else if (!explicitKey && value instanceof Collection) {
443 const flow = valueStr[0] === '[' || valueStr[0] === '{';
444 if (!flow || valueStr.includes('\n')) ws = `\n${ctx.indent}`;
445 } else if (valueStr[0] === '\n') ws = '';
446
447 if (chompKeep && !valueComment && onChompKeep) onChompKeep();
448 return addComment(str + ws + valueStr, ctx.indent, valueComment);
449 }
450
451}
452
453PlainValue._defineProperty(Pair, "Type", {
454 PAIR: 'PAIR',
455 MERGE_PAIR: 'MERGE_PAIR'
456});
457
458const getAliasCount = (node, anchors) => {
459 if (node instanceof Alias) {
460 const anchor = anchors.get(node.source);
461 return anchor.count * anchor.aliasCount;
462 } else if (node instanceof Collection) {
463 let count = 0;
464
465 for (const item of node.items) {
466 const c = getAliasCount(item, anchors);
467 if (c > count) count = c;
468 }
469
470 return count;
471 } else if (node instanceof Pair) {
472 const kc = getAliasCount(node.key, anchors);
473 const vc = getAliasCount(node.value, anchors);
474 return Math.max(kc, vc);
475 }
476
477 return 1;
478};
479
480class Alias extends Node {
481 static stringify({
482 range,
483 source
484 }, {
485 anchors,
486 doc,
487 implicitKey,
488 inStringifyKey
489 }) {
490 let anchor = Object.keys(anchors).find(a => anchors[a] === source);
491 if (!anchor && inStringifyKey) anchor = doc.anchors.getName(source) || doc.anchors.newName();
492 if (anchor) return `*${anchor}${implicitKey ? ' ' : ''}`;
493 const msg = doc.anchors.getName(source) ? 'Alias node must be after source node' : 'Source node not found for alias node';
494 throw new Error(`${msg} [${range}]`);
495 }
496
497 constructor(source) {
498 super();
499 this.source = source;
500 this.type = PlainValue.Type.ALIAS;
501 }
502
503 set tag(t) {
504 throw new Error('Alias nodes cannot have tags');
505 }
506
507 toJSON(arg, ctx) {
508 if (!ctx) return toJSON(this.source, arg, ctx);
509 const {
510 anchors,
511 maxAliasCount
512 } = ctx;
513 const anchor = anchors.get(this.source);
514 /* istanbul ignore if */
515
516 if (!anchor || anchor.res === undefined) {
517 const msg = 'This should not happen: Alias anchor was not resolved?';
518 if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
519 }
520
521 if (maxAliasCount >= 0) {
522 anchor.count += 1;
523 if (anchor.aliasCount === 0) anchor.aliasCount = getAliasCount(this.source, anchors);
524
525 if (anchor.count * anchor.aliasCount > maxAliasCount) {
526 const msg = 'Excessive alias count indicates a resource exhaustion attack';
527 if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
528 }
529 }
530
531 return anchor.res;
532 } // Only called when stringifying an alias mapping key while constructing
533 // Object output.
534
535
536 toString(ctx) {
537 return Alias.stringify(this, ctx);
538 }
539
540}
541
542PlainValue._defineProperty(Alias, "default", true);
543
544function findPair(items, key) {
545 const k = key instanceof Scalar ? key.value : key;
546
547 for (const it of items) {
548 if (it instanceof Pair) {
549 if (it.key === key || it.key === k) return it;
550 if (it.key && it.key.value === k) return it;
551 }
552 }
553
554 return undefined;
555}
556class YAMLMap extends Collection {
557 add(pair, overwrite) {
558 if (!pair) pair = new Pair(pair);else if (!(pair instanceof Pair)) pair = new Pair(pair.key || pair, pair.value);
559 const prev = findPair(this.items, pair.key);
560 const sortEntries = this.schema && this.schema.sortMapEntries;
561
562 if (prev) {
563 if (overwrite) prev.value = pair.value;else throw new Error(`Key ${pair.key} already set`);
564 } else if (sortEntries) {
565 const i = this.items.findIndex(item => sortEntries(pair, item) < 0);
566 if (i === -1) this.items.push(pair);else this.items.splice(i, 0, pair);
567 } else {
568 this.items.push(pair);
569 }
570 }
571
572 delete(key) {
573 const it = findPair(this.items, key);
574 if (!it) return false;
575 const del = this.items.splice(this.items.indexOf(it), 1);
576 return del.length > 0;
577 }
578
579 get(key, keepScalar) {
580 const it = findPair(this.items, key);
581 const node = it && it.value;
582 return !keepScalar && node instanceof Scalar ? node.value : node;
583 }
584
585 has(key) {
586 return !!findPair(this.items, key);
587 }
588
589 set(key, value) {
590 this.add(new Pair(key, value), true);
591 }
592 /**
593 * @param {*} arg ignored
594 * @param {*} ctx Conversion context, originally set in Document#toJSON()
595 * @param {Class} Type If set, forces the returned collection type
596 * @returns {*} Instance of Type, Map, or Object
597 */
598
599
600 toJSON(_, ctx, Type) {
601 const map = Type ? new Type() : ctx && ctx.mapAsMap ? new Map() : {};
602 if (ctx && ctx.onCreate) ctx.onCreate(map);
603
604 for (const item of this.items) item.addToJSMap(ctx, map);
605
606 return map;
607 }
608
609 toString(ctx, onComment, onChompKeep) {
610 if (!ctx) return JSON.stringify(this);
611
612 for (const item of this.items) {
613 if (!(item instanceof Pair)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
614 }
615
616 return super.toString(ctx, {
617 blockItem: n => n.str,
618 flowChars: {
619 start: '{',
620 end: '}'
621 },
622 isMap: true,
623 itemIndent: ctx.indent || ''
624 }, onComment, onChompKeep);
625 }
626
627}
628
629const MERGE_KEY = '<<';
630class Merge extends Pair {
631 constructor(pair) {
632 if (pair instanceof Pair) {
633 let seq = pair.value;
634
635 if (!(seq instanceof YAMLSeq)) {
636 seq = new YAMLSeq();
637 seq.items.push(pair.value);
638 seq.range = pair.value.range;
639 }
640
641 super(pair.key, seq);
642 this.range = pair.range;
643 } else {
644 super(new Scalar(MERGE_KEY), new YAMLSeq());
645 }
646
647 this.type = Pair.Type.MERGE_PAIR;
648 } // If the value associated with a merge key is a single mapping node, each of
649 // its key/value pairs is inserted into the current mapping, unless the key
650 // already exists in it. If the value associated with the merge key is a
651 // sequence, then this sequence is expected to contain mapping nodes and each
652 // of these nodes is merged in turn according to its order in the sequence.
653 // Keys in mapping nodes earlier in the sequence override keys specified in
654 // later mapping nodes. -- http://yaml.org/type/merge.html
655
656
657 addToJSMap(ctx, map) {
658 for (const {
659 source
660 } of this.value.items) {
661 if (!(source instanceof YAMLMap)) throw new Error('Merge sources must be maps');
662 const srcMap = source.toJSON(null, ctx, Map);
663
664 for (const [key, value] of srcMap) {
665 if (map instanceof Map) {
666 if (!map.has(key)) map.set(key, value);
667 } else if (map instanceof Set) {
668 map.add(key);
669 } else if (!Object.prototype.hasOwnProperty.call(map, key)) {
670 Object.defineProperty(map, key, {
671 value,
672 writable: true,
673 enumerable: true,
674 configurable: true
675 });
676 }
677 }
678 }
679
680 return map;
681 }
682
683 toString(ctx, onComment) {
684 const seq = this.value;
685 if (seq.items.length > 1) return super.toString(ctx, onComment);
686 this.value = seq.items[0];
687 const str = super.toString(ctx, onComment);
688 this.value = seq;
689 return str;
690 }
691
692}
693
694const binaryOptions = {
695 defaultType: PlainValue.Type.BLOCK_LITERAL,
696 lineWidth: 76
697};
698const boolOptions = {
699 trueStr: 'true',
700 falseStr: 'false'
701};
702const intOptions = {
703 asBigInt: false
704};
705const nullOptions = {
706 nullStr: 'null'
707};
708const strOptions = {
709 defaultType: PlainValue.Type.PLAIN,
710 doubleQuoted: {
711 jsonEncoding: false,
712 minMultiLineLength: 40
713 },
714 fold: {
715 lineWidth: 80,
716 minContentWidth: 20
717 }
718};
719
720function resolveScalar(str, tags, scalarFallback) {
721 for (const {
722 format,
723 test,
724 resolve
725 } of tags) {
726 if (test) {
727 const match = str.match(test);
728
729 if (match) {
730 let res = resolve.apply(null, match);
731 if (!(res instanceof Scalar)) res = new Scalar(res);
732 if (format) res.format = format;
733 return res;
734 }
735 }
736 }
737
738 if (scalarFallback) str = scalarFallback(str);
739 return new Scalar(str);
740}
741
742const FOLD_FLOW = 'flow';
743const FOLD_BLOCK = 'block';
744const FOLD_QUOTED = 'quoted'; // presumes i+1 is at the start of a line
745// returns index of last newline in more-indented block
746
747const consumeMoreIndentedLines = (text, i) => {
748 let ch = text[i + 1];
749
750 while (ch === ' ' || ch === '\t') {
751 do {
752 ch = text[i += 1];
753 } while (ch && ch !== '\n');
754
755 ch = text[i + 1];
756 }
757
758 return i;
759};
760/**
761 * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
762 * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
763 * terminated with `\n` and started with `indent`.
764 *
765 * @param {string} text
766 * @param {string} indent
767 * @param {string} [mode='flow'] `'block'` prevents more-indented lines
768 * from being folded; `'quoted'` allows for `\` escapes, including escaped
769 * newlines
770 * @param {Object} options
771 * @param {number} [options.indentAtStart] Accounts for leading contents on
772 * the first line, defaulting to `indent.length`
773 * @param {number} [options.lineWidth=80]
774 * @param {number} [options.minContentWidth=20] Allow highly indented lines to
775 * stretch the line width or indent content from the start
776 * @param {function} options.onFold Called once if the text is folded
777 * @param {function} options.onFold Called once if any line of text exceeds
778 * lineWidth characters
779 */
780
781
782function foldFlowLines(text, indent, mode, {
783 indentAtStart,
784 lineWidth = 80,
785 minContentWidth = 20,
786 onFold,
787 onOverflow
788}) {
789 if (!lineWidth || lineWidth < 0) return text;
790 const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
791 if (text.length <= endStep) return text;
792 const folds = [];
793 const escapedFolds = {};
794 let end = lineWidth - indent.length;
795
796 if (typeof indentAtStart === 'number') {
797 if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) folds.push(0);else end = lineWidth - indentAtStart;
798 }
799
800 let split = undefined;
801 let prev = undefined;
802 let overflow = false;
803 let i = -1;
804 let escStart = -1;
805 let escEnd = -1;
806
807 if (mode === FOLD_BLOCK) {
808 i = consumeMoreIndentedLines(text, i);
809 if (i !== -1) end = i + endStep;
810 }
811
812 for (let ch; ch = text[i += 1];) {
813 if (mode === FOLD_QUOTED && ch === '\\') {
814 escStart = i;
815
816 switch (text[i + 1]) {
817 case 'x':
818 i += 3;
819 break;
820
821 case 'u':
822 i += 5;
823 break;
824
825 case 'U':
826 i += 9;
827 break;
828
829 default:
830 i += 1;
831 }
832
833 escEnd = i;
834 }
835
836 if (ch === '\n') {
837 if (mode === FOLD_BLOCK) i = consumeMoreIndentedLines(text, i);
838 end = i + endStep;
839 split = undefined;
840 } else {
841 if (ch === ' ' && prev && prev !== ' ' && prev !== '\n' && prev !== '\t') {
842 // space surrounded by non-space can be replaced with newline + indent
843 const next = text[i + 1];
844 if (next && next !== ' ' && next !== '\n' && next !== '\t') split = i;
845 }
846
847 if (i >= end) {
848 if (split) {
849 folds.push(split);
850 end = split + endStep;
851 split = undefined;
852 } else if (mode === FOLD_QUOTED) {
853 // white-space collected at end may stretch past lineWidth
854 while (prev === ' ' || prev === '\t') {
855 prev = ch;
856 ch = text[i += 1];
857 overflow = true;
858 } // Account for newline escape, but don't break preceding escape
859
860
861 const j = i > escEnd + 1 ? i - 2 : escStart - 1; // Bail out if lineWidth & minContentWidth are shorter than an escape string
862
863 if (escapedFolds[j]) return text;
864 folds.push(j);
865 escapedFolds[j] = true;
866 end = j + endStep;
867 split = undefined;
868 } else {
869 overflow = true;
870 }
871 }
872 }
873
874 prev = ch;
875 }
876
877 if (overflow && onOverflow) onOverflow();
878 if (folds.length === 0) return text;
879 if (onFold) onFold();
880 let res = text.slice(0, folds[0]);
881
882 for (let i = 0; i < folds.length; ++i) {
883 const fold = folds[i];
884 const end = folds[i + 1] || text.length;
885 if (fold === 0) res = `\n${indent}${text.slice(0, end)}`;else {
886 if (mode === FOLD_QUOTED && escapedFolds[fold]) res += `${text[fold]}\\`;
887 res += `\n${indent}${text.slice(fold + 1, end)}`;
888 }
889 }
890
891 return res;
892}
893
894const getFoldOptions = ({
895 indentAtStart
896}) => indentAtStart ? Object.assign({
897 indentAtStart
898}, strOptions.fold) : strOptions.fold; // Also checks for lines starting with %, as parsing the output as YAML 1.1 will
899// presume that's starting a new document.
900
901
902const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
903
904function lineLengthOverLimit(str, lineWidth, indentLength) {
905 if (!lineWidth || lineWidth < 0) return false;
906 const limit = lineWidth - indentLength;
907 const strLen = str.length;
908 if (strLen <= limit) return false;
909
910 for (let i = 0, start = 0; i < strLen; ++i) {
911 if (str[i] === '\n') {
912 if (i - start > limit) return true;
913 start = i + 1;
914 if (strLen - start <= limit) return false;
915 }
916 }
917
918 return true;
919}
920
921function doubleQuotedString(value, ctx) {
922 const {
923 implicitKey
924 } = ctx;
925 const {
926 jsonEncoding,
927 minMultiLineLength
928 } = strOptions.doubleQuoted;
929 const json = JSON.stringify(value);
930 if (jsonEncoding) return json;
931 const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
932 let str = '';
933 let start = 0;
934
935 for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
936 if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
937 // space before newline needs to be escaped to not be folded
938 str += json.slice(start, i) + '\\ ';
939 i += 1;
940 start = i;
941 ch = '\\';
942 }
943
944 if (ch === '\\') switch (json[i + 1]) {
945 case 'u':
946 {
947 str += json.slice(start, i);
948 const code = json.substr(i + 2, 4);
949
950 switch (code) {
951 case '0000':
952 str += '\\0';
953 break;
954
955 case '0007':
956 str += '\\a';
957 break;
958
959 case '000b':
960 str += '\\v';
961 break;
962
963 case '001b':
964 str += '\\e';
965 break;
966
967 case '0085':
968 str += '\\N';
969 break;
970
971 case '00a0':
972 str += '\\_';
973 break;
974
975 case '2028':
976 str += '\\L';
977 break;
978
979 case '2029':
980 str += '\\P';
981 break;
982
983 default:
984 if (code.substr(0, 2) === '00') str += '\\x' + code.substr(2);else str += json.substr(i, 6);
985 }
986
987 i += 5;
988 start = i + 1;
989 }
990 break;
991
992 case 'n':
993 if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
994 i += 1;
995 } else {
996 // folding will eat first newline
997 str += json.slice(start, i) + '\n\n';
998
999 while (json[i + 2] === '\\' && json[i + 3] === 'n' && json[i + 4] !== '"') {
1000 str += '\n';
1001 i += 2;
1002 }
1003
1004 str += indent; // space after newline needs to be escaped to not be folded
1005
1006 if (json[i + 2] === ' ') str += '\\';
1007 i += 1;
1008 start = i + 1;
1009 }
1010
1011 break;
1012
1013 default:
1014 i += 1;
1015 }
1016 }
1017
1018 str = start ? str + json.slice(start) : json;
1019 return implicitKey ? str : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx));
1020}
1021
1022function singleQuotedString(value, ctx) {
1023 if (ctx.implicitKey) {
1024 if (/\n/.test(value)) return doubleQuotedString(value, ctx);
1025 } else {
1026 // single quoted string can't have leading or trailing whitespace around newline
1027 if (/[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx);
1028 }
1029
1030 const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
1031 const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
1032 return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx));
1033}
1034
1035function blockString({
1036 comment,
1037 type,
1038 value
1039}, ctx, onComment, onChompKeep) {
1040 // 1. Block can't end in whitespace unless the last line is non-empty.
1041 // 2. Strings consisting of only whitespace are best rendered explicitly.
1042 if (/\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
1043 return doubleQuotedString(value, ctx);
1044 }
1045
1046 const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? ' ' : '');
1047 const indentSize = indent ? '2' : '1'; // root is at -1
1048
1049 const literal = type === PlainValue.Type.BLOCK_FOLDED ? false : type === PlainValue.Type.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, strOptions.fold.lineWidth, indent.length);
1050 let header = literal ? '|' : '>';
1051 if (!value) return header + '\n';
1052 let wsStart = '';
1053 let wsEnd = '';
1054 value = value.replace(/[\n\t ]*$/, ws => {
1055 const n = ws.indexOf('\n');
1056
1057 if (n === -1) {
1058 header += '-'; // strip
1059 } else if (value === ws || n !== ws.length - 1) {
1060 header += '+'; // keep
1061
1062 if (onChompKeep) onChompKeep();
1063 }
1064
1065 wsEnd = ws.replace(/\n$/, '');
1066 return '';
1067 }).replace(/^[\n ]*/, ws => {
1068 if (ws.indexOf(' ') !== -1) header += indentSize;
1069 const m = ws.match(/ +$/);
1070
1071 if (m) {
1072 wsStart = ws.slice(0, -m[0].length);
1073 return m[0];
1074 } else {
1075 wsStart = ws;
1076 return '';
1077 }
1078 });
1079 if (wsEnd) wsEnd = wsEnd.replace(/\n+(?!\n|$)/g, `$&${indent}`);
1080 if (wsStart) wsStart = wsStart.replace(/\n+/g, `$&${indent}`);
1081
1082 if (comment) {
1083 header += ' #' + comment.replace(/ ?[\r\n]+/g, ' ');
1084 if (onComment) onComment();
1085 }
1086
1087 if (!value) return `${header}${indentSize}\n${indent}${wsEnd}`;
1088
1089 if (literal) {
1090 value = value.replace(/\n+/g, `$&${indent}`);
1091 return `${header}\n${indent}${wsStart}${value}${wsEnd}`;
1092 }
1093
1094 value = value.replace(/\n+/g, '\n$&').replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded
1095 // ^ ind.line ^ empty ^ capture next empty lines only at end of indent
1096 .replace(/\n+/g, `$&${indent}`);
1097 const body = foldFlowLines(`${wsStart}${value}${wsEnd}`, indent, FOLD_BLOCK, strOptions.fold);
1098 return `${header}\n${indent}${body}`;
1099}
1100
1101function plainString(item, ctx, onComment, onChompKeep) {
1102 const {
1103 comment,
1104 type,
1105 value
1106 } = item;
1107 const {
1108 actualString,
1109 implicitKey,
1110 indent,
1111 inFlow
1112 } = ctx;
1113
1114 if (implicitKey && /[\n[\]{},]/.test(value) || inFlow && /[[\]{},]/.test(value)) {
1115 return doubleQuotedString(value, ctx);
1116 }
1117
1118 if (!value || /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
1119 // not allowed:
1120 // - empty string, '-' or '?'
1121 // - start with an indicator character (except [?:-]) or /[?-] /
1122 // - '\n ', ': ' or ' \n' anywhere
1123 // - '#' not preceded by a non-space char
1124 // - end with ' ' or ':'
1125 return implicitKey || inFlow || value.indexOf('\n') === -1 ? value.indexOf('"') !== -1 && value.indexOf("'") === -1 ? singleQuotedString(value, ctx) : doubleQuotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep);
1126 }
1127
1128 if (!implicitKey && !inFlow && type !== PlainValue.Type.PLAIN && value.indexOf('\n') !== -1) {
1129 // Where allowed & type not set explicitly, prefer block style for multiline strings
1130 return blockString(item, ctx, onComment, onChompKeep);
1131 }
1132
1133 if (indent === '' && containsDocumentMarker(value)) {
1134 ctx.forceBlockIndent = true;
1135 return blockString(item, ctx, onComment, onChompKeep);
1136 }
1137
1138 const str = value.replace(/\n+/g, `$&\n${indent}`); // Verify that output will be parsed as a string, as e.g. plain numbers and
1139 // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'),
1140 // and others in v1.1.
1141
1142 if (actualString) {
1143 const {
1144 tags
1145 } = ctx.doc.schema;
1146 const resolved = resolveScalar(str, tags, tags.scalarFallback).value;
1147 if (typeof resolved !== 'string') return doubleQuotedString(value, ctx);
1148 }
1149
1150 const body = implicitKey ? str : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx));
1151
1152 if (comment && !inFlow && (body.indexOf('\n') !== -1 || comment.indexOf('\n') !== -1)) {
1153 if (onComment) onComment();
1154 return addCommentBefore(body, indent, comment);
1155 }
1156
1157 return body;
1158}
1159
1160function stringifyString(item, ctx, onComment, onChompKeep) {
1161 const {
1162 defaultType
1163 } = strOptions;
1164 const {
1165 implicitKey,
1166 inFlow
1167 } = ctx;
1168 let {
1169 type,
1170 value
1171 } = item;
1172
1173 if (typeof value !== 'string') {
1174 value = String(value);
1175 item = Object.assign({}, item, {
1176 value
1177 });
1178 }
1179
1180 const _stringify = _type => {
1181 switch (_type) {
1182 case PlainValue.Type.BLOCK_FOLDED:
1183 case PlainValue.Type.BLOCK_LITERAL:
1184 return blockString(item, ctx, onComment, onChompKeep);
1185
1186 case PlainValue.Type.QUOTE_DOUBLE:
1187 return doubleQuotedString(value, ctx);
1188
1189 case PlainValue.Type.QUOTE_SINGLE:
1190 return singleQuotedString(value, ctx);
1191
1192 case PlainValue.Type.PLAIN:
1193 return plainString(item, ctx, onComment, onChompKeep);
1194
1195 default:
1196 return null;
1197 }
1198 };
1199
1200 if (type !== PlainValue.Type.QUOTE_DOUBLE && /[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(value)) {
1201 // force double quotes on control characters
1202 type = PlainValue.Type.QUOTE_DOUBLE;
1203 } else if ((implicitKey || inFlow) && (type === PlainValue.Type.BLOCK_FOLDED || type === PlainValue.Type.BLOCK_LITERAL)) {
1204 // should not happen; blocks are not valid inside flow containers
1205 type = PlainValue.Type.QUOTE_DOUBLE;
1206 }
1207
1208 let res = _stringify(type);
1209
1210 if (res === null) {
1211 res = _stringify(defaultType);
1212 if (res === null) throw new Error(`Unsupported default string type ${defaultType}`);
1213 }
1214
1215 return res;
1216}
1217
1218function stringifyNumber({
1219 format,
1220 minFractionDigits,
1221 tag,
1222 value
1223}) {
1224 if (typeof value === 'bigint') return String(value);
1225 if (!isFinite(value)) return isNaN(value) ? '.nan' : value < 0 ? '-.inf' : '.inf';
1226 let n = JSON.stringify(value);
1227
1228 if (!format && minFractionDigits && (!tag || tag === 'tag:yaml.org,2002:float') && /^\d/.test(n)) {
1229 let i = n.indexOf('.');
1230
1231 if (i < 0) {
1232 i = n.length;
1233 n += '.';
1234 }
1235
1236 let d = minFractionDigits - (n.length - i - 1);
1237
1238 while (d-- > 0) n += '0';
1239 }
1240
1241 return n;
1242}
1243
1244function checkFlowCollectionEnd(errors, cst) {
1245 let char, name;
1246
1247 switch (cst.type) {
1248 case PlainValue.Type.FLOW_MAP:
1249 char = '}';
1250 name = 'flow map';
1251 break;
1252
1253 case PlainValue.Type.FLOW_SEQ:
1254 char = ']';
1255 name = 'flow sequence';
1256 break;
1257
1258 default:
1259 errors.push(new PlainValue.YAMLSemanticError(cst, 'Not a flow collection!?'));
1260 return;
1261 }
1262
1263 let lastItem;
1264
1265 for (let i = cst.items.length - 1; i >= 0; --i) {
1266 const item = cst.items[i];
1267
1268 if (!item || item.type !== PlainValue.Type.COMMENT) {
1269 lastItem = item;
1270 break;
1271 }
1272 }
1273
1274 if (lastItem && lastItem.char !== char) {
1275 const msg = `Expected ${name} to end with ${char}`;
1276 let err;
1277
1278 if (typeof lastItem.offset === 'number') {
1279 err = new PlainValue.YAMLSemanticError(cst, msg);
1280 err.offset = lastItem.offset + 1;
1281 } else {
1282 err = new PlainValue.YAMLSemanticError(lastItem, msg);
1283 if (lastItem.range && lastItem.range.end) err.offset = lastItem.range.end - lastItem.range.start;
1284 }
1285
1286 errors.push(err);
1287 }
1288}
1289function checkFlowCommentSpace(errors, comment) {
1290 const prev = comment.context.src[comment.range.start - 1];
1291
1292 if (prev !== '\n' && prev !== '\t' && prev !== ' ') {
1293 const msg = 'Comments must be separated from other tokens by white space characters';
1294 errors.push(new PlainValue.YAMLSemanticError(comment, msg));
1295 }
1296}
1297function getLongKeyError(source, key) {
1298 const sk = String(key);
1299 const k = sk.substr(0, 8) + '...' + sk.substr(-8);
1300 return new PlainValue.YAMLSemanticError(source, `The "${k}" key is too long`);
1301}
1302function resolveComments(collection, comments) {
1303 for (const {
1304 afterKey,
1305 before,
1306 comment
1307 } of comments) {
1308 let item = collection.items[before];
1309
1310 if (!item) {
1311 if (comment !== undefined) {
1312 if (collection.comment) collection.comment += '\n' + comment;else collection.comment = comment;
1313 }
1314 } else {
1315 if (afterKey && item.value) item = item.value;
1316
1317 if (comment === undefined) {
1318 if (afterKey || !item.commentBefore) item.spaceBefore = true;
1319 } else {
1320 if (item.commentBefore) item.commentBefore += '\n' + comment;else item.commentBefore = comment;
1321 }
1322 }
1323 }
1324}
1325
1326// on error, will return { str: string, errors: Error[] }
1327function resolveString(doc, node) {
1328 const res = node.strValue;
1329 if (!res) return '';
1330 if (typeof res === 'string') return res;
1331 res.errors.forEach(error => {
1332 if (!error.source) error.source = node;
1333 doc.errors.push(error);
1334 });
1335 return res.str;
1336}
1337
1338function resolveTagHandle(doc, node) {
1339 const {
1340 handle,
1341 suffix
1342 } = node.tag;
1343 let prefix = doc.tagPrefixes.find(p => p.handle === handle);
1344
1345 if (!prefix) {
1346 const dtp = doc.getDefaults().tagPrefixes;
1347 if (dtp) prefix = dtp.find(p => p.handle === handle);
1348 if (!prefix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag handle is non-default and was not declared.`);
1349 }
1350
1351 if (!suffix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag has no suffix.`);
1352
1353 if (handle === '!' && (doc.version || doc.options.version) === '1.0') {
1354 if (suffix[0] === '^') {
1355 doc.warnings.push(new PlainValue.YAMLWarning(node, 'YAML 1.0 ^ tag expansion is not supported'));
1356 return suffix;
1357 }
1358
1359 if (/[:/]/.test(suffix)) {
1360 // word/foo -> tag:word.yaml.org,2002:foo
1361 const vocab = suffix.match(/^([a-z0-9-]+)\/(.*)/i);
1362 return vocab ? `tag:${vocab[1]}.yaml.org,2002:${vocab[2]}` : `tag:${suffix}`;
1363 }
1364 }
1365
1366 return prefix.prefix + decodeURIComponent(suffix);
1367}
1368
1369function resolveTagName(doc, node) {
1370 const {
1371 tag,
1372 type
1373 } = node;
1374 let nonSpecific = false;
1375
1376 if (tag) {
1377 const {
1378 handle,
1379 suffix,
1380 verbatim
1381 } = tag;
1382
1383 if (verbatim) {
1384 if (verbatim !== '!' && verbatim !== '!!') return verbatim;
1385 const msg = `Verbatim tags aren't resolved, so ${verbatim} is invalid.`;
1386 doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
1387 } else if (handle === '!' && !suffix) {
1388 nonSpecific = true;
1389 } else {
1390 try {
1391 return resolveTagHandle(doc, node);
1392 } catch (error) {
1393 doc.errors.push(error);
1394 }
1395 }
1396 }
1397
1398 switch (type) {
1399 case PlainValue.Type.BLOCK_FOLDED:
1400 case PlainValue.Type.BLOCK_LITERAL:
1401 case PlainValue.Type.QUOTE_DOUBLE:
1402 case PlainValue.Type.QUOTE_SINGLE:
1403 return PlainValue.defaultTags.STR;
1404
1405 case PlainValue.Type.FLOW_MAP:
1406 case PlainValue.Type.MAP:
1407 return PlainValue.defaultTags.MAP;
1408
1409 case PlainValue.Type.FLOW_SEQ:
1410 case PlainValue.Type.SEQ:
1411 return PlainValue.defaultTags.SEQ;
1412
1413 case PlainValue.Type.PLAIN:
1414 return nonSpecific ? PlainValue.defaultTags.STR : null;
1415
1416 default:
1417 return null;
1418 }
1419}
1420
1421function resolveByTagName(doc, node, tagName) {
1422 const {
1423 tags
1424 } = doc.schema;
1425 const matchWithTest = [];
1426
1427 for (const tag of tags) {
1428 if (tag.tag === tagName) {
1429 if (tag.test) matchWithTest.push(tag);else {
1430 const res = tag.resolve(doc, node);
1431 return res instanceof Collection ? res : new Scalar(res);
1432 }
1433 }
1434 }
1435
1436 const str = resolveString(doc, node);
1437 if (typeof str === 'string' && matchWithTest.length > 0) return resolveScalar(str, matchWithTest, tags.scalarFallback);
1438 return null;
1439}
1440
1441function getFallbackTagName({
1442 type
1443}) {
1444 switch (type) {
1445 case PlainValue.Type.FLOW_MAP:
1446 case PlainValue.Type.MAP:
1447 return PlainValue.defaultTags.MAP;
1448
1449 case PlainValue.Type.FLOW_SEQ:
1450 case PlainValue.Type.SEQ:
1451 return PlainValue.defaultTags.SEQ;
1452
1453 default:
1454 return PlainValue.defaultTags.STR;
1455 }
1456}
1457
1458function resolveTag(doc, node, tagName) {
1459 try {
1460 const res = resolveByTagName(doc, node, tagName);
1461
1462 if (res) {
1463 if (tagName && node.tag) res.tag = tagName;
1464 return res;
1465 }
1466 } catch (error) {
1467 /* istanbul ignore if */
1468 if (!error.source) error.source = node;
1469 doc.errors.push(error);
1470 return null;
1471 }
1472
1473 try {
1474 const fallback = getFallbackTagName(node);
1475 if (!fallback) throw new Error(`The tag ${tagName} is unavailable`);
1476 const msg = `The tag ${tagName} is unavailable, falling back to ${fallback}`;
1477 doc.warnings.push(new PlainValue.YAMLWarning(node, msg));
1478 const res = resolveByTagName(doc, node, fallback);
1479 res.tag = tagName;
1480 return res;
1481 } catch (error) {
1482 const refError = new PlainValue.YAMLReferenceError(node, error.message);
1483 refError.stack = error.stack;
1484 doc.errors.push(refError);
1485 return null;
1486 }
1487}
1488
1489const isCollectionItem = node => {
1490 if (!node) return false;
1491 const {
1492 type
1493 } = node;
1494 return type === PlainValue.Type.MAP_KEY || type === PlainValue.Type.MAP_VALUE || type === PlainValue.Type.SEQ_ITEM;
1495};
1496
1497function resolveNodeProps(errors, node) {
1498 const comments = {
1499 before: [],
1500 after: []
1501 };
1502 let hasAnchor = false;
1503 let hasTag = false;
1504 const props = isCollectionItem(node.context.parent) ? node.context.parent.props.concat(node.props) : node.props;
1505
1506 for (const {
1507 start,
1508 end
1509 } of props) {
1510 switch (node.context.src[start]) {
1511 case PlainValue.Char.COMMENT:
1512 {
1513 if (!node.commentHasRequiredWhitespace(start)) {
1514 const msg = 'Comments must be separated from other tokens by white space characters';
1515 errors.push(new PlainValue.YAMLSemanticError(node, msg));
1516 }
1517
1518 const {
1519 header,
1520 valueRange
1521 } = node;
1522 const cc = valueRange && (start > valueRange.start || header && start > header.start) ? comments.after : comments.before;
1523 cc.push(node.context.src.slice(start + 1, end));
1524 break;
1525 }
1526 // Actual anchor & tag resolution is handled by schema, here we just complain
1527
1528 case PlainValue.Char.ANCHOR:
1529 if (hasAnchor) {
1530 const msg = 'A node can have at most one anchor';
1531 errors.push(new PlainValue.YAMLSemanticError(node, msg));
1532 }
1533
1534 hasAnchor = true;
1535 break;
1536
1537 case PlainValue.Char.TAG:
1538 if (hasTag) {
1539 const msg = 'A node can have at most one tag';
1540 errors.push(new PlainValue.YAMLSemanticError(node, msg));
1541 }
1542
1543 hasTag = true;
1544 break;
1545 }
1546 }
1547
1548 return {
1549 comments,
1550 hasAnchor,
1551 hasTag
1552 };
1553}
1554
1555function resolveNodeValue(doc, node) {
1556 const {
1557 anchors,
1558 errors,
1559 schema
1560 } = doc;
1561
1562 if (node.type === PlainValue.Type.ALIAS) {
1563 const name = node.rawValue;
1564 const src = anchors.getNode(name);
1565
1566 if (!src) {
1567 const msg = `Aliased anchor not found: ${name}`;
1568 errors.push(new PlainValue.YAMLReferenceError(node, msg));
1569 return null;
1570 } // Lazy resolution for circular references
1571
1572
1573 const res = new Alias(src);
1574
1575 anchors._cstAliases.push(res);
1576
1577 return res;
1578 }
1579
1580 const tagName = resolveTagName(doc, node);
1581 if (tagName) return resolveTag(doc, node, tagName);
1582
1583 if (node.type !== PlainValue.Type.PLAIN) {
1584 const msg = `Failed to resolve ${node.type} node here`;
1585 errors.push(new PlainValue.YAMLSyntaxError(node, msg));
1586 return null;
1587 }
1588
1589 try {
1590 const str = resolveString(doc, node);
1591 return resolveScalar(str, schema.tags, schema.tags.scalarFallback);
1592 } catch (error) {
1593 if (!error.source) error.source = node;
1594 errors.push(error);
1595 return null;
1596 }
1597} // sets node.resolved on success
1598
1599
1600function resolveNode(doc, node) {
1601 if (!node) return null;
1602 if (node.error) doc.errors.push(node.error);
1603 const {
1604 comments,
1605 hasAnchor,
1606 hasTag
1607 } = resolveNodeProps(doc.errors, node);
1608
1609 if (hasAnchor) {
1610 const {
1611 anchors
1612 } = doc;
1613 const name = node.anchor;
1614 const prev = anchors.getNode(name); // At this point, aliases for any preceding node with the same anchor
1615 // name have already been resolved, so it may safely be renamed.
1616
1617 if (prev) anchors.map[anchors.newName(name)] = prev; // During parsing, we need to store the CST node in anchors.map as
1618 // anchors need to be available during resolution to allow for
1619 // circular references.
1620
1621 anchors.map[name] = node;
1622 }
1623
1624 if (node.type === PlainValue.Type.ALIAS && (hasAnchor || hasTag)) {
1625 const msg = 'An alias node must not specify any properties';
1626 doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
1627 }
1628
1629 const res = resolveNodeValue(doc, node);
1630
1631 if (res) {
1632 res.range = [node.range.start, node.range.end];
1633 if (doc.options.keepCstNodes) res.cstNode = node;
1634 if (doc.options.keepNodeTypes) res.type = node.type;
1635 const cb = comments.before.join('\n');
1636
1637 if (cb) {
1638 res.commentBefore = res.commentBefore ? `${res.commentBefore}\n${cb}` : cb;
1639 }
1640
1641 const ca = comments.after.join('\n');
1642 if (ca) res.comment = res.comment ? `${res.comment}\n${ca}` : ca;
1643 }
1644
1645 return node.resolved = res;
1646}
1647
1648function resolveMap(doc, cst) {
1649 if (cst.type !== PlainValue.Type.MAP && cst.type !== PlainValue.Type.FLOW_MAP) {
1650 const msg = `A ${cst.type} node cannot be resolved as a mapping`;
1651 doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
1652 return null;
1653 }
1654
1655 const {
1656 comments,
1657 items
1658 } = cst.type === PlainValue.Type.FLOW_MAP ? resolveFlowMapItems(doc, cst) : resolveBlockMapItems(doc, cst);
1659 const map = new YAMLMap();
1660 map.items = items;
1661 resolveComments(map, comments);
1662 let hasCollectionKey = false;
1663
1664 for (let i = 0; i < items.length; ++i) {
1665 const {
1666 key: iKey
1667 } = items[i];
1668 if (iKey instanceof Collection) hasCollectionKey = true;
1669
1670 if (doc.schema.merge && iKey && iKey.value === MERGE_KEY) {
1671 items[i] = new Merge(items[i]);
1672 const sources = items[i].value.items;
1673 let error = null;
1674 sources.some(node => {
1675 if (node instanceof Alias) {
1676 // During parsing, alias sources are CST nodes; to account for
1677 // circular references their resolved values can't be used here.
1678 const {
1679 type
1680 } = node.source;
1681 if (type === PlainValue.Type.MAP || type === PlainValue.Type.FLOW_MAP) return false;
1682 return error = 'Merge nodes aliases can only point to maps';
1683 }
1684
1685 return error = 'Merge nodes can only have Alias nodes as values';
1686 });
1687 if (error) doc.errors.push(new PlainValue.YAMLSemanticError(cst, error));
1688 } else {
1689 for (let j = i + 1; j < items.length; ++j) {
1690 const {
1691 key: jKey
1692 } = items[j];
1693
1694 if (iKey === jKey || iKey && jKey && Object.prototype.hasOwnProperty.call(iKey, 'value') && iKey.value === jKey.value) {
1695 const msg = `Map keys must be unique; "${iKey}" is repeated`;
1696 doc.errors.push(new PlainValue.YAMLSemanticError(cst, msg));
1697 break;
1698 }
1699 }
1700 }
1701 }
1702
1703 if (hasCollectionKey && !doc.options.mapAsMap) {
1704 const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
1705 doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
1706 }
1707
1708 cst.resolved = map;
1709 return map;
1710}
1711
1712const valueHasPairComment = ({
1713 context: {
1714 lineStart,
1715 node,
1716 src
1717 },
1718 props
1719}) => {
1720 if (props.length === 0) return false;
1721 const {
1722 start
1723 } = props[0];
1724 if (node && start > node.valueRange.start) return false;
1725 if (src[start] !== PlainValue.Char.COMMENT) return false;
1726
1727 for (let i = lineStart; i < start; ++i) if (src[i] === '\n') return false;
1728
1729 return true;
1730};
1731
1732function resolvePairComment(item, pair) {
1733 if (!valueHasPairComment(item)) return;
1734 const comment = item.getPropValue(0, PlainValue.Char.COMMENT, true);
1735 let found = false;
1736 const cb = pair.value.commentBefore;
1737
1738 if (cb && cb.startsWith(comment)) {
1739 pair.value.commentBefore = cb.substr(comment.length + 1);
1740 found = true;
1741 } else {
1742 const cc = pair.value.comment;
1743
1744 if (!item.node && cc && cc.startsWith(comment)) {
1745 pair.value.comment = cc.substr(comment.length + 1);
1746 found = true;
1747 }
1748 }
1749
1750 if (found) pair.comment = comment;
1751}
1752
1753function resolveBlockMapItems(doc, cst) {
1754 const comments = [];
1755 const items = [];
1756 let key = undefined;
1757 let keyStart = null;
1758
1759 for (let i = 0; i < cst.items.length; ++i) {
1760 const item = cst.items[i];
1761
1762 switch (item.type) {
1763 case PlainValue.Type.BLANK_LINE:
1764 comments.push({
1765 afterKey: !!key,
1766 before: items.length
1767 });
1768 break;
1769
1770 case PlainValue.Type.COMMENT:
1771 comments.push({
1772 afterKey: !!key,
1773 before: items.length,
1774 comment: item.comment
1775 });
1776 break;
1777
1778 case PlainValue.Type.MAP_KEY:
1779 if (key !== undefined) items.push(new Pair(key));
1780 if (item.error) doc.errors.push(item.error);
1781 key = resolveNode(doc, item.node);
1782 keyStart = null;
1783 break;
1784
1785 case PlainValue.Type.MAP_VALUE:
1786 {
1787 if (key === undefined) key = null;
1788 if (item.error) doc.errors.push(item.error);
1789
1790 if (!item.context.atLineStart && item.node && item.node.type === PlainValue.Type.MAP && !item.node.context.atLineStart) {
1791 const msg = 'Nested mappings are not allowed in compact mappings';
1792 doc.errors.push(new PlainValue.YAMLSemanticError(item.node, msg));
1793 }
1794
1795 let valueNode = item.node;
1796
1797 if (!valueNode && item.props.length > 0) {
1798 // Comments on an empty mapping value need to be preserved, so we
1799 // need to construct a minimal empty node here to use instead of the
1800 // missing `item.node`. -- eemeli/yaml#19
1801 valueNode = new PlainValue.PlainValue(PlainValue.Type.PLAIN, []);
1802 valueNode.context = {
1803 parent: item,
1804 src: item.context.src
1805 };
1806 const pos = item.range.start + 1;
1807 valueNode.range = {
1808 start: pos,
1809 end: pos
1810 };
1811 valueNode.valueRange = {
1812 start: pos,
1813 end: pos
1814 };
1815
1816 if (typeof item.range.origStart === 'number') {
1817 const origPos = item.range.origStart + 1;
1818 valueNode.range.origStart = valueNode.range.origEnd = origPos;
1819 valueNode.valueRange.origStart = valueNode.valueRange.origEnd = origPos;
1820 }
1821 }
1822
1823 const pair = new Pair(key, resolveNode(doc, valueNode));
1824 resolvePairComment(item, pair);
1825 items.push(pair);
1826
1827 if (key && typeof keyStart === 'number') {
1828 if (item.range.start > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
1829 }
1830
1831 key = undefined;
1832 keyStart = null;
1833 }
1834 break;
1835
1836 default:
1837 if (key !== undefined) items.push(new Pair(key));
1838 key = resolveNode(doc, item);
1839 keyStart = item.range.start;
1840 if (item.error) doc.errors.push(item.error);
1841
1842 next: for (let j = i + 1;; ++j) {
1843 const nextItem = cst.items[j];
1844
1845 switch (nextItem && nextItem.type) {
1846 case PlainValue.Type.BLANK_LINE:
1847 case PlainValue.Type.COMMENT:
1848 continue next;
1849
1850 case PlainValue.Type.MAP_VALUE:
1851 break next;
1852
1853 default:
1854 {
1855 const msg = 'Implicit map keys need to be followed by map values';
1856 doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
1857 break next;
1858 }
1859 }
1860 }
1861
1862 if (item.valueRangeContainsNewline) {
1863 const msg = 'Implicit map keys need to be on a single line';
1864 doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
1865 }
1866
1867 }
1868 }
1869
1870 if (key !== undefined) items.push(new Pair(key));
1871 return {
1872 comments,
1873 items
1874 };
1875}
1876
1877function resolveFlowMapItems(doc, cst) {
1878 const comments = [];
1879 const items = [];
1880 let key = undefined;
1881 let explicitKey = false;
1882 let next = '{';
1883
1884 for (let i = 0; i < cst.items.length; ++i) {
1885 const item = cst.items[i];
1886
1887 if (typeof item.char === 'string') {
1888 const {
1889 char,
1890 offset
1891 } = item;
1892
1893 if (char === '?' && key === undefined && !explicitKey) {
1894 explicitKey = true;
1895 next = ':';
1896 continue;
1897 }
1898
1899 if (char === ':') {
1900 if (key === undefined) key = null;
1901
1902 if (next === ':') {
1903 next = ',';
1904 continue;
1905 }
1906 } else {
1907 if (explicitKey) {
1908 if (key === undefined && char !== ',') key = null;
1909 explicitKey = false;
1910 }
1911
1912 if (key !== undefined) {
1913 items.push(new Pair(key));
1914 key = undefined;
1915
1916 if (char === ',') {
1917 next = ':';
1918 continue;
1919 }
1920 }
1921 }
1922
1923 if (char === '}') {
1924 if (i === cst.items.length - 1) continue;
1925 } else if (char === next) {
1926 next = ':';
1927 continue;
1928 }
1929
1930 const msg = `Flow map contains an unexpected ${char}`;
1931 const err = new PlainValue.YAMLSyntaxError(cst, msg);
1932 err.offset = offset;
1933 doc.errors.push(err);
1934 } else if (item.type === PlainValue.Type.BLANK_LINE) {
1935 comments.push({
1936 afterKey: !!key,
1937 before: items.length
1938 });
1939 } else if (item.type === PlainValue.Type.COMMENT) {
1940 checkFlowCommentSpace(doc.errors, item);
1941 comments.push({
1942 afterKey: !!key,
1943 before: items.length,
1944 comment: item.comment
1945 });
1946 } else if (key === undefined) {
1947 if (next === ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Separator , missing in flow map'));
1948 key = resolveNode(doc, item);
1949 } else {
1950 if (next !== ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Indicator : missing in flow map entry'));
1951 items.push(new Pair(key, resolveNode(doc, item)));
1952 key = undefined;
1953 explicitKey = false;
1954 }
1955 }
1956
1957 checkFlowCollectionEnd(doc.errors, cst);
1958 if (key !== undefined) items.push(new Pair(key));
1959 return {
1960 comments,
1961 items
1962 };
1963}
1964
1965function resolveSeq(doc, cst) {
1966 if (cst.type !== PlainValue.Type.SEQ && cst.type !== PlainValue.Type.FLOW_SEQ) {
1967 const msg = `A ${cst.type} node cannot be resolved as a sequence`;
1968 doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
1969 return null;
1970 }
1971
1972 const {
1973 comments,
1974 items
1975 } = cst.type === PlainValue.Type.FLOW_SEQ ? resolveFlowSeqItems(doc, cst) : resolveBlockSeqItems(doc, cst);
1976 const seq = new YAMLSeq();
1977 seq.items = items;
1978 resolveComments(seq, comments);
1979
1980 if (!doc.options.mapAsMap && items.some(it => it instanceof Pair && it.key instanceof Collection)) {
1981 const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
1982 doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
1983 }
1984
1985 cst.resolved = seq;
1986 return seq;
1987}
1988
1989function resolveBlockSeqItems(doc, cst) {
1990 const comments = [];
1991 const items = [];
1992
1993 for (let i = 0; i < cst.items.length; ++i) {
1994 const item = cst.items[i];
1995
1996 switch (item.type) {
1997 case PlainValue.Type.BLANK_LINE:
1998 comments.push({
1999 before: items.length
2000 });
2001 break;
2002
2003 case PlainValue.Type.COMMENT:
2004 comments.push({
2005 comment: item.comment,
2006 before: items.length
2007 });
2008 break;
2009
2010 case PlainValue.Type.SEQ_ITEM:
2011 if (item.error) doc.errors.push(item.error);
2012 items.push(resolveNode(doc, item.node));
2013
2014 if (item.hasProps) {
2015 const msg = 'Sequence items cannot have tags or anchors before the - indicator';
2016 doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
2017 }
2018
2019 break;
2020
2021 default:
2022 if (item.error) doc.errors.push(item.error);
2023 doc.errors.push(new PlainValue.YAMLSyntaxError(item, `Unexpected ${item.type} node in sequence`));
2024 }
2025 }
2026
2027 return {
2028 comments,
2029 items
2030 };
2031}
2032
2033function resolveFlowSeqItems(doc, cst) {
2034 const comments = [];
2035 const items = [];
2036 let explicitKey = false;
2037 let key = undefined;
2038 let keyStart = null;
2039 let next = '[';
2040 let prevItem = null;
2041
2042 for (let i = 0; i < cst.items.length; ++i) {
2043 const item = cst.items[i];
2044
2045 if (typeof item.char === 'string') {
2046 const {
2047 char,
2048 offset
2049 } = item;
2050
2051 if (char !== ':' && (explicitKey || key !== undefined)) {
2052 if (explicitKey && key === undefined) key = next ? items.pop() : null;
2053 items.push(new Pair(key));
2054 explicitKey = false;
2055 key = undefined;
2056 keyStart = null;
2057 }
2058
2059 if (char === next) {
2060 next = null;
2061 } else if (!next && char === '?') {
2062 explicitKey = true;
2063 } else if (next !== '[' && char === ':' && key === undefined) {
2064 if (next === ',') {
2065 key = items.pop();
2066
2067 if (key instanceof Pair) {
2068 const msg = 'Chaining flow sequence pairs is invalid';
2069 const err = new PlainValue.YAMLSemanticError(cst, msg);
2070 err.offset = offset;
2071 doc.errors.push(err);
2072 }
2073
2074 if (!explicitKey && typeof keyStart === 'number') {
2075 const keyEnd = item.range ? item.range.start : item.offset;
2076 if (keyEnd > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
2077 const {
2078 src
2079 } = prevItem.context;
2080
2081 for (let i = keyStart; i < keyEnd; ++i) if (src[i] === '\n') {
2082 const msg = 'Implicit keys of flow sequence pairs need to be on a single line';
2083 doc.errors.push(new PlainValue.YAMLSemanticError(prevItem, msg));
2084 break;
2085 }
2086 }
2087 } else {
2088 key = null;
2089 }
2090
2091 keyStart = null;
2092 explicitKey = false;
2093 next = null;
2094 } else if (next === '[' || char !== ']' || i < cst.items.length - 1) {
2095 const msg = `Flow sequence contains an unexpected ${char}`;
2096 const err = new PlainValue.YAMLSyntaxError(cst, msg);
2097 err.offset = offset;
2098 doc.errors.push(err);
2099 }
2100 } else if (item.type === PlainValue.Type.BLANK_LINE) {
2101 comments.push({
2102 before: items.length
2103 });
2104 } else if (item.type === PlainValue.Type.COMMENT) {
2105 checkFlowCommentSpace(doc.errors, item);
2106 comments.push({
2107 comment: item.comment,
2108 before: items.length
2109 });
2110 } else {
2111 if (next) {
2112 const msg = `Expected a ${next} in flow sequence`;
2113 doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
2114 }
2115
2116 const value = resolveNode(doc, item);
2117
2118 if (key === undefined) {
2119 items.push(value);
2120 prevItem = item;
2121 } else {
2122 items.push(new Pair(key, value));
2123 key = undefined;
2124 }
2125
2126 keyStart = item.range.start;
2127 next = ',';
2128 }
2129 }
2130
2131 checkFlowCollectionEnd(doc.errors, cst);
2132 if (key !== undefined) items.push(new Pair(key));
2133 return {
2134 comments,
2135 items
2136 };
2137}
2138
2139exports.Alias = Alias;
2140exports.Collection = Collection;
2141exports.Merge = Merge;
2142exports.Node = Node;
2143exports.Pair = Pair;
2144exports.Scalar = Scalar;
2145exports.YAMLMap = YAMLMap;
2146exports.YAMLSeq = YAMLSeq;
2147exports.addComment = addComment;
2148exports.binaryOptions = binaryOptions;
2149exports.boolOptions = boolOptions;
2150exports.findPair = findPair;
2151exports.intOptions = intOptions;
2152exports.isEmptyPath = isEmptyPath;
2153exports.nullOptions = nullOptions;
2154exports.resolveMap = resolveMap;
2155exports.resolveNode = resolveNode;
2156exports.resolveSeq = resolveSeq;
2157exports.resolveString = resolveString;
2158exports.strOptions = strOptions;
2159exports.stringifyNumber = stringifyNumber;
2160exports.stringifyString = stringifyString;
2161exports.toJSON = toJSON;
Note: See TracBrowser for help on using the repository browser.