[d24f17c] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | var Scalar = require('../nodes/Scalar.js');
|
---|
| 4 |
|
---|
| 5 | function resolveBlockScalar(scalar, strict, onError) {
|
---|
| 6 | const start = scalar.offset;
|
---|
| 7 | const header = parseBlockScalarHeader(scalar, strict, onError);
|
---|
| 8 | if (!header)
|
---|
| 9 | return { value: '', type: null, comment: '', range: [start, start, start] };
|
---|
| 10 | const type = header.mode === '>' ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL;
|
---|
| 11 | const lines = scalar.source ? splitLines(scalar.source) : [];
|
---|
| 12 | // determine the end of content & start of chomping
|
---|
| 13 | let chompStart = lines.length;
|
---|
| 14 | for (let i = lines.length - 1; i >= 0; --i) {
|
---|
| 15 | const content = lines[i][1];
|
---|
| 16 | if (content === '' || content === '\r')
|
---|
| 17 | chompStart = i;
|
---|
| 18 | else
|
---|
| 19 | break;
|
---|
| 20 | }
|
---|
| 21 | // shortcut for empty contents
|
---|
| 22 | if (chompStart === 0) {
|
---|
| 23 | const value = header.chomp === '+' && lines.length > 0
|
---|
| 24 | ? '\n'.repeat(Math.max(1, lines.length - 1))
|
---|
| 25 | : '';
|
---|
| 26 | let end = start + header.length;
|
---|
| 27 | if (scalar.source)
|
---|
| 28 | end += scalar.source.length;
|
---|
| 29 | return { value, type, comment: header.comment, range: [start, end, end] };
|
---|
| 30 | }
|
---|
| 31 | // find the indentation level to trim from start
|
---|
| 32 | let trimIndent = scalar.indent + header.indent;
|
---|
| 33 | let offset = scalar.offset + header.length;
|
---|
| 34 | let contentStart = 0;
|
---|
| 35 | for (let i = 0; i < chompStart; ++i) {
|
---|
| 36 | const [indent, content] = lines[i];
|
---|
| 37 | if (content === '' || content === '\r') {
|
---|
| 38 | if (header.indent === 0 && indent.length > trimIndent)
|
---|
| 39 | trimIndent = indent.length;
|
---|
| 40 | }
|
---|
| 41 | else {
|
---|
| 42 | if (indent.length < trimIndent) {
|
---|
| 43 | const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator';
|
---|
| 44 | onError(offset + indent.length, 'MISSING_CHAR', message);
|
---|
| 45 | }
|
---|
| 46 | if (header.indent === 0)
|
---|
| 47 | trimIndent = indent.length;
|
---|
| 48 | contentStart = i;
|
---|
| 49 | break;
|
---|
| 50 | }
|
---|
| 51 | offset += indent.length + content.length + 1;
|
---|
| 52 | }
|
---|
| 53 | // include trailing more-indented empty lines in content
|
---|
| 54 | for (let i = lines.length - 1; i >= chompStart; --i) {
|
---|
| 55 | if (lines[i][0].length > trimIndent)
|
---|
| 56 | chompStart = i + 1;
|
---|
| 57 | }
|
---|
| 58 | let value = '';
|
---|
| 59 | let sep = '';
|
---|
| 60 | let prevMoreIndented = false;
|
---|
| 61 | // leading whitespace is kept intact
|
---|
| 62 | for (let i = 0; i < contentStart; ++i)
|
---|
| 63 | value += lines[i][0].slice(trimIndent) + '\n';
|
---|
| 64 | for (let i = contentStart; i < chompStart; ++i) {
|
---|
| 65 | let [indent, content] = lines[i];
|
---|
| 66 | offset += indent.length + content.length + 1;
|
---|
| 67 | const crlf = content[content.length - 1] === '\r';
|
---|
| 68 | if (crlf)
|
---|
| 69 | content = content.slice(0, -1);
|
---|
| 70 | /* istanbul ignore if already caught in lexer */
|
---|
| 71 | if (content && indent.length < trimIndent) {
|
---|
| 72 | const src = header.indent
|
---|
| 73 | ? 'explicit indentation indicator'
|
---|
| 74 | : 'first line';
|
---|
| 75 | const message = `Block scalar lines must not be less indented than their ${src}`;
|
---|
| 76 | onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message);
|
---|
| 77 | indent = '';
|
---|
| 78 | }
|
---|
| 79 | if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
---|
| 80 | value += sep + indent.slice(trimIndent) + content;
|
---|
| 81 | sep = '\n';
|
---|
| 82 | }
|
---|
| 83 | else if (indent.length > trimIndent || content[0] === '\t') {
|
---|
| 84 | // more-indented content within a folded block
|
---|
| 85 | if (sep === ' ')
|
---|
| 86 | sep = '\n';
|
---|
| 87 | else if (!prevMoreIndented && sep === '\n')
|
---|
| 88 | sep = '\n\n';
|
---|
| 89 | value += sep + indent.slice(trimIndent) + content;
|
---|
| 90 | sep = '\n';
|
---|
| 91 | prevMoreIndented = true;
|
---|
| 92 | }
|
---|
| 93 | else if (content === '') {
|
---|
| 94 | // empty line
|
---|
| 95 | if (sep === '\n')
|
---|
| 96 | value += '\n';
|
---|
| 97 | else
|
---|
| 98 | sep = '\n';
|
---|
| 99 | }
|
---|
| 100 | else {
|
---|
| 101 | value += sep + content;
|
---|
| 102 | sep = ' ';
|
---|
| 103 | prevMoreIndented = false;
|
---|
| 104 | }
|
---|
| 105 | }
|
---|
| 106 | switch (header.chomp) {
|
---|
| 107 | case '-':
|
---|
| 108 | break;
|
---|
| 109 | case '+':
|
---|
| 110 | for (let i = chompStart; i < lines.length; ++i)
|
---|
| 111 | value += '\n' + lines[i][0].slice(trimIndent);
|
---|
| 112 | if (value[value.length - 1] !== '\n')
|
---|
| 113 | value += '\n';
|
---|
| 114 | break;
|
---|
| 115 | default:
|
---|
| 116 | value += '\n';
|
---|
| 117 | }
|
---|
| 118 | const end = start + header.length + scalar.source.length;
|
---|
| 119 | return { value, type, comment: header.comment, range: [start, end, end] };
|
---|
| 120 | }
|
---|
| 121 | function parseBlockScalarHeader({ offset, props }, strict, onError) {
|
---|
| 122 | /* istanbul ignore if should not happen */
|
---|
| 123 | if (props[0].type !== 'block-scalar-header') {
|
---|
| 124 | onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found');
|
---|
| 125 | return null;
|
---|
| 126 | }
|
---|
| 127 | const { source } = props[0];
|
---|
| 128 | const mode = source[0];
|
---|
| 129 | let indent = 0;
|
---|
| 130 | let chomp = '';
|
---|
| 131 | let error = -1;
|
---|
| 132 | for (let i = 1; i < source.length; ++i) {
|
---|
| 133 | const ch = source[i];
|
---|
| 134 | if (!chomp && (ch === '-' || ch === '+'))
|
---|
| 135 | chomp = ch;
|
---|
| 136 | else {
|
---|
| 137 | const n = Number(ch);
|
---|
| 138 | if (!indent && n)
|
---|
| 139 | indent = n;
|
---|
| 140 | else if (error === -1)
|
---|
| 141 | error = offset + i;
|
---|
| 142 | }
|
---|
| 143 | }
|
---|
| 144 | if (error !== -1)
|
---|
| 145 | onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`);
|
---|
| 146 | let hasSpace = false;
|
---|
| 147 | let comment = '';
|
---|
| 148 | let length = source.length;
|
---|
| 149 | for (let i = 1; i < props.length; ++i) {
|
---|
| 150 | const token = props[i];
|
---|
| 151 | switch (token.type) {
|
---|
| 152 | case 'space':
|
---|
| 153 | hasSpace = true;
|
---|
| 154 | // fallthrough
|
---|
| 155 | case 'newline':
|
---|
| 156 | length += token.source.length;
|
---|
| 157 | break;
|
---|
| 158 | case 'comment':
|
---|
| 159 | if (strict && !hasSpace) {
|
---|
| 160 | const message = 'Comments must be separated from other tokens by white space characters';
|
---|
| 161 | onError(token, 'MISSING_CHAR', message);
|
---|
| 162 | }
|
---|
| 163 | length += token.source.length;
|
---|
| 164 | comment = token.source.substring(1);
|
---|
| 165 | break;
|
---|
| 166 | case 'error':
|
---|
| 167 | onError(token, 'UNEXPECTED_TOKEN', token.message);
|
---|
| 168 | length += token.source.length;
|
---|
| 169 | break;
|
---|
| 170 | /* istanbul ignore next should not happen */
|
---|
| 171 | default: {
|
---|
| 172 | const message = `Unexpected token in block scalar header: ${token.type}`;
|
---|
| 173 | onError(token, 'UNEXPECTED_TOKEN', message);
|
---|
| 174 | const ts = token.source;
|
---|
| 175 | if (ts && typeof ts === 'string')
|
---|
| 176 | length += ts.length;
|
---|
| 177 | }
|
---|
| 178 | }
|
---|
| 179 | }
|
---|
| 180 | return { mode, indent, chomp, comment, length };
|
---|
| 181 | }
|
---|
| 182 | /** @returns Array of lines split up as `[indent, content]` */
|
---|
| 183 | function splitLines(source) {
|
---|
| 184 | const split = source.split(/\n( *)/);
|
---|
| 185 | const first = split[0];
|
---|
| 186 | const m = first.match(/^( *)/);
|
---|
| 187 | const line0 = m?.[1]
|
---|
| 188 | ? [m[1], first.slice(m[1].length)]
|
---|
| 189 | : ['', first];
|
---|
| 190 | const lines = [line0];
|
---|
| 191 | for (let i = 1; i < split.length; i += 2)
|
---|
| 192 | lines.push([split[i], split[i + 1]]);
|
---|
| 193 | return lines;
|
---|
| 194 | }
|
---|
| 195 |
|
---|
| 196 | exports.resolveBlockScalar = resolveBlockScalar;
|
---|