1 | 'use strict';
|
---|
2 |
|
---|
3 | var PlainValue = require('./PlainValue-ec8e588e.js');
|
---|
4 |
|
---|
5 | function 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 | }
|
---|
10 | function addComment(str, indent, comment) {
|
---|
11 | return !comment ? str : comment.indexOf('\n') === -1 ? `${str} #${comment}` : `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`);
|
---|
12 | }
|
---|
13 |
|
---|
14 | class Node {}
|
---|
15 |
|
---|
16 | function 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 |
|
---|
34 | class 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 |
|
---|
50 | function 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 |
|
---|
76 | const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
|
---|
77 | class 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 |
|
---|
226 | PlainValue._defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
|
---|
227 |
|
---|
228 | function 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 |
|
---|
234 | class 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 |
|
---|
289 | const 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 |
|
---|
304 | class 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 |
|
---|
453 | PlainValue._defineProperty(Pair, "Type", {
|
---|
454 | PAIR: 'PAIR',
|
---|
455 | MERGE_PAIR: 'MERGE_PAIR'
|
---|
456 | });
|
---|
457 |
|
---|
458 | const 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 |
|
---|
480 | class 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 |
|
---|
542 | PlainValue._defineProperty(Alias, "default", true);
|
---|
543 |
|
---|
544 | function 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 | }
|
---|
556 | class 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 |
|
---|
629 | const MERGE_KEY = '<<';
|
---|
630 | class 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 |
|
---|
694 | const binaryOptions = {
|
---|
695 | defaultType: PlainValue.Type.BLOCK_LITERAL,
|
---|
696 | lineWidth: 76
|
---|
697 | };
|
---|
698 | const boolOptions = {
|
---|
699 | trueStr: 'true',
|
---|
700 | falseStr: 'false'
|
---|
701 | };
|
---|
702 | const intOptions = {
|
---|
703 | asBigInt: false
|
---|
704 | };
|
---|
705 | const nullOptions = {
|
---|
706 | nullStr: 'null'
|
---|
707 | };
|
---|
708 | const 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 |
|
---|
720 | function 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 |
|
---|
742 | const FOLD_FLOW = 'flow';
|
---|
743 | const FOLD_BLOCK = 'block';
|
---|
744 | const FOLD_QUOTED = 'quoted'; // presumes i+1 is at the start of a line
|
---|
745 | // returns index of last newline in more-indented block
|
---|
746 |
|
---|
747 | const 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 |
|
---|
782 | function 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 |
|
---|
894 | const 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 |
|
---|
902 | const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
|
---|
903 |
|
---|
904 | function 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 |
|
---|
921 | function 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 |
|
---|
1022 | function 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 |
|
---|
1035 | function 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 |
|
---|
1101 | function 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 |
|
---|
1160 | function 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 |
|
---|
1218 | function 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 |
|
---|
1244 | function 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 | }
|
---|
1289 | function 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 | }
|
---|
1297 | function 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 | }
|
---|
1302 | function 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[] }
|
---|
1327 | function 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 |
|
---|
1338 | function 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 |
|
---|
1369 | function 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 |
|
---|
1421 | function 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 |
|
---|
1441 | function 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 |
|
---|
1458 | function 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 |
|
---|
1489 | const 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 |
|
---|
1497 | function 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 |
|
---|
1555 | function 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 |
|
---|
1600 | function 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 |
|
---|
1648 | function 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 |
|
---|
1712 | const 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 |
|
---|
1732 | function 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 |
|
---|
1753 | function 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 |
|
---|
1877 | function 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 |
|
---|
1965 | function 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 |
|
---|
1989 | function 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 |
|
---|
2033 | function 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 |
|
---|
2139 | exports.Alias = Alias;
|
---|
2140 | exports.Collection = Collection;
|
---|
2141 | exports.Merge = Merge;
|
---|
2142 | exports.Node = Node;
|
---|
2143 | exports.Pair = Pair;
|
---|
2144 | exports.Scalar = Scalar;
|
---|
2145 | exports.YAMLMap = YAMLMap;
|
---|
2146 | exports.YAMLSeq = YAMLSeq;
|
---|
2147 | exports.addComment = addComment;
|
---|
2148 | exports.binaryOptions = binaryOptions;
|
---|
2149 | exports.boolOptions = boolOptions;
|
---|
2150 | exports.findPair = findPair;
|
---|
2151 | exports.intOptions = intOptions;
|
---|
2152 | exports.isEmptyPath = isEmptyPath;
|
---|
2153 | exports.nullOptions = nullOptions;
|
---|
2154 | exports.resolveMap = resolveMap;
|
---|
2155 | exports.resolveNode = resolveNode;
|
---|
2156 | exports.resolveSeq = resolveSeq;
|
---|
2157 | exports.resolveString = resolveString;
|
---|
2158 | exports.strOptions = strOptions;
|
---|
2159 | exports.stringifyNumber = stringifyNumber;
|
---|
2160 | exports.stringifyString = stringifyString;
|
---|
2161 | exports.toJSON = toJSON;
|
---|