199 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| 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 };
 |