123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import { Scalar } from '../nodes/Scalar.js';
- function resolveBlockScalar(ctx, scalar, onError) {
- const start = scalar.offset;
- const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError);
- if (!header)
- return { value: '', type: null, comment: '', range: [start, start, start] };
- const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL;
- const lines = scalar.source ? splitLines(scalar.source) : [];
- // determine the end of content & start of chomping
- let chompStart = lines.length;
- for (let i = lines.length - 1; i >= 0; --i) {
- const content = lines[i][1];
- if (content === '' || content === '\r')
- chompStart = i;
- else
- break;
- }
- // shortcut for empty contents
- if (chompStart === 0) {
- const value = header.chomp === '+' && lines.length > 0
- ? '\n'.repeat(Math.max(1, lines.length - 1))
- : '';
- let end = start + header.length;
- if (scalar.source)
- end += scalar.source.length;
- return { value, type, comment: header.comment, range: [start, end, end] };
- }
- // find the indentation level to trim from start
- let trimIndent = scalar.indent + header.indent;
- let offset = scalar.offset + header.length;
- let contentStart = 0;
- for (let i = 0; i < chompStart; ++i) {
- const [indent, content] = lines[i];
- if (content === '' || content === '\r') {
- if (header.indent === 0 && indent.length > trimIndent)
- trimIndent = indent.length;
- }
- else {
- if (indent.length < trimIndent) {
- const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator';
- onError(offset + indent.length, 'MISSING_CHAR', message);
- }
- 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;
- }
- // include trailing more-indented empty lines in content
- for (let i = lines.length - 1; i >= chompStart; --i) {
- if (lines[i][0].length > trimIndent)
- chompStart = i + 1;
- }
- let value = '';
- let sep = '';
- let prevMoreIndented = false;
- // leading whitespace is kept intact
- for (let i = 0; i < contentStart; ++i)
- value += lines[i][0].slice(trimIndent) + '\n';
- for (let i = contentStart; i < chompStart; ++i) {
- let [indent, content] = lines[i];
- offset += indent.length + content.length + 1;
- const crlf = content[content.length - 1] === '\r';
- if (crlf)
- content = content.slice(0, -1);
- /* istanbul ignore if already caught in lexer */
- if (content && indent.length < trimIndent) {
- const src = header.indent
- ? 'explicit indentation indicator'
- : 'first line';
- const message = `Block scalar lines must not be less indented than their ${src}`;
- onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message);
- indent = '';
- }
- if (type === Scalar.BLOCK_LITERAL) {
- value += sep + indent.slice(trimIndent) + content;
- sep = '\n';
- }
- else if (indent.length > trimIndent || content[0] === '\t') {
- // more-indented content within a folded block
- if (sep === ' ')
- sep = '\n';
- else if (!prevMoreIndented && sep === '\n')
- sep = '\n\n';
- value += sep + indent.slice(trimIndent) + content;
- sep = '\n';
- prevMoreIndented = true;
- }
- else if (content === '') {
- // empty line
- if (sep === '\n')
- value += '\n';
- else
- sep = '\n';
- }
- else {
- value += sep + content;
- sep = ' ';
- prevMoreIndented = false;
- }
- }
- switch (header.chomp) {
- case '-':
- break;
- case '+':
- for (let i = chompStart; i < lines.length; ++i)
- value += '\n' + lines[i][0].slice(trimIndent);
- if (value[value.length - 1] !== '\n')
- value += '\n';
- break;
- default:
- value += '\n';
- }
- const end = start + header.length + scalar.source.length;
- return { value, type, comment: header.comment, range: [start, end, end] };
- }
- function parseBlockScalarHeader({ offset, props }, strict, onError) {
- /* istanbul ignore if should not happen */
- if (props[0].type !== 'block-scalar-header') {
- onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found');
- return null;
- }
- const { source } = props[0];
- const mode = source[0];
- let indent = 0;
- let chomp = '';
- let error = -1;
- for (let i = 1; i < source.length; ++i) {
- const ch = source[i];
- if (!chomp && (ch === '-' || ch === '+'))
- chomp = ch;
- else {
- const n = Number(ch);
- if (!indent && n)
- indent = n;
- else if (error === -1)
- error = offset + i;
- }
- }
- if (error !== -1)
- onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`);
- let hasSpace = false;
- let comment = '';
- let length = source.length;
- for (let i = 1; i < props.length; ++i) {
- const token = props[i];
- switch (token.type) {
- case 'space':
- hasSpace = true;
- // fallthrough
- case 'newline':
- length += token.source.length;
- break;
- case 'comment':
- if (strict && !hasSpace) {
- const message = 'Comments must be separated from other tokens by white space characters';
- onError(token, 'MISSING_CHAR', message);
- }
- length += token.source.length;
- comment = token.source.substring(1);
- break;
- case 'error':
- onError(token, 'UNEXPECTED_TOKEN', token.message);
- length += token.source.length;
- break;
- /* istanbul ignore next should not happen */
- default: {
- const message = `Unexpected token in block scalar header: ${token.type}`;
- onError(token, 'UNEXPECTED_TOKEN', message);
- const ts = token.source;
- if (ts && typeof ts === 'string')
- length += ts.length;
- }
- }
- }
- return { mode, indent, chomp, comment, length };
- }
- /** @returns Array of lines split up as `[indent, content]` */
- function splitLines(source) {
- const split = source.split(/\n( *)/);
- const first = split[0];
- const m = first.match(/^( *)/);
- const line0 = m?.[1]
- ? [m[1], first.slice(m[1].length)]
- : ['', first];
- const lines = [line0];
- for (let i = 1; i < split.length; i += 2)
- lines.push([split[i], split[i + 1]]);
- return lines;
- }
- export { resolveBlockScalar };
|