123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115 |
- 'use strict';
- var PlainValue = require('./PlainValue-ec8e588e.js');
- function addCommentBefore(str, indent, comment) {
- if (!comment) return str;
- const cc = comment.replace(/[\s\S]^/gm, `$&${indent}#`);
- return `#${cc}\n${indent}${str}`;
- }
- function addComment(str, indent, comment) {
- return !comment ? str : comment.indexOf('\n') === -1 ? `${str} #${comment}` : `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`);
- }
- class Node {}
- function toJSON(value, arg, ctx) {
- if (Array.isArray(value)) return value.map((v, i) => toJSON(v, String(i), ctx));
- if (value && typeof value.toJSON === 'function') {
- const anchor = ctx && ctx.anchors && ctx.anchors.get(value);
- if (anchor) ctx.onCreate = res => {
- anchor.res = res;
- delete ctx.onCreate;
- };
- const res = value.toJSON(arg, ctx);
- if (anchor && ctx.onCreate) ctx.onCreate(res);
- return res;
- }
- if ((!ctx || !ctx.keep) && typeof value === 'bigint') return Number(value);
- return value;
- }
- class Scalar extends Node {
- constructor(value) {
- super();
- this.value = value;
- }
- toJSON(arg, ctx) {
- return ctx && ctx.keep ? this.value : toJSON(this.value, arg, ctx);
- }
- toString() {
- return String(this.value);
- }
- }
- function collectionFromPath(schema, path, value) {
- let v = value;
- for (let i = path.length - 1; i >= 0; --i) {
- const k = path[i];
- const o = Number.isInteger(k) && k >= 0 ? [] : {};
- o[k] = v;
- v = o;
- }
- return schema.createNode(v, false);
- } // null, undefined, or an empty non-string iterable (e.g. [])
- const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
- class Collection extends Node {
- constructor(schema) {
- super();
- PlainValue._defineProperty(this, "items", []);
- this.schema = schema;
- }
- addIn(path, value) {
- if (isEmptyPath(path)) this.add(value);else {
- const [key, ...rest] = path;
- const node = this.get(key, true);
- 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}`);
- }
- }
- deleteIn([key, ...rest]) {
- if (rest.length === 0) return this.delete(key);
- const node = this.get(key, true);
- if (node instanceof Collection) return node.deleteIn(rest);else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
- }
- getIn([key, ...rest], keepScalar) {
- const node = this.get(key, true);
- if (rest.length === 0) return !keepScalar && node instanceof Scalar ? node.value : node;else return node instanceof Collection ? node.getIn(rest, keepScalar) : undefined;
- }
- hasAllNullValues() {
- return this.items.every(node => {
- if (!node || node.type !== 'PAIR') return false;
- const n = node.value;
- return n == null || n instanceof Scalar && n.value == null && !n.commentBefore && !n.comment && !n.tag;
- });
- }
- hasIn([key, ...rest]) {
- if (rest.length === 0) return this.has(key);
- const node = this.get(key, true);
- return node instanceof Collection ? node.hasIn(rest) : false;
- }
- setIn([key, ...rest], value) {
- if (rest.length === 0) {
- this.set(key, value);
- } else {
- const node = this.get(key, true);
- 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}`);
- }
- } // overridden in implementations
- /* istanbul ignore next */
- toJSON() {
- return null;
- }
- toString(ctx, {
- blockItem,
- flowChars,
- isMap,
- itemIndent
- }, onComment, onChompKeep) {
- const {
- indent,
- indentStep,
- stringify
- } = ctx;
- const inFlow = this.type === PlainValue.Type.FLOW_MAP || this.type === PlainValue.Type.FLOW_SEQ || ctx.inFlow;
- if (inFlow) itemIndent += indentStep;
- const allNullValues = isMap && this.hasAllNullValues();
- ctx = Object.assign({}, ctx, {
- allNullValues,
- indent: itemIndent,
- inFlow,
- type: null
- });
- let chompKeep = false;
- let hasItemWithNewLine = false;
- const nodes = this.items.reduce((nodes, item, i) => {
- let comment;
- if (item) {
- if (!chompKeep && item.spaceBefore) nodes.push({
- type: 'comment',
- str: ''
- });
- if (item.commentBefore) item.commentBefore.match(/^.*$/gm).forEach(line => {
- nodes.push({
- type: 'comment',
- str: `#${line}`
- });
- });
- if (item.comment) comment = item.comment;
- 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;
- }
- chompKeep = false;
- let str = stringify(item, ctx, () => comment = null, () => chompKeep = true);
- if (inFlow && !hasItemWithNewLine && str.includes('\n')) hasItemWithNewLine = true;
- if (inFlow && i < this.items.length - 1) str += ',';
- str = addComment(str, itemIndent, comment);
- if (chompKeep && (comment || inFlow)) chompKeep = false;
- nodes.push({
- type: 'item',
- str
- });
- return nodes;
- }, []);
- let str;
- if (nodes.length === 0) {
- str = flowChars.start + flowChars.end;
- } else if (inFlow) {
- const {
- start,
- end
- } = flowChars;
- const strings = nodes.map(n => n.str);
- if (hasItemWithNewLine || strings.reduce((sum, str) => sum + str.length + 2, 2) > Collection.maxFlowStringSingleLineLength) {
- str = start;
- for (const s of strings) {
- str += s ? `\n${indentStep}${indent}${s}` : '\n';
- }
- str += `\n${indent}${end}`;
- } else {
- str = `${start} ${strings.join(' ')} ${end}`;
- }
- } else {
- const strings = nodes.map(blockItem);
- str = strings.shift();
- for (const s of strings) str += s ? `\n${indent}${s}` : '\n';
- }
- if (this.comment) {
- str += '\n' + this.comment.replace(/^/gm, `${indent}#`);
- if (onComment) onComment();
- } else if (chompKeep && onChompKeep) onChompKeep();
- return str;
- }
- }
- PlainValue._defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
- function asItemIndex(key) {
- let idx = key instanceof Scalar ? key.value : key;
- if (idx && typeof idx === 'string') idx = Number(idx);
- return Number.isInteger(idx) && idx >= 0 ? idx : null;
- }
- class YAMLSeq extends Collection {
- add(value) {
- this.items.push(value);
- }
- delete(key) {
- const idx = asItemIndex(key);
- if (typeof idx !== 'number') return false;
- const del = this.items.splice(idx, 1);
- return del.length > 0;
- }
- get(key, keepScalar) {
- const idx = asItemIndex(key);
- if (typeof idx !== 'number') return undefined;
- const it = this.items[idx];
- return !keepScalar && it instanceof Scalar ? it.value : it;
- }
- has(key) {
- const idx = asItemIndex(key);
- return typeof idx === 'number' && idx < this.items.length;
- }
- set(key, value) {
- const idx = asItemIndex(key);
- if (typeof idx !== 'number') throw new Error(`Expected a valid index, not ${key}.`);
- this.items[idx] = value;
- }
- toJSON(_, ctx) {
- const seq = [];
- if (ctx && ctx.onCreate) ctx.onCreate(seq);
- let i = 0;
- for (const item of this.items) seq.push(toJSON(item, String(i++), ctx));
- return seq;
- }
- toString(ctx, onComment, onChompKeep) {
- if (!ctx) return JSON.stringify(this);
- return super.toString(ctx, {
- blockItem: n => n.type === 'comment' ? n.str : `- ${n.str}`,
- flowChars: {
- start: '[',
- end: ']'
- },
- isMap: false,
- itemIndent: (ctx.indent || '') + ' '
- }, onComment, onChompKeep);
- }
- }
- const stringifyKey = (key, jsKey, ctx) => {
- if (jsKey === null) return '';
- if (typeof jsKey !== 'object') return String(jsKey);
- if (key instanceof Node && ctx && ctx.doc) return key.toString({
- anchors: {},
- doc: ctx.doc,
- indent: '',
- indentStep: ctx.indentStep,
- inFlow: true,
- inStringifyKey: true,
- stringify: ctx.stringify
- });
- return JSON.stringify(jsKey);
- };
- class Pair extends Node {
- constructor(key, value = null) {
- super();
- this.key = key;
- this.value = value;
- this.type = Pair.Type.PAIR;
- }
- get commentBefore() {
- return this.key instanceof Node ? this.key.commentBefore : undefined;
- }
- set commentBefore(cb) {
- if (this.key == null) this.key = new Scalar(null);
- if (this.key instanceof Node) this.key.commentBefore = cb;else {
- const msg = 'Pair.commentBefore is an alias for Pair.key.commentBefore. To set it, the key must be a Node.';
- throw new Error(msg);
- }
- }
- addToJSMap(ctx, map) {
- const key = toJSON(this.key, '', ctx);
- if (map instanceof Map) {
- const value = toJSON(this.value, key, ctx);
- map.set(key, value);
- } else if (map instanceof Set) {
- map.add(key);
- } else {
- const stringKey = stringifyKey(this.key, key, ctx);
- map[stringKey] = toJSON(this.value, stringKey, ctx);
- }
- return map;
- }
- toJSON(_, ctx) {
- const pair = ctx && ctx.mapAsMap ? new Map() : {};
- return this.addToJSMap(ctx, pair);
- }
- toString(ctx, onComment, onChompKeep) {
- if (!ctx || !ctx.doc) return JSON.stringify(this);
- const {
- indent: indentSize,
- indentSeq,
- simpleKeys
- } = ctx.doc.options;
- let {
- key,
- value
- } = this;
- let keyComment = key instanceof Node && key.comment;
- if (simpleKeys) {
- if (keyComment) {
- throw new Error('With simple keys, key nodes cannot have comments');
- }
- if (key instanceof Collection) {
- const msg = 'With simple keys, collection cannot be used as a key value';
- throw new Error(msg);
- }
- }
- const explicitKey = !simpleKeys && (!key || keyComment || key instanceof Collection || key.type === PlainValue.Type.BLOCK_FOLDED || key.type === PlainValue.Type.BLOCK_LITERAL);
- const {
- doc,
- indent,
- indentStep,
- stringify
- } = ctx;
- ctx = Object.assign({}, ctx, {
- implicitKey: !explicitKey,
- indent: indent + indentStep
- });
- let chompKeep = false;
- let str = stringify(key, ctx, () => keyComment = null, () => chompKeep = true);
- str = addComment(str, ctx.indent, keyComment);
- if (ctx.allNullValues && !simpleKeys) {
- if (this.comment) {
- str = addComment(str, ctx.indent, this.comment);
- if (onComment) onComment();
- } else if (chompKeep && !keyComment && onChompKeep) onChompKeep();
- return ctx.inFlow ? str : `? ${str}`;
- }
- str = explicitKey ? `? ${str}\n${indent}:` : `${str}:`;
- if (this.comment) {
- // expected (but not strictly required) to be a single-line comment
- str = addComment(str, ctx.indent, this.comment);
- if (onComment) onComment();
- }
- let vcb = '';
- let valueComment = null;
- if (value instanceof Node) {
- if (value.spaceBefore) vcb = '\n';
- if (value.commentBefore) {
- const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
- vcb += `\n${cs}`;
- }
- valueComment = value.comment;
- } else if (value && typeof value === 'object') {
- value = doc.schema.createNode(value, true);
- }
- ctx.implicitKey = false;
- if (!explicitKey && !this.comment && value instanceof Scalar) ctx.indentAtStart = str.length + 1;
- chompKeep = false;
- if (!indentSeq && indentSize >= 2 && !ctx.inFlow && !explicitKey && value instanceof YAMLSeq && value.type !== PlainValue.Type.FLOW_SEQ && !value.tag && !doc.anchors.getName(value)) {
- // If indentSeq === false, consider '- ' as part of indentation where possible
- ctx.indent = ctx.indent.substr(2);
- }
- const valueStr = stringify(value, ctx, () => valueComment = null, () => chompKeep = true);
- let ws = ' ';
- if (vcb || this.comment) {
- ws = `${vcb}\n${ctx.indent}`;
- } else if (!explicitKey && value instanceof Collection) {
- const flow = valueStr[0] === '[' || valueStr[0] === '{';
- if (!flow || valueStr.includes('\n')) ws = `\n${ctx.indent}`;
- }
- if (chompKeep && !valueComment && onChompKeep) onChompKeep();
- return addComment(str + ws + valueStr, ctx.indent, valueComment);
- }
- }
- PlainValue._defineProperty(Pair, "Type", {
- PAIR: 'PAIR',
- MERGE_PAIR: 'MERGE_PAIR'
- });
- const getAliasCount = (node, anchors) => {
- if (node instanceof Alias) {
- const anchor = anchors.get(node.source);
- return anchor.count * anchor.aliasCount;
- } else if (node instanceof Collection) {
- let count = 0;
- for (const item of node.items) {
- const c = getAliasCount(item, anchors);
- if (c > count) count = c;
- }
- return count;
- } else if (node instanceof Pair) {
- const kc = getAliasCount(node.key, anchors);
- const vc = getAliasCount(node.value, anchors);
- return Math.max(kc, vc);
- }
- return 1;
- };
- class Alias extends Node {
- static stringify({
- range,
- source
- }, {
- anchors,
- doc,
- implicitKey,
- inStringifyKey
- }) {
- let anchor = Object.keys(anchors).find(a => anchors[a] === source);
- if (!anchor && inStringifyKey) anchor = doc.anchors.getName(source) || doc.anchors.newName();
- if (anchor) return `*${anchor}${implicitKey ? ' ' : ''}`;
- const msg = doc.anchors.getName(source) ? 'Alias node must be after source node' : 'Source node not found for alias node';
- throw new Error(`${msg} [${range}]`);
- }
- constructor(source) {
- super();
- this.source = source;
- this.type = PlainValue.Type.ALIAS;
- }
- set tag(t) {
- throw new Error('Alias nodes cannot have tags');
- }
- toJSON(arg, ctx) {
- if (!ctx) return toJSON(this.source, arg, ctx);
- const {
- anchors,
- maxAliasCount
- } = ctx;
- const anchor = anchors.get(this.source);
- /* istanbul ignore if */
- if (!anchor || anchor.res === undefined) {
- const msg = 'This should not happen: Alias anchor was not resolved?';
- if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
- }
- if (maxAliasCount >= 0) {
- anchor.count += 1;
- if (anchor.aliasCount === 0) anchor.aliasCount = getAliasCount(this.source, anchors);
- if (anchor.count * anchor.aliasCount > maxAliasCount) {
- const msg = 'Excessive alias count indicates a resource exhaustion attack';
- if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
- }
- }
- return anchor.res;
- } // Only called when stringifying an alias mapping key while constructing
- // Object output.
- toString(ctx) {
- return Alias.stringify(this, ctx);
- }
- }
- PlainValue._defineProperty(Alias, "default", true);
- function findPair(items, key) {
- const k = key instanceof Scalar ? key.value : key;
- for (const it of items) {
- if (it instanceof Pair) {
- if (it.key === key || it.key === k) return it;
- if (it.key && it.key.value === k) return it;
- }
- }
- return undefined;
- }
- class YAMLMap extends Collection {
- add(pair, overwrite) {
- if (!pair) pair = new Pair(pair);else if (!(pair instanceof Pair)) pair = new Pair(pair.key || pair, pair.value);
- const prev = findPair(this.items, pair.key);
- const sortEntries = this.schema && this.schema.sortMapEntries;
- if (prev) {
- if (overwrite) prev.value = pair.value;else throw new Error(`Key ${pair.key} already set`);
- } else if (sortEntries) {
- const i = this.items.findIndex(item => sortEntries(pair, item) < 0);
- if (i === -1) this.items.push(pair);else this.items.splice(i, 0, pair);
- } else {
- this.items.push(pair);
- }
- }
- delete(key) {
- const it = findPair(this.items, key);
- if (!it) return false;
- const del = this.items.splice(this.items.indexOf(it), 1);
- return del.length > 0;
- }
- get(key, keepScalar) {
- const it = findPair(this.items, key);
- const node = it && it.value;
- return !keepScalar && node instanceof Scalar ? node.value : node;
- }
- has(key) {
- return !!findPair(this.items, key);
- }
- set(key, value) {
- this.add(new Pair(key, value), true);
- }
- /**
- * @param {*} arg ignored
- * @param {*} ctx Conversion context, originally set in Document#toJSON()
- * @param {Class} Type If set, forces the returned collection type
- * @returns {*} Instance of Type, Map, or Object
- */
- toJSON(_, ctx, Type) {
- const map = Type ? new Type() : ctx && ctx.mapAsMap ? new Map() : {};
- if (ctx && ctx.onCreate) ctx.onCreate(map);
- for (const item of this.items) item.addToJSMap(ctx, map);
- return map;
- }
- toString(ctx, onComment, onChompKeep) {
- if (!ctx) return JSON.stringify(this);
- for (const item of this.items) {
- if (!(item instanceof Pair)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
- }
- return super.toString(ctx, {
- blockItem: n => n.str,
- flowChars: {
- start: '{',
- end: '}'
- },
- isMap: true,
- itemIndent: ctx.indent || ''
- }, onComment, onChompKeep);
- }
- }
- const MERGE_KEY = '<<';
- class Merge extends Pair {
- constructor(pair) {
- if (pair instanceof Pair) {
- let seq = pair.value;
- if (!(seq instanceof YAMLSeq)) {
- seq = new YAMLSeq();
- seq.items.push(pair.value);
- seq.range = pair.value.range;
- }
- super(pair.key, seq);
- this.range = pair.range;
- } else {
- super(new Scalar(MERGE_KEY), new YAMLSeq());
- }
- this.type = Pair.Type.MERGE_PAIR;
- } // If the value associated with a merge key is a single mapping node, each of
- // its key/value pairs is inserted into the current mapping, unless the key
- // already exists in it. If the value associated with the merge key is a
- // sequence, then this sequence is expected to contain mapping nodes and each
- // of these nodes is merged in turn according to its order in the sequence.
- // Keys in mapping nodes earlier in the sequence override keys specified in
- // later mapping nodes. -- http://yaml.org/type/merge.html
- addToJSMap(ctx, map) {
- for (const {
- source
- } of this.value.items) {
- if (!(source instanceof YAMLMap)) throw new Error('Merge sources must be maps');
- const srcMap = source.toJSON(null, ctx, Map);
- for (const [key, value] of srcMap) {
- if (map instanceof Map) {
- if (!map.has(key)) map.set(key, value);
- } else if (map instanceof Set) {
- map.add(key);
- } else {
- if (!Object.prototype.hasOwnProperty.call(map, key)) map[key] = value;
- }
- }
- }
- return map;
- }
- toString(ctx, onComment) {
- const seq = this.value;
- if (seq.items.length > 1) return super.toString(ctx, onComment);
- this.value = seq.items[0];
- const str = super.toString(ctx, onComment);
- this.value = seq;
- return str;
- }
- }
- const binaryOptions = {
- defaultType: PlainValue.Type.BLOCK_LITERAL,
- lineWidth: 76
- };
- const boolOptions = {
- trueStr: 'true',
- falseStr: 'false'
- };
- const intOptions = {
- asBigInt: false
- };
- const nullOptions = {
- nullStr: 'null'
- };
- const strOptions = {
- defaultType: PlainValue.Type.PLAIN,
- doubleQuoted: {
- jsonEncoding: false,
- minMultiLineLength: 40
- },
- fold: {
- lineWidth: 80,
- minContentWidth: 20
- }
- };
- function resolveScalar(str, tags, scalarFallback) {
- for (const {
- format,
- test,
- resolve
- } of tags) {
- if (test) {
- const match = str.match(test);
- if (match) {
- let res = resolve.apply(null, match);
- if (!(res instanceof Scalar)) res = new Scalar(res);
- if (format) res.format = format;
- return res;
- }
- }
- }
- if (scalarFallback) str = scalarFallback(str);
- return new Scalar(str);
- }
- const FOLD_FLOW = 'flow';
- const FOLD_BLOCK = 'block';
- const FOLD_QUOTED = 'quoted'; // presumes i+1 is at the start of a line
- // returns index of last newline in more-indented block
- const consumeMoreIndentedLines = (text, i) => {
- let ch = text[i + 1];
- while (ch === ' ' || ch === '\t') {
- do {
- ch = text[i += 1];
- } while (ch && ch !== '\n');
- ch = text[i + 1];
- }
- return i;
- };
- /**
- * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
- * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
- * terminated with `\n` and started with `indent`.
- *
- * @param {string} text
- * @param {string} indent
- * @param {string} [mode='flow'] `'block'` prevents more-indented lines
- * from being folded; `'quoted'` allows for `\` escapes, including escaped
- * newlines
- * @param {Object} options
- * @param {number} [options.indentAtStart] Accounts for leading contents on
- * the first line, defaulting to `indent.length`
- * @param {number} [options.lineWidth=80]
- * @param {number} [options.minContentWidth=20] Allow highly indented lines to
- * stretch the line width
- * @param {function} options.onFold Called once if the text is folded
- * @param {function} options.onFold Called once if any line of text exceeds
- * lineWidth characters
- */
- function foldFlowLines(text, indent, mode, {
- indentAtStart,
- lineWidth = 80,
- minContentWidth = 20,
- onFold,
- onOverflow
- }) {
- if (!lineWidth || lineWidth < 0) return text;
- const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
- if (text.length <= endStep) return text;
- const folds = [];
- const escapedFolds = {};
- let end = lineWidth - (typeof indentAtStart === 'number' ? indentAtStart : indent.length);
- let split = undefined;
- let prev = undefined;
- let overflow = false;
- let i = -1;
- if (mode === FOLD_BLOCK) {
- i = consumeMoreIndentedLines(text, i);
- if (i !== -1) end = i + endStep;
- }
- for (let ch; ch = text[i += 1];) {
- if (mode === FOLD_QUOTED && ch === '\\') {
- switch (text[i + 1]) {
- case 'x':
- i += 3;
- break;
- case 'u':
- i += 5;
- break;
- case 'U':
- i += 9;
- break;
- default:
- i += 1;
- }
- }
- if (ch === '\n') {
- if (mode === FOLD_BLOCK) i = consumeMoreIndentedLines(text, i);
- end = i + endStep;
- split = undefined;
- } else {
- if (ch === ' ' && prev && prev !== ' ' && prev !== '\n' && prev !== '\t') {
- // space surrounded by non-space can be replaced with newline + indent
- const next = text[i + 1];
- if (next && next !== ' ' && next !== '\n' && next !== '\t') split = i;
- }
- if (i >= end) {
- if (split) {
- folds.push(split);
- end = split + endStep;
- split = undefined;
- } else if (mode === FOLD_QUOTED) {
- // white-space collected at end may stretch past lineWidth
- while (prev === ' ' || prev === '\t') {
- prev = ch;
- ch = text[i += 1];
- overflow = true;
- } // i - 2 accounts for not-dropped last char + newline-escaping \
- folds.push(i - 2);
- escapedFolds[i - 2] = true;
- end = i - 2 + endStep;
- split = undefined;
- } else {
- overflow = true;
- }
- }
- }
- prev = ch;
- }
- if (overflow && onOverflow) onOverflow();
- if (folds.length === 0) return text;
- if (onFold) onFold();
- let res = text.slice(0, folds[0]);
- for (let i = 0; i < folds.length; ++i) {
- const fold = folds[i];
- const end = folds[i + 1] || text.length;
- if (mode === FOLD_QUOTED && escapedFolds[fold]) res += `${text[fold]}\\`;
- res += `\n${indent}${text.slice(fold + 1, end)}`;
- }
- return res;
- }
- const getFoldOptions = ({
- indentAtStart
- }) => indentAtStart ? Object.assign({
- indentAtStart
- }, strOptions.fold) : strOptions.fold; // Also checks for lines starting with %, as parsing the output as YAML 1.1 will
- // presume that's starting a new document.
- const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
- function lineLengthOverLimit(str, limit) {
- const strLen = str.length;
- if (strLen <= limit) return false;
- for (let i = 0, start = 0; i < strLen; ++i) {
- if (str[i] === '\n') {
- if (i - start > limit) return true;
- start = i + 1;
- if (strLen - start <= limit) return false;
- }
- }
- return true;
- }
- function doubleQuotedString(value, ctx) {
- const {
- implicitKey
- } = ctx;
- const {
- jsonEncoding,
- minMultiLineLength
- } = strOptions.doubleQuoted;
- const json = JSON.stringify(value);
- if (jsonEncoding) return json;
- const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
- let str = '';
- let start = 0;
- for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
- if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
- // space before newline needs to be escaped to not be folded
- str += json.slice(start, i) + '\\ ';
- i += 1;
- start = i;
- ch = '\\';
- }
- if (ch === '\\') switch (json[i + 1]) {
- case 'u':
- {
- str += json.slice(start, i);
- const code = json.substr(i + 2, 4);
- switch (code) {
- case '0000':
- str += '\\0';
- break;
- case '0007':
- str += '\\a';
- break;
- case '000b':
- str += '\\v';
- break;
- case '001b':
- str += '\\e';
- break;
- case '0085':
- str += '\\N';
- break;
- case '00a0':
- str += '\\_';
- break;
- case '2028':
- str += '\\L';
- break;
- case '2029':
- str += '\\P';
- break;
- default:
- if (code.substr(0, 2) === '00') str += '\\x' + code.substr(2);else str += json.substr(i, 6);
- }
- i += 5;
- start = i + 1;
- }
- break;
- case 'n':
- if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
- i += 1;
- } else {
- // folding will eat first newline
- str += json.slice(start, i) + '\n\n';
- while (json[i + 2] === '\\' && json[i + 3] === 'n' && json[i + 4] !== '"') {
- str += '\n';
- i += 2;
- }
- str += indent; // space after newline needs to be escaped to not be folded
- if (json[i + 2] === ' ') str += '\\';
- i += 1;
- start = i + 1;
- }
- break;
- default:
- i += 1;
- }
- }
- str = start ? str + json.slice(start) : json;
- return implicitKey ? str : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx));
- }
- function singleQuotedString(value, ctx) {
- if (ctx.implicitKey) {
- if (/\n/.test(value)) return doubleQuotedString(value, ctx);
- } else {
- // single quoted string can't have leading or trailing whitespace around newline
- if (/[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx);
- }
- const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
- const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
- return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx));
- }
- function blockString({
- comment,
- type,
- value
- }, ctx, onComment, onChompKeep) {
- // 1. Block can't end in whitespace unless the last line is non-empty.
- // 2. Strings consisting of only whitespace are best rendered explicitly.
- if (/\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
- return doubleQuotedString(value, ctx);
- }
- const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? ' ' : '');
- const indentSize = indent ? '2' : '1'; // root is at -1
- const literal = type === PlainValue.Type.BLOCK_FOLDED ? false : type === PlainValue.Type.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, strOptions.fold.lineWidth - indent.length);
- let header = literal ? '|' : '>';
- if (!value) return header + '\n';
- let wsStart = '';
- let wsEnd = '';
- value = value.replace(/[\n\t ]*$/, ws => {
- const n = ws.indexOf('\n');
- if (n === -1) {
- header += '-'; // strip
- } else if (value === ws || n !== ws.length - 1) {
- header += '+'; // keep
- if (onChompKeep) onChompKeep();
- }
- wsEnd = ws.replace(/\n$/, '');
- return '';
- }).replace(/^[\n ]*/, ws => {
- if (ws.indexOf(' ') !== -1) header += indentSize;
- const m = ws.match(/ +$/);
- if (m) {
- wsStart = ws.slice(0, -m[0].length);
- return m[0];
- } else {
- wsStart = ws;
- return '';
- }
- });
- if (wsEnd) wsEnd = wsEnd.replace(/\n+(?!\n|$)/g, `$&${indent}`);
- if (wsStart) wsStart = wsStart.replace(/\n+/g, `$&${indent}`);
- if (comment) {
- header += ' #' + comment.replace(/ ?[\r\n]+/g, ' ');
- if (onComment) onComment();
- }
- if (!value) return `${header}${indentSize}\n${indent}${wsEnd}`;
- if (literal) {
- value = value.replace(/\n+/g, `$&${indent}`);
- return `${header}\n${indent}${wsStart}${value}${wsEnd}`;
- }
- value = value.replace(/\n+/g, '\n$&').replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded
- // ^ ind.line ^ empty ^ capture next empty lines only at end of indent
- .replace(/\n+/g, `$&${indent}`);
- const body = foldFlowLines(`${wsStart}${value}${wsEnd}`, indent, FOLD_BLOCK, strOptions.fold);
- return `${header}\n${indent}${body}`;
- }
- function plainString(item, ctx, onComment, onChompKeep) {
- const {
- comment,
- type,
- value
- } = item;
- const {
- actualString,
- implicitKey,
- indent,
- inFlow
- } = ctx;
- if (implicitKey && /[\n[\]{},]/.test(value) || inFlow && /[[\]{},]/.test(value)) {
- return doubleQuotedString(value, ctx);
- }
- if (!value || /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
- // not allowed:
- // - empty string, '-' or '?'
- // - start with an indicator character (except [?:-]) or /[?-] /
- // - '\n ', ': ' or ' \n' anywhere
- // - '#' not preceded by a non-space char
- // - end with ' ' or ':'
- return implicitKey || inFlow || value.indexOf('\n') === -1 ? value.indexOf('"') !== -1 && value.indexOf("'") === -1 ? singleQuotedString(value, ctx) : doubleQuotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep);
- }
- if (!implicitKey && !inFlow && type !== PlainValue.Type.PLAIN && value.indexOf('\n') !== -1) {
- // Where allowed & type not set explicitly, prefer block style for multiline strings
- return blockString(item, ctx, onComment, onChompKeep);
- }
- if (indent === '' && containsDocumentMarker(value)) {
- ctx.forceBlockIndent = true;
- return blockString(item, ctx, onComment, onChompKeep);
- }
- const str = value.replace(/\n+/g, `$&\n${indent}`); // Verify that output will be parsed as a string, as e.g. plain numbers and
- // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'),
- // and others in v1.1.
- if (actualString) {
- const {
- tags
- } = ctx.doc.schema;
- const resolved = resolveScalar(str, tags, tags.scalarFallback).value;
- if (typeof resolved !== 'string') return doubleQuotedString(value, ctx);
- }
- const body = implicitKey ? str : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx));
- if (comment && !inFlow && (body.indexOf('\n') !== -1 || comment.indexOf('\n') !== -1)) {
- if (onComment) onComment();
- return addCommentBefore(body, indent, comment);
- }
- return body;
- }
- function stringifyString(item, ctx, onComment, onChompKeep) {
- const {
- defaultType
- } = strOptions;
- const {
- implicitKey,
- inFlow
- } = ctx;
- let {
- type,
- value
- } = item;
- if (typeof value !== 'string') {
- value = String(value);
- item = Object.assign({}, item, {
- value
- });
- }
- const _stringify = _type => {
- switch (_type) {
- case PlainValue.Type.BLOCK_FOLDED:
- case PlainValue.Type.BLOCK_LITERAL:
- return blockString(item, ctx, onComment, onChompKeep);
- case PlainValue.Type.QUOTE_DOUBLE:
- return doubleQuotedString(value, ctx);
- case PlainValue.Type.QUOTE_SINGLE:
- return singleQuotedString(value, ctx);
- case PlainValue.Type.PLAIN:
- return plainString(item, ctx, onComment, onChompKeep);
- default:
- return null;
- }
- };
- if (type !== PlainValue.Type.QUOTE_DOUBLE && /[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(value)) {
- // force double quotes on control characters
- type = PlainValue.Type.QUOTE_DOUBLE;
- } else if ((implicitKey || inFlow) && (type === PlainValue.Type.BLOCK_FOLDED || type === PlainValue.Type.BLOCK_LITERAL)) {
- // should not happen; blocks are not valid inside flow containers
- type = PlainValue.Type.QUOTE_DOUBLE;
- }
- let res = _stringify(type);
- if (res === null) {
- res = _stringify(defaultType);
- if (res === null) throw new Error(`Unsupported default string type ${defaultType}`);
- }
- return res;
- }
- function stringifyNumber({
- format,
- minFractionDigits,
- tag,
- value
- }) {
- if (typeof value === 'bigint') return String(value);
- if (!isFinite(value)) return isNaN(value) ? '.nan' : value < 0 ? '-.inf' : '.inf';
- let n = JSON.stringify(value);
- if (!format && minFractionDigits && (!tag || tag === 'tag:yaml.org,2002:float') && /^\d/.test(n)) {
- let i = n.indexOf('.');
- if (i < 0) {
- i = n.length;
- n += '.';
- }
- let d = minFractionDigits - (n.length - i - 1);
- while (d-- > 0) n += '0';
- }
- return n;
- }
- function checkFlowCollectionEnd(errors, cst) {
- let char, name;
- switch (cst.type) {
- case PlainValue.Type.FLOW_MAP:
- char = '}';
- name = 'flow map';
- break;
- case PlainValue.Type.FLOW_SEQ:
- char = ']';
- name = 'flow sequence';
- break;
- default:
- errors.push(new PlainValue.YAMLSemanticError(cst, 'Not a flow collection!?'));
- return;
- }
- let lastItem;
- for (let i = cst.items.length - 1; i >= 0; --i) {
- const item = cst.items[i];
- if (!item || item.type !== PlainValue.Type.COMMENT) {
- lastItem = item;
- break;
- }
- }
- if (lastItem && lastItem.char !== char) {
- const msg = `Expected ${name} to end with ${char}`;
- let err;
- if (typeof lastItem.offset === 'number') {
- err = new PlainValue.YAMLSemanticError(cst, msg);
- err.offset = lastItem.offset + 1;
- } else {
- err = new PlainValue.YAMLSemanticError(lastItem, msg);
- if (lastItem.range && lastItem.range.end) err.offset = lastItem.range.end - lastItem.range.start;
- }
- errors.push(err);
- }
- }
- function checkFlowCommentSpace(errors, comment) {
- const prev = comment.context.src[comment.range.start - 1];
- if (prev !== '\n' && prev !== '\t' && prev !== ' ') {
- const msg = 'Comments must be separated from other tokens by white space characters';
- errors.push(new PlainValue.YAMLSemanticError(comment, msg));
- }
- }
- function getLongKeyError(source, key) {
- const sk = String(key);
- const k = sk.substr(0, 8) + '...' + sk.substr(-8);
- return new PlainValue.YAMLSemanticError(source, `The "${k}" key is too long`);
- }
- function resolveComments(collection, comments) {
- for (const {
- afterKey,
- before,
- comment
- } of comments) {
- let item = collection.items[before];
- if (!item) {
- if (comment !== undefined) {
- if (collection.comment) collection.comment += '\n' + comment;else collection.comment = comment;
- }
- } else {
- if (afterKey && item.value) item = item.value;
- if (comment === undefined) {
- if (afterKey || !item.commentBefore) item.spaceBefore = true;
- } else {
- if (item.commentBefore) item.commentBefore += '\n' + comment;else item.commentBefore = comment;
- }
- }
- }
- }
- // on error, will return { str: string, errors: Error[] }
- function resolveString(doc, node) {
- const res = node.strValue;
- if (!res) return '';
- if (typeof res === 'string') return res;
- res.errors.forEach(error => {
- if (!error.source) error.source = node;
- doc.errors.push(error);
- });
- return res.str;
- }
- function resolveTagHandle(doc, node) {
- const {
- handle,
- suffix
- } = node.tag;
- let prefix = doc.tagPrefixes.find(p => p.handle === handle);
- if (!prefix) {
- const dtp = doc.getDefaults().tagPrefixes;
- if (dtp) prefix = dtp.find(p => p.handle === handle);
- if (!prefix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag handle is non-default and was not declared.`);
- }
- if (!suffix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag has no suffix.`);
- if (handle === '!' && (doc.version || doc.options.version) === '1.0') {
- if (suffix[0] === '^') {
- doc.warnings.push(new PlainValue.YAMLWarning(node, 'YAML 1.0 ^ tag expansion is not supported'));
- return suffix;
- }
- if (/[:/]/.test(suffix)) {
- // word/foo -> tag:word.yaml.org,2002:foo
- const vocab = suffix.match(/^([a-z0-9-]+)\/(.*)/i);
- return vocab ? `tag:${vocab[1]}.yaml.org,2002:${vocab[2]}` : `tag:${suffix}`;
- }
- }
- return prefix.prefix + decodeURIComponent(suffix);
- }
- function resolveTagName(doc, node) {
- const {
- tag,
- type
- } = node;
- let nonSpecific = false;
- if (tag) {
- const {
- handle,
- suffix,
- verbatim
- } = tag;
- if (verbatim) {
- if (verbatim !== '!' && verbatim !== '!!') return verbatim;
- const msg = `Verbatim tags aren't resolved, so ${verbatim} is invalid.`;
- doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
- } else if (handle === '!' && !suffix) {
- nonSpecific = true;
- } else {
- try {
- return resolveTagHandle(doc, node);
- } catch (error) {
- doc.errors.push(error);
- }
- }
- }
- switch (type) {
- case PlainValue.Type.BLOCK_FOLDED:
- case PlainValue.Type.BLOCK_LITERAL:
- case PlainValue.Type.QUOTE_DOUBLE:
- case PlainValue.Type.QUOTE_SINGLE:
- return PlainValue.defaultTags.STR;
- case PlainValue.Type.FLOW_MAP:
- case PlainValue.Type.MAP:
- return PlainValue.defaultTags.MAP;
- case PlainValue.Type.FLOW_SEQ:
- case PlainValue.Type.SEQ:
- return PlainValue.defaultTags.SEQ;
- case PlainValue.Type.PLAIN:
- return nonSpecific ? PlainValue.defaultTags.STR : null;
- default:
- return null;
- }
- }
- function resolveByTagName(doc, node, tagName) {
- const {
- tags
- } = doc.schema;
- const matchWithTest = [];
- for (const tag of tags) {
- if (tag.tag === tagName) {
- if (tag.test) matchWithTest.push(tag);else {
- const res = tag.resolve(doc, node);
- return res instanceof Collection ? res : new Scalar(res);
- }
- }
- }
- const str = resolveString(doc, node);
- if (typeof str === 'string' && matchWithTest.length > 0) return resolveScalar(str, matchWithTest, tags.scalarFallback);
- return null;
- }
- function getFallbackTagName({
- type
- }) {
- switch (type) {
- case PlainValue.Type.FLOW_MAP:
- case PlainValue.Type.MAP:
- return PlainValue.defaultTags.MAP;
- case PlainValue.Type.FLOW_SEQ:
- case PlainValue.Type.SEQ:
- return PlainValue.defaultTags.SEQ;
- default:
- return PlainValue.defaultTags.STR;
- }
- }
- function resolveTag(doc, node, tagName) {
- try {
- const res = resolveByTagName(doc, node, tagName);
- if (res) {
- if (tagName && node.tag) res.tag = tagName;
- return res;
- }
- } catch (error) {
- /* istanbul ignore if */
- if (!error.source) error.source = node;
- doc.errors.push(error);
- return null;
- }
- try {
- const fallback = getFallbackTagName(node);
- if (!fallback) throw new Error(`The tag ${tagName} is unavailable`);
- const msg = `The tag ${tagName} is unavailable, falling back to ${fallback}`;
- doc.warnings.push(new PlainValue.YAMLWarning(node, msg));
- const res = resolveByTagName(doc, node, fallback);
- res.tag = tagName;
- return res;
- } catch (error) {
- const refError = new PlainValue.YAMLReferenceError(node, error.message);
- refError.stack = error.stack;
- doc.errors.push(refError);
- return null;
- }
- }
- const isCollectionItem = node => {
- if (!node) return false;
- const {
- type
- } = node;
- return type === PlainValue.Type.MAP_KEY || type === PlainValue.Type.MAP_VALUE || type === PlainValue.Type.SEQ_ITEM;
- };
- function resolveNodeProps(errors, node) {
- const comments = {
- before: [],
- after: []
- };
- let hasAnchor = false;
- let hasTag = false;
- const props = isCollectionItem(node.context.parent) ? node.context.parent.props.concat(node.props) : node.props;
- for (const {
- start,
- end
- } of props) {
- switch (node.context.src[start]) {
- case PlainValue.Char.COMMENT:
- {
- if (!node.commentHasRequiredWhitespace(start)) {
- const msg = 'Comments must be separated from other tokens by white space characters';
- errors.push(new PlainValue.YAMLSemanticError(node, msg));
- }
- const {
- header,
- valueRange
- } = node;
- const cc = valueRange && (start > valueRange.start || header && start > header.start) ? comments.after : comments.before;
- cc.push(node.context.src.slice(start + 1, end));
- break;
- }
- // Actual anchor & tag resolution is handled by schema, here we just complain
- case PlainValue.Char.ANCHOR:
- if (hasAnchor) {
- const msg = 'A node can have at most one anchor';
- errors.push(new PlainValue.YAMLSemanticError(node, msg));
- }
- hasAnchor = true;
- break;
- case PlainValue.Char.TAG:
- if (hasTag) {
- const msg = 'A node can have at most one tag';
- errors.push(new PlainValue.YAMLSemanticError(node, msg));
- }
- hasTag = true;
- break;
- }
- }
- return {
- comments,
- hasAnchor,
- hasTag
- };
- }
- function resolveNodeValue(doc, node) {
- const {
- anchors,
- errors,
- schema
- } = doc;
- if (node.type === PlainValue.Type.ALIAS) {
- const name = node.rawValue;
- const src = anchors.getNode(name);
- if (!src) {
- const msg = `Aliased anchor not found: ${name}`;
- errors.push(new PlainValue.YAMLReferenceError(node, msg));
- return null;
- } // Lazy resolution for circular references
- const res = new Alias(src);
- anchors._cstAliases.push(res);
- return res;
- }
- const tagName = resolveTagName(doc, node);
- if (tagName) return resolveTag(doc, node, tagName);
- if (node.type !== PlainValue.Type.PLAIN) {
- const msg = `Failed to resolve ${node.type} node here`;
- errors.push(new PlainValue.YAMLSyntaxError(node, msg));
- return null;
- }
- try {
- const str = resolveString(doc, node);
- return resolveScalar(str, schema.tags, schema.tags.scalarFallback);
- } catch (error) {
- if (!error.source) error.source = node;
- errors.push(error);
- return null;
- }
- } // sets node.resolved on success
- function resolveNode(doc, node) {
- if (!node) return null;
- if (node.error) doc.errors.push(node.error);
- const {
- comments,
- hasAnchor,
- hasTag
- } = resolveNodeProps(doc.errors, node);
- if (hasAnchor) {
- const {
- anchors
- } = doc;
- const name = node.anchor;
- const prev = anchors.getNode(name); // At this point, aliases for any preceding node with the same anchor
- // name have already been resolved, so it may safely be renamed.
- if (prev) anchors.map[anchors.newName(name)] = prev; // During parsing, we need to store the CST node in anchors.map as
- // anchors need to be available during resolution to allow for
- // circular references.
- anchors.map[name] = node;
- }
- if (node.type === PlainValue.Type.ALIAS && (hasAnchor || hasTag)) {
- const msg = 'An alias node must not specify any properties';
- doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
- }
- const res = resolveNodeValue(doc, node);
- if (res) {
- res.range = [node.range.start, node.range.end];
- if (doc.options.keepCstNodes) res.cstNode = node;
- if (doc.options.keepNodeTypes) res.type = node.type;
- const cb = comments.before.join('\n');
- if (cb) {
- res.commentBefore = res.commentBefore ? `${res.commentBefore}\n${cb}` : cb;
- }
- const ca = comments.after.join('\n');
- if (ca) res.comment = res.comment ? `${res.comment}\n${ca}` : ca;
- }
- return node.resolved = res;
- }
- function resolveMap(doc, cst) {
- if (cst.type !== PlainValue.Type.MAP && cst.type !== PlainValue.Type.FLOW_MAP) {
- const msg = `A ${cst.type} node cannot be resolved as a mapping`;
- doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
- return null;
- }
- const {
- comments,
- items
- } = cst.type === PlainValue.Type.FLOW_MAP ? resolveFlowMapItems(doc, cst) : resolveBlockMapItems(doc, cst);
- const map = new YAMLMap();
- map.items = items;
- resolveComments(map, comments);
- let hasCollectionKey = false;
- for (let i = 0; i < items.length; ++i) {
- const {
- key: iKey
- } = items[i];
- if (iKey instanceof Collection) hasCollectionKey = true;
- if (doc.schema.merge && iKey && iKey.value === MERGE_KEY) {
- items[i] = new Merge(items[i]);
- const sources = items[i].value.items;
- let error = null;
- sources.some(node => {
- if (node instanceof Alias) {
- // During parsing, alias sources are CST nodes; to account for
- // circular references their resolved values can't be used here.
- const {
- type
- } = node.source;
- if (type === PlainValue.Type.MAP || type === PlainValue.Type.FLOW_MAP) return false;
- return error = 'Merge nodes aliases can only point to maps';
- }
- return error = 'Merge nodes can only have Alias nodes as values';
- });
- if (error) doc.errors.push(new PlainValue.YAMLSemanticError(cst, error));
- } else {
- for (let j = i + 1; j < items.length; ++j) {
- const {
- key: jKey
- } = items[j];
- if (iKey === jKey || iKey && jKey && Object.prototype.hasOwnProperty.call(iKey, 'value') && iKey.value === jKey.value) {
- const msg = `Map keys must be unique; "${iKey}" is repeated`;
- doc.errors.push(new PlainValue.YAMLSemanticError(cst, msg));
- break;
- }
- }
- }
- }
- if (hasCollectionKey && !doc.options.mapAsMap) {
- const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
- doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
- }
- cst.resolved = map;
- return map;
- }
- const valueHasPairComment = ({
- context: {
- lineStart,
- node,
- src
- },
- props
- }) => {
- if (props.length === 0) return false;
- const {
- start
- } = props[0];
- if (node && start > node.valueRange.start) return false;
- if (src[start] !== PlainValue.Char.COMMENT) return false;
- for (let i = lineStart; i < start; ++i) if (src[i] === '\n') return false;
- return true;
- };
- function resolvePairComment(item, pair) {
- if (!valueHasPairComment(item)) return;
- const comment = item.getPropValue(0, PlainValue.Char.COMMENT, true);
- let found = false;
- const cb = pair.value.commentBefore;
- if (cb && cb.startsWith(comment)) {
- pair.value.commentBefore = cb.substr(comment.length + 1);
- found = true;
- } else {
- const cc = pair.value.comment;
- if (!item.node && cc && cc.startsWith(comment)) {
- pair.value.comment = cc.substr(comment.length + 1);
- found = true;
- }
- }
- if (found) pair.comment = comment;
- }
- function resolveBlockMapItems(doc, cst) {
- const comments = [];
- const items = [];
- let key = undefined;
- let keyStart = null;
- for (let i = 0; i < cst.items.length; ++i) {
- const item = cst.items[i];
- switch (item.type) {
- case PlainValue.Type.BLANK_LINE:
- comments.push({
- afterKey: !!key,
- before: items.length
- });
- break;
- case PlainValue.Type.COMMENT:
- comments.push({
- afterKey: !!key,
- before: items.length,
- comment: item.comment
- });
- break;
- case PlainValue.Type.MAP_KEY:
- if (key !== undefined) items.push(new Pair(key));
- if (item.error) doc.errors.push(item.error);
- key = resolveNode(doc, item.node);
- keyStart = null;
- break;
- case PlainValue.Type.MAP_VALUE:
- {
- if (key === undefined) key = null;
- if (item.error) doc.errors.push(item.error);
- if (!item.context.atLineStart && item.node && item.node.type === PlainValue.Type.MAP && !item.node.context.atLineStart) {
- const msg = 'Nested mappings are not allowed in compact mappings';
- doc.errors.push(new PlainValue.YAMLSemanticError(item.node, msg));
- }
- let valueNode = item.node;
- if (!valueNode && item.props.length > 0) {
- // Comments on an empty mapping value need to be preserved, so we
- // need to construct a minimal empty node here to use instead of the
- // missing `item.node`. -- eemeli/yaml#19
- valueNode = new PlainValue.PlainValue(PlainValue.Type.PLAIN, []);
- valueNode.context = {
- parent: item,
- src: item.context.src
- };
- const pos = item.range.start + 1;
- valueNode.range = {
- start: pos,
- end: pos
- };
- valueNode.valueRange = {
- start: pos,
- end: pos
- };
- if (typeof item.range.origStart === 'number') {
- const origPos = item.range.origStart + 1;
- valueNode.range.origStart = valueNode.range.origEnd = origPos;
- valueNode.valueRange.origStart = valueNode.valueRange.origEnd = origPos;
- }
- }
- const pair = new Pair(key, resolveNode(doc, valueNode));
- resolvePairComment(item, pair);
- items.push(pair);
- if (key && typeof keyStart === 'number') {
- if (item.range.start > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
- }
- key = undefined;
- keyStart = null;
- }
- break;
- default:
- if (key !== undefined) items.push(new Pair(key));
- key = resolveNode(doc, item);
- keyStart = item.range.start;
- if (item.error) doc.errors.push(item.error);
- next: for (let j = i + 1;; ++j) {
- const nextItem = cst.items[j];
- switch (nextItem && nextItem.type) {
- case PlainValue.Type.BLANK_LINE:
- case PlainValue.Type.COMMENT:
- continue next;
- case PlainValue.Type.MAP_VALUE:
- break next;
- default:
- {
- const msg = 'Implicit map keys need to be followed by map values';
- doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
- break next;
- }
- }
- }
- if (item.valueRangeContainsNewline) {
- const msg = 'Implicit map keys need to be on a single line';
- doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
- }
- }
- }
- if (key !== undefined) items.push(new Pair(key));
- return {
- comments,
- items
- };
- }
- function resolveFlowMapItems(doc, cst) {
- const comments = [];
- const items = [];
- let key = undefined;
- let explicitKey = false;
- let next = '{';
- for (let i = 0; i < cst.items.length; ++i) {
- const item = cst.items[i];
- if (typeof item.char === 'string') {
- const {
- char,
- offset
- } = item;
- if (char === '?' && key === undefined && !explicitKey) {
- explicitKey = true;
- next = ':';
- continue;
- }
- if (char === ':') {
- if (key === undefined) key = null;
- if (next === ':') {
- next = ',';
- continue;
- }
- } else {
- if (explicitKey) {
- if (key === undefined && char !== ',') key = null;
- explicitKey = false;
- }
- if (key !== undefined) {
- items.push(new Pair(key));
- key = undefined;
- if (char === ',') {
- next = ':';
- continue;
- }
- }
- }
- if (char === '}') {
- if (i === cst.items.length - 1) continue;
- } else if (char === next) {
- next = ':';
- continue;
- }
- const msg = `Flow map contains an unexpected ${char}`;
- const err = new PlainValue.YAMLSyntaxError(cst, msg);
- err.offset = offset;
- doc.errors.push(err);
- } else if (item.type === PlainValue.Type.BLANK_LINE) {
- comments.push({
- afterKey: !!key,
- before: items.length
- });
- } else if (item.type === PlainValue.Type.COMMENT) {
- checkFlowCommentSpace(doc.errors, item);
- comments.push({
- afterKey: !!key,
- before: items.length,
- comment: item.comment
- });
- } else if (key === undefined) {
- if (next === ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Separator , missing in flow map'));
- key = resolveNode(doc, item);
- } else {
- if (next !== ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Indicator : missing in flow map entry'));
- items.push(new Pair(key, resolveNode(doc, item)));
- key = undefined;
- explicitKey = false;
- }
- }
- checkFlowCollectionEnd(doc.errors, cst);
- if (key !== undefined) items.push(new Pair(key));
- return {
- comments,
- items
- };
- }
- function resolveSeq(doc, cst) {
- if (cst.type !== PlainValue.Type.SEQ && cst.type !== PlainValue.Type.FLOW_SEQ) {
- const msg = `A ${cst.type} node cannot be resolved as a sequence`;
- doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
- return null;
- }
- const {
- comments,
- items
- } = cst.type === PlainValue.Type.FLOW_SEQ ? resolveFlowSeqItems(doc, cst) : resolveBlockSeqItems(doc, cst);
- const seq = new YAMLSeq();
- seq.items = items;
- resolveComments(seq, comments);
- if (!doc.options.mapAsMap && items.some(it => it instanceof Pair && it.key instanceof Collection)) {
- const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
- doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
- }
- cst.resolved = seq;
- return seq;
- }
- function resolveBlockSeqItems(doc, cst) {
- const comments = [];
- const items = [];
- for (let i = 0; i < cst.items.length; ++i) {
- const item = cst.items[i];
- switch (item.type) {
- case PlainValue.Type.BLANK_LINE:
- comments.push({
- before: items.length
- });
- break;
- case PlainValue.Type.COMMENT:
- comments.push({
- comment: item.comment,
- before: items.length
- });
- break;
- case PlainValue.Type.SEQ_ITEM:
- if (item.error) doc.errors.push(item.error);
- items.push(resolveNode(doc, item.node));
- if (item.hasProps) {
- const msg = 'Sequence items cannot have tags or anchors before the - indicator';
- doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
- }
- break;
- default:
- if (item.error) doc.errors.push(item.error);
- doc.errors.push(new PlainValue.YAMLSyntaxError(item, `Unexpected ${item.type} node in sequence`));
- }
- }
- return {
- comments,
- items
- };
- }
- function resolveFlowSeqItems(doc, cst) {
- const comments = [];
- const items = [];
- let explicitKey = false;
- let key = undefined;
- let keyStart = null;
- let next = '[';
- let prevItem = null;
- for (let i = 0; i < cst.items.length; ++i) {
- const item = cst.items[i];
- if (typeof item.char === 'string') {
- const {
- char,
- offset
- } = item;
- if (char !== ':' && (explicitKey || key !== undefined)) {
- if (explicitKey && key === undefined) key = next ? items.pop() : null;
- items.push(new Pair(key));
- explicitKey = false;
- key = undefined;
- keyStart = null;
- }
- if (char === next) {
- next = null;
- } else if (!next && char === '?') {
- explicitKey = true;
- } else if (next !== '[' && char === ':' && key === undefined) {
- if (next === ',') {
- key = items.pop();
- if (key instanceof Pair) {
- const msg = 'Chaining flow sequence pairs is invalid';
- const err = new PlainValue.YAMLSemanticError(cst, msg);
- err.offset = offset;
- doc.errors.push(err);
- }
- if (!explicitKey && typeof keyStart === 'number') {
- const keyEnd = item.range ? item.range.start : item.offset;
- if (keyEnd > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
- const {
- src
- } = prevItem.context;
- for (let i = keyStart; i < keyEnd; ++i) if (src[i] === '\n') {
- const msg = 'Implicit keys of flow sequence pairs need to be on a single line';
- doc.errors.push(new PlainValue.YAMLSemanticError(prevItem, msg));
- break;
- }
- }
- } else {
- key = null;
- }
- keyStart = null;
- explicitKey = false;
- next = null;
- } else if (next === '[' || char !== ']' || i < cst.items.length - 1) {
- const msg = `Flow sequence contains an unexpected ${char}`;
- const err = new PlainValue.YAMLSyntaxError(cst, msg);
- err.offset = offset;
- doc.errors.push(err);
- }
- } else if (item.type === PlainValue.Type.BLANK_LINE) {
- comments.push({
- before: items.length
- });
- } else if (item.type === PlainValue.Type.COMMENT) {
- checkFlowCommentSpace(doc.errors, item);
- comments.push({
- comment: item.comment,
- before: items.length
- });
- } else {
- if (next) {
- const msg = `Expected a ${next} in flow sequence`;
- doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
- }
- const value = resolveNode(doc, item);
- if (key === undefined) {
- items.push(value);
- prevItem = item;
- } else {
- items.push(new Pair(key, value));
- key = undefined;
- }
- keyStart = item.range.start;
- next = ',';
- }
- }
- checkFlowCollectionEnd(doc.errors, cst);
- if (key !== undefined) items.push(new Pair(key));
- return {
- comments,
- items
- };
- }
- exports.Alias = Alias;
- exports.Collection = Collection;
- exports.Merge = Merge;
- exports.Node = Node;
- exports.Pair = Pair;
- exports.Scalar = Scalar;
- exports.YAMLMap = YAMLMap;
- exports.YAMLSeq = YAMLSeq;
- exports.addComment = addComment;
- exports.binaryOptions = binaryOptions;
- exports.boolOptions = boolOptions;
- exports.findPair = findPair;
- exports.intOptions = intOptions;
- exports.isEmptyPath = isEmptyPath;
- exports.nullOptions = nullOptions;
- exports.resolveMap = resolveMap;
- exports.resolveNode = resolveNode;
- exports.resolveSeq = resolveSeq;
- exports.resolveString = resolveString;
- exports.strOptions = strOptions;
- exports.stringifyNumber = stringifyNumber;
- exports.stringifyString = stringifyString;
- exports.toJSON = toJSON;
|