source: frontend/node_modules/yaml/dist/resolveSeq-95613e94.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

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