| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | var PlainValue = require('./PlainValue-516d5bc2.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 | 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 |
|
|---|
| 32 | class 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 |
|
|---|
| 45 | function 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. [])
|
|---|
| 68 | const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
|
|---|
| 69 | class 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 | }
|
|---|
| 197 | PlainValue._defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
|
|---|
| 198 |
|
|---|
| 199 | function 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 | }
|
|---|
| 204 | class 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 |
|
|---|
| 250 | const 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 | };
|
|---|
| 264 | class 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 | }
|
|---|
| 387 | PlainValue._defineProperty(Pair, "Type", {
|
|---|
| 388 | PAIR: 'PAIR',
|
|---|
| 389 | MERGE_PAIR: 'MERGE_PAIR'
|
|---|
| 390 | });
|
|---|
| 391 |
|
|---|
| 392 | const 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 | };
|
|---|
| 410 | class 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 | }
|
|---|
| 463 | PlainValue._defineProperty(Alias, "default", true);
|
|---|
| 464 |
|
|---|
| 465 | function 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 | }
|
|---|
| 475 | class 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 |
|
|---|
| 536 | const MERGE_KEY = '<<';
|
|---|
| 537 | class 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 |
|
|---|
| 594 | const binaryOptions = {
|
|---|
| 595 | defaultType: PlainValue.Type.BLOCK_LITERAL,
|
|---|
| 596 | lineWidth: 76
|
|---|
| 597 | };
|
|---|
| 598 | const boolOptions = {
|
|---|
| 599 | trueStr: 'true',
|
|---|
| 600 | falseStr: 'false'
|
|---|
| 601 | };
|
|---|
| 602 | const intOptions = {
|
|---|
| 603 | asBigInt: false
|
|---|
| 604 | };
|
|---|
| 605 | const nullOptions = {
|
|---|
| 606 | nullStr: 'null'
|
|---|
| 607 | };
|
|---|
| 608 | const 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
|
|---|
| 621 | function 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 |
|
|---|
| 641 | const FOLD_FLOW = 'flow';
|
|---|
| 642 | const FOLD_BLOCK = 'block';
|
|---|
| 643 | const 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
|
|---|
| 647 | const 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 | */
|
|---|
| 678 | function 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 |
|
|---|
| 774 | const 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.
|
|---|
| 782 | const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
|
|---|
| 783 | function 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 | }
|
|---|
| 797 | function 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 | }
|
|---|
| 879 | function 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 | }
|
|---|
| 890 | function 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 | }
|
|---|
| 945 | function 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 | }
|
|---|
| 995 | function 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 |
|
|---|
| 1043 | function 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 |
|
|---|
| 1064 | function 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 | }
|
|---|
| 1100 | function 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 | }
|
|---|
| 1107 | function 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 | }
|
|---|
| 1112 | function 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[] }
|
|---|
| 1135 | function 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 |
|
|---|
| 1146 | function 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 | }
|
|---|
| 1171 | function 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 |
|
|---|
| 1216 | function 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 | }
|
|---|
| 1233 | function 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 | }
|
|---|
| 1247 | function 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 |
|
|---|
| 1280 | const 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 | };
|
|---|
| 1287 | function 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 | }
|
|---|
| 1338 | function 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
|
|---|
| 1376 | function 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 |
|
|---|
| 1417 | function 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 | }
|
|---|
| 1473 | const 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 | };
|
|---|
| 1490 | function 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 | }
|
|---|
| 1507 | function 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 | }
|
|---|
| 1610 | function 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 |
|
|---|
| 1688 | function 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 | }
|
|---|
| 1708 | function 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 | }
|
|---|
| 1743 | function 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 |
|
|---|
| 1837 | exports.Alias = Alias;
|
|---|
| 1838 | exports.Collection = Collection;
|
|---|
| 1839 | exports.Merge = Merge;
|
|---|
| 1840 | exports.Node = Node;
|
|---|
| 1841 | exports.Pair = Pair;
|
|---|
| 1842 | exports.Scalar = Scalar;
|
|---|
| 1843 | exports.YAMLMap = YAMLMap;
|
|---|
| 1844 | exports.YAMLSeq = YAMLSeq;
|
|---|
| 1845 | exports.addComment = addComment;
|
|---|
| 1846 | exports.binaryOptions = binaryOptions;
|
|---|
| 1847 | exports.boolOptions = boolOptions;
|
|---|
| 1848 | exports.findPair = findPair;
|
|---|
| 1849 | exports.intOptions = intOptions;
|
|---|
| 1850 | exports.isEmptyPath = isEmptyPath;
|
|---|
| 1851 | exports.nullOptions = nullOptions;
|
|---|
| 1852 | exports.resolveMap = resolveMap;
|
|---|
| 1853 | exports.resolveNode = resolveNode;
|
|---|
| 1854 | exports.resolveSeq = resolveSeq;
|
|---|
| 1855 | exports.resolveString = resolveString;
|
|---|
| 1856 | exports.strOptions = strOptions;
|
|---|
| 1857 | exports.stringifyNumber = stringifyNumber;
|
|---|
| 1858 | exports.stringifyString = stringifyString;
|
|---|
| 1859 | exports.toJSON = toJSON;
|
|---|