UNPKG

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