diff --git a/dist/index.js b/dist/index.js index 3d92df9..6665c7b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6355,6 +6355,7 @@ function composeDoc(options, directives, { offset, start, value, end }, onError) next: value ?? end?.[0], offset, onError, + parentIndent: 0, startOnNewline: true }); if (props.found) { @@ -6497,7 +6498,7 @@ var resolveFlowScalar = __nccwpck_require__(7578); function composeScalar(ctx, token, tagToken, onError) { const { value, type, comment, range } = token.type === 'block-scalar' - ? resolveBlockScalar.resolveBlockScalar(token, ctx.options.strict, onError) + ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError); const tagName = tagToken ? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg)) @@ -6832,6 +6833,7 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, ta next: key ?? sep?.[0], offset, onError, + parentIndent: bm.indent, startOnNewline: true }); const implicitKey = !keyProps.found; @@ -6874,6 +6876,7 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, ta next: value, offset: keyNode.range[2], onError, + parentIndent: bm.indent, startOnNewline: !key || key.type === 'block-scalar' }); offset = valueProps.end; @@ -6932,9 +6935,9 @@ exports.resolveBlockMap = resolveBlockMap; var Scalar = __nccwpck_require__(9338); -function resolveBlockScalar(scalar, strict, onError) { +function resolveBlockScalar(ctx, scalar, onError) { const start = scalar.offset; - const header = parseBlockScalarHeader(scalar, strict, onError); + const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); if (!header) return { value: '', type: null, comment: '', range: [start, start, start] }; const type = header.mode === '>' ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL; @@ -6976,6 +6979,10 @@ function resolveBlockScalar(scalar, strict, onError) { if (header.indent === 0) trimIndent = indent.length; contentStart = i; + if (trimIndent === 0 && !ctx.atRoot) { + const message = 'Block scalar values in collections must be indented'; + onError(offset, 'BAD_INDENT', message); + } break; } offset += indent.length + content.length + 1; @@ -7151,6 +7158,7 @@ function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, ta next: value, offset, onError, + parentIndent: bs.indent, startOnNewline: true }); if (!props.found) { @@ -7267,6 +7275,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr next: key ?? sep?.[0], offset, onError, + parentIndent: fc.indent, startOnNewline: false }); if (!props.found) { @@ -7348,6 +7357,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr next: value, offset: keyNode.range[2], onError, + parentIndent: fc.indent, startOnNewline: false }); if (valueProps.found) { @@ -7637,19 +7647,19 @@ function foldNewline(source, offset) { return { fold, offset }; } const escapeCodes = { - '0': '\0', - a: '\x07', - b: '\b', - e: '\x1b', - f: '\f', - n: '\n', - r: '\r', - t: '\t', - v: '\v', - N: '\u0085', - _: '\u00a0', - L: '\u2028', - P: '\u2029', + '0': '\0', // null character + a: '\x07', // bell character + b: '\b', // backspace + e: '\x1b', // escape character + f: '\f', // form feed + n: '\n', // line feed + r: '\r', // carriage return + t: '\t', // horizontal tab + v: '\v', // vertical tab + N: '\u0085', // Unicode next line + _: '\u00a0', // Unicode non-breaking space + L: '\u2028', // Unicode line separator + P: '\u2029', // Unicode paragraph separator ' ': ' ', '"': '"', '/': '/', @@ -7679,7 +7689,7 @@ exports.resolveFlowScalar = resolveFlowScalar; "use strict"; -function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnNewline }) { +function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { let spaceBefore = false; let atNewline = startOnNewline; let hasSpace = startOnNewline; @@ -7688,6 +7698,7 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN let hasNewline = false; let hasNewlineAfterProp = false; let reqSpace = false; + let tab = null; let anchor = null; let tag = null; let comma = null; @@ -7701,16 +7712,22 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN onError(token.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); reqSpace = false; } + if (tab) { + if (atNewline && token.type !== 'comment' && token.type !== 'newline') { + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + } + tab = null; + } switch (token.type) { case 'space': // At the doc level, tabs at line start may be parsed // as leading white space rather than indentation. // In a flow collection, only the parser handles indent. if (!flow && - atNewline && - indicator !== 'doc-start' && - token.source[0] === '\t') - onError(token, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); + (indicator !== 'doc-start' || next?.type !== 'flow-collection') && + token.source.includes('\t')) { + tab = token; + } hasSpace = true; break; case 'comment': { @@ -7770,7 +7787,8 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN if (found) onError(token, 'UNEXPECTED_TOKEN', `Unexpected ${token.source} in ${flow ?? 'collection'}`); found = token; - atNewline = false; + atNewline = + indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'; hasSpace = false; break; case 'comma': @@ -7796,8 +7814,14 @@ function resolveProps(tokens, { flow, indicator, next, offset, onError, startOnN next.type !== 'space' && next.type !== 'newline' && next.type !== 'comma' && - (next.type !== 'scalar' || next.source !== '')) + (next.type !== 'scalar' || next.source !== '')) { onError(next.offset, 'MISSING_CHAR', 'Tags and anchors must be separated from the next token by white space'); + } + if (tab && + ((atNewline && tab.indent <= parentIndent) || + next?.type === 'block-map' || + next?.type === 'block-seq')) + onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation'); return { comma, found, @@ -9810,7 +9834,7 @@ function resolveAsScalar(token, strict = true, onError) { case 'double-quoted-scalar': return resolveFlowScalar.resolveFlowScalar(token, strict, _onError); case 'block-scalar': - return resolveBlockScalar.resolveBlockScalar(token, strict, _onError); + return resolveBlockScalar.resolveBlockScalar({ options: { strict } }, token, _onError); } } return null; @@ -10395,11 +10419,11 @@ function isEmpty(ch) { return false; } } -const hexDigits = '0123456789ABCDEFabcdef'.split(''); -const tagChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()".split(''); -const invalidFlowScalarChars = ',[]{}'.split(''); -const invalidAnchorChars = ' ,[]{}\n\r\t'.split(''); -const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.includes(ch); +const hexDigits = new Set('0123456789ABCDEFabcdef'); +const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); +const flowIndicatorChars = new Set(',[]{}'); +const invalidAnchorChars = new Set(' ,[]{}\n\r\t'); +const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); /** * Splits an input string into lexical tokens, i.e. smaller strings that are * easily identifiable by `tokens.tokenType()`. @@ -10465,6 +10489,8 @@ class Lexer { */ *lex(source, incomplete = false) { if (source) { + if (typeof source !== 'string') + throw TypeError('source is not a string'); this.buffer = this.buffer ? this.buffer + source : source; this.lineEndPos = null; } @@ -10564,11 +10590,16 @@ class Lexer { } if (line[0] === '%') { let dirEnd = line.length; - const cs = line.indexOf('#'); - if (cs !== -1) { + let cs = line.indexOf('#'); + while (cs !== -1) { const ch = line[cs - 1]; - if (ch === ' ' || ch === '\t') + if (ch === ' ' || ch === '\t') { dirEnd = cs - 1; + break; + } + else { + cs = line.indexOf('#', cs + 1); + } } while (true) { const ch = line[dirEnd - 1]; @@ -10834,8 +10865,10 @@ class Lexer { if (indent >= this.indentNext) { if (this.blockScalarIndent === -1) this.indentNext = indent; - else - this.indentNext += this.blockScalarIndent; + else { + this.indentNext = + this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); + } do { const cs = this.continueScalar(nl + 1); if (cs === -1) @@ -10848,14 +10881,25 @@ class Lexer { nl = this.buffer.length; } } - if (!this.blockScalarKeep) { + // Trailing insufficiently indented tabs are invalid. + // To catch that during parsing, we include them in the block scalar value. + let i = nl + 1; + ch = this.buffer[i]; + while (ch === ' ') + ch = this.buffer[++i]; + if (ch === '\t') { + while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n') + ch = this.buffer[++i]; + nl = i - 1; + } + else if (!this.blockScalarKeep) { do { let i = nl - 1; let ch = this.buffer[i]; if (ch === '\r') ch = this.buffer[--i]; const lastChar = i; // Drop the line if last char not more indented - while (ch === ' ' || ch === '\t') + while (ch === ' ') ch = this.buffer[--i]; if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) nl = i; @@ -10875,7 +10919,7 @@ class Lexer { while ((ch = this.buffer[++i])) { if (ch === ':') { const next = this.buffer[i + 1]; - if (isEmpty(next) || (inFlow && next === ',')) + if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next))) break; end = i; } @@ -10890,7 +10934,7 @@ class Lexer { else end = i; } - if (next === '#' || (inFlow && invalidFlowScalarChars.includes(next))) + if (next === '#' || (inFlow && flowIndicatorChars.has(next))) break; if (ch === '\n') { const cs = this.continueScalar(i + 1); @@ -10900,7 +10944,7 @@ class Lexer { } } else { - if (inFlow && invalidFlowScalarChars.includes(ch)) + if (inFlow && flowIndicatorChars.has(ch)) break; end = i; } @@ -10945,7 +10989,7 @@ class Lexer { case ':': { const inFlow = this.flowLevel > 0; const ch1 = this.charAt(1); - if (isEmpty(ch1) || (inFlow && invalidFlowScalarChars.includes(ch1))) { + if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) { if (!inFlow) this.indentNext = this.indentValue + 1; else if (this.flowKey) @@ -10970,11 +11014,11 @@ class Lexer { let i = this.pos + 1; let ch = this.buffer[i]; while (ch) { - if (tagChars.includes(ch)) + if (tagChars.has(ch)) ch = this.buffer[++i]; else if (ch === '%' && - hexDigits.includes(this.buffer[i + 1]) && - hexDigits.includes(this.buffer[i + 2])) { + hexDigits.has(this.buffer[i + 1]) && + hexDigits.has(this.buffer[i + 2])) { ch = this.buffer[(i += 3)]; } else @@ -11382,7 +11426,7 @@ class Parser { } else { Object.assign(it, { key: token, sep: [] }); - this.onKeyLine = !includesToken(it.start, 'explicit-key-ind'); + this.onKeyLine = !it.explicitKey; return; } break; @@ -11591,7 +11635,10 @@ class Parser { return; } if (this.indent >= map.indent) { - const atNextItem = !this.onKeyLine && this.indent === map.indent && it.sep; + const atMapIndent = !this.onKeyLine && this.indent === map.indent; + const atNextItem = atMapIndent && + (it.sep || it.explicitKey) && + this.type !== 'seq-item-ind'; // For empty nodes, assign newline-separated not indented empty tokens to following node let start = []; if (atNextItem && it.sep && !it.value) { @@ -11631,25 +11678,26 @@ class Parser { } return; case 'explicit-key-ind': - if (!it.sep && !includesToken(it.start, 'explicit-key-ind')) { + if (!it.sep && !it.explicitKey) { it.start.push(this.sourceToken); + it.explicitKey = true; } else if (atNextItem || it.value) { start.push(this.sourceToken); - map.items.push({ start }); + map.items.push({ start, explicitKey: true }); } else { this.stack.push({ type: 'block-map', offset: this.offset, indent: this.indent, - items: [{ start: [this.sourceToken] }] + items: [{ start: [this.sourceToken], explicitKey: true }] }); } this.onKeyLine = true; return; case 'map-value-ind': - if (includesToken(it.start, 'explicit-key-ind')) { + if (it.explicitKey) { if (!it.sep) { if (includesToken(it.start, 'newline')) { Object.assign(it, { key: null, sep: [this.sourceToken] }); @@ -11740,9 +11788,7 @@ class Parser { default: { const bv = this.startBlockValue(map); if (bv) { - if (atNextItem && - bv.type !== 'block-seq' && - includesToken(it.start, 'explicit-key-ind')) { + if (atMapIndent && bv.type !== 'block-seq') { map.items.push({ start }); } this.stack.push(bv); @@ -11963,7 +12009,7 @@ class Parser { type: 'block-map', offset: this.offset, indent: this.indent, - items: [{ start }] + items: [{ start, explicitKey: true }] }; } case 'map-value-ind': { @@ -12335,7 +12381,7 @@ const floatNaN = { identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^(?:[-+]?\.(?:inf|Inf|INF|nan|NaN|NAN))$/, + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, resolve: str => str.slice(-3).toLowerCase() === 'nan' ? NaN : str[0] === '-' @@ -12636,7 +12682,7 @@ var Scalar = __nccwpck_require__(9338); var stringifyString = __nccwpck_require__(6226); const binary = { - identify: value => value instanceof Uint8Array, + identify: value => value instanceof Uint8Array, // Buffer inherits from Uint8Array default: false, tag: 'tag:yaml.org,2002:binary', /** @@ -12728,7 +12774,7 @@ const falseTag = { identify: value => value === false, default: true, tag: 'tag:yaml.org,2002:bool', - test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/i, + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, resolve: () => new Scalar.Scalar(false), stringify: boolStringify }; @@ -12752,7 +12798,7 @@ const floatNaN = { identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^[-+]?\.(?:inf|Inf|INF|nan|NaN|NAN)$/, + test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, resolve: (str) => str.slice(-3).toLowerCase() === 'nan' ? NaN : str[0] === '-' @@ -13356,7 +13402,7 @@ function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = let escStart = -1; let escEnd = -1; if (mode === FOLD_BLOCK) { - i = consumeMoreIndentedLines(text, i); + i = consumeMoreIndentedLines(text, i, indent.length); if (i !== -1) end = i + endStep; } @@ -13380,8 +13426,8 @@ function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = } if (ch === '\n') { if (mode === FOLD_BLOCK) - i = consumeMoreIndentedLines(text, i); - end = i + endStep; + i = consumeMoreIndentedLines(text, i, indent.length); + end = i + indent.length + endStep; split = undefined; } else { @@ -13449,15 +13495,24 @@ function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = * Presumes `i + 1` is at the start of a line * @returns index of last newline in more-indented block */ -function consumeMoreIndentedLines(text, i) { - let ch = text[i + 1]; +function consumeMoreIndentedLines(text, i, indent) { + let end = i; + let start = i + 1; + let ch = text[start]; while (ch === ' ' || ch === '\t') { - do { - ch = text[(i += 1)]; - } while (ch && ch !== '\n'); - ch = text[i + 1]; + if (i < start + indent) { + ch = text[++i]; + } + else { + do { + ch = text[++i]; + } while (ch && ch !== '\n'); + end = i; + start = i + 1; + ch = text[start]; + } } - return i; + return end; } exports.FOLD_BLOCK = FOLD_BLOCK; @@ -13609,7 +13664,6 @@ exports.stringify = stringify; "use strict"; -var Collection = __nccwpck_require__(3466); var identity = __nccwpck_require__(5589); var stringify = __nccwpck_require__(8409); var stringifyComment = __nccwpck_require__(5182); @@ -13670,7 +13724,7 @@ function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, fl onChompKeep(); return str; } -function stringifyFlowCollection({ comment, items }, ctx, { flowChars, itemIndent, onComment }) { +function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; itemIndent += indentStep; const itemCtx = Object.assign({}, ctx, { @@ -13723,32 +13777,25 @@ function stringifyFlowCollection({ comment, items }, ctx, { flowChars, itemInden lines.push(str); linesAtValue = lines.length; } - let str; const { start, end } = flowChars; if (lines.length === 0) { - str = start + end; + return start + end; } else { if (!reqNewline) { const len = lines.reduce((sum, line) => sum + line.length + 2, 2); - reqNewline = len > Collection.Collection.maxFlowStringSingleLineLength; + reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; } if (reqNewline) { - str = start; + let str = start; for (const line of lines) str += line ? `\n${indentStep}${indent}${line}` : '\n'; - str += `\n${indent}${end}`; + return `${str}\n${indent}${end}`; } else { - str = `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; + return `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`; } } - if (comment) { - str += stringifyComment.lineComment(str, indent, commentString(comment)); - if (onComment) - onComment(); - } - return str; } function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { if (comment && chompKeep) @@ -13943,7 +13990,7 @@ function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { if (keyComment) { throw new Error('With simple keys, key nodes cannot have comments'); } - if (identity.isCollection(key)) { + if (identity.isCollection(key) || (!identity.isNode(key) && typeof key === 'object')) { const msg = 'With simple keys, collection cannot be used as a key value'; throw new Error(msg); } diff --git a/package-lock.json b/package-lock.json index 3348e2d..1f0c1de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "escape-string-regexp": "^4.0.0", "fast-xml-parser": "^4.4.0", "properties-parser": "^0.6.0", - "yaml": "^2.3.4" + "yaml": "^2.4.5" }, "devDependencies": { "@types/jest": "^29.5.12", @@ -7016,9 +7016,12 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index 9442baf..e715e40 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "escape-string-regexp": "^4.0.0", "fast-xml-parser": "^4.4.0", "properties-parser": "^0.6.0", - "yaml": "^2.3.4" + "yaml": "^2.4.5" }, "devDependencies": { "@types/jest": "^29.5.12",