src/vs/base/common/yaml.ts

1701 LOC · 1414 covered · 287 uncovered · 387 ranges · 2817 concepts · 150 introducers · 1521 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- yaml.ts ×54
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { localize } from '../../nls.js';
7 >
8 > /**
9 > * Parses a simplified YAML-like input from a single string.
10 > * Supports objects, arrays, primitive types (string, number, boolean, null).
11 > * Tracks positions for error reporting and node locations.
12 > *
13 > * Limitations:
14 > * - No anchors or references
15 > * - No complex types (dates, binary)
16 > * - No single pair implicit entries
17 > *
18 > * @param input A string containing the YAML-like input
19 > * @param errors Array to collect parsing errors
20 > * @returns The parsed representation (YamlMapNode, YamlSequenceNode, or YamlScalarNode)
21 > */
22 > export function parse(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): YamlNode | undefined {
23 > const scanner = new YamlScanner(input); yaml.ts ×1
24 > const tokens = scanner.scan();
25 > const parser = new YamlParser(tokens, input, errors, options);
26 > return parser.parse();
27 > }
29 > /**
30 > * Helper to parse a Markdown with YAML frontmatter document
31 > * @returns
32 > */
33 > export function parseFrontMatter(input: string, errors: YamlParseError[] = [], options: ParseOptions = {}): MarkdownNode | undefined {
34 > const tokens = new YamlScanner(input).scan(); yaml.ts ×3
35 > if (tokens.length === 0 || tokens[0].type !== TokenType.DocumentStart) {
36 > // does not start with a frontmatter header (---) yaml.ts ×1
37 > return new MarkdownNode(undefined, input);
38 > }
39 > const hasClosingFrontMatter = tokens.slice(1).some(token => token.type === TokenType.DocumentStart); yaml.ts ×2
40 > if (!hasClosingFrontMatter) {
41 return new MarkdownNode(undefined, input);
42 }
43 > const header = new YamlParser(tokens, input, errors, options).parse(); yaml.ts ×2
44 > const lastToken = tokens[tokens.length - 1];
45 > const body = lastToken.type === TokenType.EOF ? input.substring(lastToken.startOffset) : ''; yaml.ts ×3
46 > return new MarkdownNode(header, body);
47 > }
49 > export class MarkdownNode {
50 > constructor(public readonly header: YamlNode | undefined, public readonly body: string) {
51 > } yaml.ts ×3
53 > getStringValue(name: string): string | undefined {
54 > if (this.header && this.header.type === 'map') { yaml.ts ×2
55 > const property = this.header.properties.find(p => p.key.value === name); yaml.ts ×2
56 > if (property && property.value.type === 'scalar') {
57 > return property.value.value; yaml.ts ×1
58 > }
59 > } yaml.ts ×2
60 > return undefined; yaml.ts ×4
61 > } yaml.ts ×2
63 > getStringArrayValue(name: string): string[] | undefined {
64 > if (this.header && this.header.type === 'map') { yaml.ts ×2
65 > const property = this.header.properties.find(p => p.key.value === name); yaml.ts ×3
66 > if (property && property.value.type === 'sequence') {
67 > return property.value.items.filter(item => item.type === 'scalar').map(item => item.value); yaml.ts ×1
68 > } else if (property && property.value.type === 'scalar') { yaml.ts ×3
69 > if (property.value.format === 'none') { yaml.ts ×3
70 > return parseCommaSeparatedList(property.value.value, 0).map(item => item.value); yaml.ts ×1
71 > } else { yaml.ts ×3
72 > return [property.value.value]; yaml.ts ×1
73 > }
74 > } yaml.ts ×3
75 > } yaml.ts ×3
76 > return undefined; yaml.ts ×1
77 > } yaml.ts ×2
79 > getBooleanValue(name: string): boolean | undefined {
80 > const value = this.getStringValue(name); yaml.ts ×4
81 > if (value === 'true') {
82 > return true; yaml.ts ×1
83 > } else if (value === 'false') { yaml.ts ×4
84 return false;
85 }
86 > return undefined; yaml.ts ×4
87 > }
88 > } yaml.ts ×54
89 >
90 >
91 > /**
92 > * Parses a comma-separated list from a scalar node's value into an array of scalars.
93 > * Handles single-quoted and double-quoted items, trimming surrounding whitespace for
94 > * unquoted items. Offsets on each produced scalar node are relative to the original
95 > * document that the input scalar was parsed from.
96 > *
97 > * Internally wraps the scalar value in `[…]` and delegates to the full YAML parser so
98 > * that quoting, whitespace, and escape handling are consistent with the rest of the parser.
99 > *
100 > * @param scalar A scalar node whose value contains a comma-separated list.
101 > */
102 > export function parseCommaSeparatedList(value: string, offset: number = 0): YamlScalarNode[] {
103 > // Wrap the value as a YAML flow sequence and parse it. yaml.ts ×3
104 > const parsed = parse(`[${value}]`);
105 > // Items from the synthetic string start at offset 1 (after the '[').
106 > // Shift them so they're relative to the original document position.
107 > const shift = offset - 1;
108 > const items: YamlScalarNode[] = [];
109 > if (parsed && parsed.type === 'sequence') {
110 > for (const item of parsed.items) {
111 > if (item.type === 'scalar') { yaml.ts ×1
112 > items.push({ ...item, startOffset: item.startOffset + shift, endOffset: item.endOffset + shift });
113 > }
114 > }
115 > } else { yaml.ts ×3
116 items.push({ type: 'scalar', value, rawValue: value, startOffset: offset, endOffset: value.length + offset, format: 'none' });
117 }
118 > return items; yaml.ts ×3
119 > }
120 > yaml.ts ×54
121 > // -- AST Node Types ----------------------------------------------------------
122 >
123 > export interface YamlScalarNode {
124 > readonly type: 'scalar';
125 > readonly value: string;
126 > readonly rawValue: string;
127 > readonly startOffset: number;
128 > readonly endOffset: number;
129 > readonly format: 'single' | 'double' | 'none' | 'literal' | 'folded';
130 > }
131 >
132 > export interface YamlMapNode {
133 > readonly type: 'map';
134 > readonly properties: { key: YamlScalarNode; value: YamlNode }[];
135 > readonly style: 'block' | 'flow';
136 > readonly startOffset: number;
137 > readonly endOffset: number;
138 > }
139 >
140 > export interface YamlSequenceNode {
141 > readonly type: 'sequence';
142 > readonly items: YamlNode[];
143 > readonly style: 'block' | 'flow';
144 > readonly startOffset: number;
145 > readonly endOffset: number;
146 > }
147 >
148 > export type YamlNode = YamlSequenceNode | YamlMapNode | YamlScalarNode;
149 >
150 > export interface YamlParseError {
151 > readonly message: string;
152 > readonly startOffset: number;
153 > readonly endOffset: number;
154 > readonly code: string;
155 > }
156 >
157 > export interface ParseOptions {
158 > readonly allowDuplicateKeys?: boolean;
159 > }
160 >
161 > // -- Token Types -------------------------------------------------------------
162 >
163 > const enum TokenType {
164 > // Scalar values (unquoted, single-quoted, double-quoted)
165 > Scalar,
166 > // Structural tokens
167 > Colon, // ':'
168 > Dash, // '- '
169 > Comma, // ','
170 > FlowMapStart, // '{'
171 > FlowMapEnd, // '}'
172 > FlowSeqStart, // '['
173 > FlowSeqEnd, // ']'
174 > // Whitespace / structure
175 > Newline,
176 > Indent, // leading whitespace at start of line (carries the indent level)
177 > Comment,
178 > DocumentStart, // '---'
179 > DocumentEnd, // '...'
180 > EOF,
181 > }
182 >
183 > interface Token {
184 > readonly type: TokenType;
185 > readonly startOffset: number;
186 > readonly endOffset: number;
187 > /** For Scalar tokens: the raw text (including quotes). */
188 > readonly rawValue: string;
189 > /** For Scalar tokens: the interpreted string value. */
190 > readonly value: string;
191 > /** For Scalar tokens: quote style. */
192 > readonly format: 'single' | 'double' | 'none' | 'literal' | 'folded';
193 > /** For Indent tokens: the column (number of spaces). */
194 > readonly indent: number;
195 > }
196 >
197 > function makeToken( yaml.ts ×3
198 > type: TokenType,
199 > startOffset: number,
200 > endOffset: number,
201 > extra?: Partial<Pick<Token, 'rawValue' | 'value' | 'format' | 'indent'>>
202 > ): Token {
203 > return {
204 > type,
205 > startOffset,
206 > endOffset,
207 > rawValue: extra?.rawValue ?? '',
208 > value: extra?.value ?? '',
209 > format: extra?.format ?? 'none' as Token['format'],
210 > indent: extra?.indent ?? 0,
211 > };
212 > }
213 > yaml.ts ×54
214 > // -- Scanner -----------------------------------------------------------------
215 >
216 > class YamlScanner {
217 > private pos = 0;
218 > private readonly tokens: Token[] = [];
219 > // Track flow nesting depth so commas and flow indicators are only special inside flow collections
220 > private flowDepth = 0;
221 > // Track whether we've already seen a block colon on the current line.
222 > // After the first key: value colon, subsequent ': ' on the same line is part of the scalar value.
223 > private seenBlockColon = false;
224 > private seenDocumentStart = 0;
225 >
226 > constructor(private readonly input: string) { }
227 >
228 > scan(maxDocuments = 1): Token[] {
229 > while (this.pos < this.input.length) { yaml.ts ×3
230 > this.scanLine(); yaml.ts ×8
231 > if (this.seenDocumentStart > maxDocuments) {
232 > break; yaml.ts ×3
233 > }
234 > } yaml.ts ×8
235 > this.tokens.push(makeToken(TokenType.EOF, this.pos, this.pos)); yaml.ts ×3
236 > return this.tokens;
237 > }
238 > yaml.ts ×54
239 > // Scan a single logical line (up to and including the newline character)
240 > private scanLine(): void {
241 > this.seenBlockColon = false; yaml.ts ×8
242 > // Handle blank lines / lines that are only whitespace
243 > if (this.peekChar() === '\n') {
244 > this.tokens.push(makeToken(TokenType.Newline, this.pos, this.pos + 1)); yaml.ts ×1
245 > this.pos++;
246 > return;
247 > }
248 > if (this.peekChar() === '\r') { yaml.ts ×4
249 const end = this.pos + (this.input[this.pos + 1] === '\n' ? 2 : 1);
250 this.tokens.push(makeToken(TokenType.Newline, this.pos, end));
251 this.pos = end;
252 return;
253 }
254 > yaml.ts ×4
255 > // Measure leading whitespace → Indent token
256 > const indentStart = this.pos;
257 > let indent = 0;
258 > while (this.pos < this.input.length && (this.input[this.pos] === ' ' || this.input[this.pos] === '\t')) { yaml.ts ×8
259 > indent++; yaml.ts ×2
260 > this.pos++;
261 > }
262 > if (indent > 0) { yaml.ts ×4
263 > this.tokens.push(makeToken(TokenType.Indent, indentStart, this.pos, { indent })); yaml.ts ×2
264 > }
265 > yaml.ts ×4
266 > // If line is now empty (only whitespace before newline/EOF), emit newline
267 > if (this.pos >= this.input.length || this.peekChar() === '\n' || this.peekChar() === '\r') { yaml.ts ×8
268 > if (this.pos < this.input.length) { yaml.ts ×2
269 const nlStart = this.pos;
270 const end = this.peekChar() === '\r' && this.input[this.pos + 1] === '\n' ? this.pos + 2 : this.pos + 1;
271 this.tokens.push(makeToken(TokenType.Newline, nlStart, end));
272 this.pos = end;
273 }
274 > return; yaml.ts ×2
275 > }
276 > yaml.ts ×6
277 > // Check for document markers (--- / ...) at column 0
278 > if (indent === 0 && this.input.length - this.pos >= 3) { yaml.ts ×8
279 > const c0 = this.input[this.pos]; yaml.ts ×3
280 > const c1 = this.input[this.pos + 1];
281 > const c2 = this.input[this.pos + 2];
282 > const c3 = this.input[this.pos + 3];
283 > const isTerminator = c3 === undefined || c3 === ' ' || c3 === '\t' || c3 === '\n' || c3 === '\r';
284 > if (c0 === '-' && c1 === '-' && c2 === '-' && isTerminator) {
285 > this.tokens.push(makeToken(TokenType.DocumentStart, this.pos, this.pos + 3)); yaml.ts ×3
286 > this.pos += 3;
287 > this.scanLineContent();
288 > this.scanNewline();
289 > this.seenDocumentStart++;
290 > return;
291 > }
292 > if (c0 === '.' && c1 === '.' && c2 === '.' && isTerminator) { yaml.ts ×3
293 this.tokens.push(makeToken(TokenType.DocumentEnd, this.pos, this.pos + 3));
294 this.pos += 3;
295 this.scanLineContent();
296 this.scanNewline();
297 return;
298 }
299 > } yaml.ts ×3
300 > yaml.ts ×1
301 > // Check for comment-only line
302 > if (this.peekChar() === '#') {
303 > this.scanComment(); yaml.ts ×1
304 > this.scanNewline();
305 > return;
306 > }
307 > yaml.ts ×10
308 > // Skip directive lines (e.g., %YAML 1.2, %TAG) - consume rest of line
309 > if (this.peekChar() === '%') {
310 while (this.pos < this.input.length && this.input[this.pos] !== '\n' && this.input[this.pos] !== '\r') {
311 this.pos++;
312 }
313 this.scanNewline();
314 return;
315 }
316 > yaml.ts ×10
317 > // Scan the rest of the line for tokens
318 > this.scanLineContent();
319 > this.scanNewline();
320 > } yaml.ts ×8
321 > yaml.ts ×54
322 > private scanLineContent(): void {
323 > while (this.pos < this.input.length && this.peekChar() !== '\n' && this.peekChar() !== '\r') { yaml.ts ×2
324 > this.skipInlineWhitespace(); yaml.ts ×10
325 > if (this.pos >= this.input.length || this.peekChar() === '\n' || this.peekChar() === '\r') {
326 > break; yaml.ts ×1
327 > }
328 > yaml.ts ×10
329 > const ch = this.peekChar();
330 >
331 > if (ch === '#') {
332 > this.scanComment(); yaml.ts ×1
333 > break; // comment consumes rest of line
334 > } else if (ch === '{') { yaml.ts ×10
335 > this.flowDepth++; yaml.ts ×5
336 > this.tokens.push(makeToken(TokenType.FlowMapStart, this.pos, this.pos + 1));
337 > this.pos++;
338 > } else if (ch === '}' && this.flowDepth > 0) { yaml.ts ×10
339 > this.flowDepth--; yaml.ts ×5
340 > this.tokens.push(makeToken(TokenType.FlowMapEnd, this.pos, this.pos + 1));
341 > this.pos++;
342 > } else if (ch === '[') { yaml.ts ×10
343 > this.flowDepth++; yaml.ts ×5
344 > this.tokens.push(makeToken(TokenType.FlowSeqStart, this.pos, this.pos + 1));
345 > this.pos++;
346 > } else if (ch === ']' && this.flowDepth > 0) { yaml.ts ×2
347 > this.flowDepth--; yaml.ts ×2
348 > this.tokens.push(makeToken(TokenType.FlowSeqEnd, this.pos, this.pos + 1));
349 > this.pos++;
350 > } else if (ch === ',' && this.flowDepth > 0) { yaml.ts ×2
351 > this.tokens.push(makeToken(TokenType.Comma, this.pos, this.pos + 1)); yaml.ts ×1
352 > this.pos++;
353 > } else if (ch === '-' && this.isBlockDash()) { yaml.ts ×5
354 > // Block sequence indicator: '- ' or '-' at end of line yaml.ts ×11
355 > this.tokens.push(makeToken(TokenType.Dash, this.pos, this.pos + 1));
356 > this.pos++;
357 > } else if (ch === ':' && this.isBlockColon()) { yaml.ts ×5
358 > this.tokens.push(makeToken(TokenType.Colon, this.pos, this.pos + 1)); yaml.ts ×4
359 > this.pos++;
360 > if (this.flowDepth === 0) {
361 > this.seenBlockColon = true; yaml.ts ×11
362 > }
363 > } else if (ch === ':' && this.flowDepth > 0 && this.lastTokenIsJsonLike()) { yaml.ts ×5
364 // In flow context, ':' immediately following a JSON-like node (quoted scalar,
365 // flow mapping, or flow sequence) is a value indicator even without trailing space
366 this.tokens.push(makeToken(TokenType.Colon, this.pos, this.pos + 1));
367 this.pos++;
368 > } else if (ch === '\'' || ch === '"') { yaml.ts ×5
369 > this.scanQuotedScalar(ch); yaml.ts ×7
370 > } else if ((ch === '|' || ch === '>') && this.flowDepth === 0 && this.isBlockScalarStart()) { yaml.ts ×5
371 > this.scanBlockScalar(ch as '|' | '>'); yaml.ts ×22
372 > break; // Block scalar consumed multiple lines; return to main scan loop
373 > } else { yaml.ts ×2
374 > this.scanUnquotedScalar();
375 > }
376 > } yaml.ts ×10
377 > } yaml.ts ×2
378 > yaml.ts ×54
379 > /** Check if '-' is a block sequence dash (followed by space, newline, or EOF) */
380 > private isBlockDash(): boolean {
381 > const next = this.input[this.pos + 1]; yaml.ts ×11
382 > return next === undefined || next === ' ' || next === '\t' || next === '\n' || next === '\r';
383 > }
384 > yaml.ts ×54
385 > /** Check if ':' acts as a mapping value indicator (followed by space, newline, EOF, or flow indicator) */
386 > private isBlockColon(): boolean {
387 > // In block context, after the first key-value colon on a line, yaml.ts ×4
388 > // subsequent ': ' is part of the scalar value, not a mapping indicator.
389 > if (this.seenBlockColon && this.flowDepth === 0) { return false; }
390 > const next = this.input[this.pos + 1];
391 > if (next === undefined || next === ' ' || next === '\t' || next === '\n' || next === '\r') { return true; }
392 // Flow indicators after colon only count inside flow context
393 > if (this.flowDepth > 0 && (next === ',' || next === '}' || next === ']')) { return true; } yaml.ts ×4
394 return false;
395 > } yaml.ts ×4
396 > yaml.ts ×54
397 > /** Check if the last non-whitespace token is a JSON-like node (quoted scalar or flow end) */
398 > private lastTokenIsJsonLike(): boolean {
399 for (let i = this.tokens.length - 1; i >= 0; i--) {
400 const t = this.tokens[i];
401 if (t.type === TokenType.Newline || t.type === TokenType.Indent || t.type === TokenType.Comment) {
402 continue;
403 }
404 // Quoted scalar or flow collection end bracket
405 if (t.type === TokenType.Scalar && t.format !== 'none') { return true; }
406 if (t.type === TokenType.FlowMapEnd || t.type === TokenType.FlowSeqEnd) { return true; }
407 return false;
408 }
409 return false;
410 }
411 > yaml.ts ×54
412 > private scanQuotedScalar(quote: '\'' | '"'): void {
413 > const start = this.pos; yaml.ts ×7
414 > this.pos++; // skip opening quote
415 > let value = '';
416 > // Track trailing literal whitespace count so flow folding only trims
417 > // source-level whitespace, not whitespace produced by escape sequences
418 > let trailingLiteralWs = 0;
419 >
420 > while (this.pos < this.input.length) {
421 > const ch = this.input[this.pos];
422 > if (ch === quote) {
423 > // In single-quoted strings, '' is an escaped single quote yaml.ts ×2
424 > if (quote === '\'' && this.input[this.pos + 1] === '\'') {
425 > value += '\''; yaml.ts ×1
426 > this.pos += 2;
427 > trailingLiteralWs = 0;
428 > continue;
429 > }
430 > this.pos++; // skip closing quote yaml.ts ×2
431 > const rawValue = this.input.substring(start, this.pos);
432 > this.tokens.push(makeToken(TokenType.Scalar, start, this.pos, {
433 > rawValue,
434 > value,
435 > format: quote === '\'' ? 'single' : 'double',
436 > }));
437 > return;
438 > }
439 > yaml.ts ×7
440 > // Handle escape sequences in double-quoted strings
441 > if (quote === '"' && ch === '\\') {
442 > const next = this.input[this.pos + 1]; yaml.ts ×5
443 > // Escaped line break: \ + newline → join lines without inserting a space
444 > if (next === '\n' || next === '\r') {
445 this.pos++; // skip '\'
446 this.consumeNewline();
447 // Strip leading whitespace on continuation line
448 this.skipInlineWhitespace();
449 trailingLiteralWs = 0;
450 continue;
451 }
452 > switch (next) { yaml.ts ×5
453 > case 'n': value += '\n'; break;
454 > case 't': value += '\t'; break;
455 > case '\\': value += '\\'; break;
456 > case '"': value += '"'; break;
457 > case '/': value += '/'; break;
458 > case 'r': value += '\r'; break;
459 > case '0': value += '\0'; break;
460 > case 'a': value += '\x07'; break;
461 > case 'b': value += '\b'; break;
462 > case 'e': value += '\x1b'; break;
463 > case 'v': value += '\v'; break;
464 > case 'f': value += '\f'; break;
465 > case ' ': value += ' '; break;
466 > case '_': value += '\xa0'; break;
467 > case 'x': {
468 // \xNN - 2-digit hex escape
469 const hex = this.input.substring(this.pos + 2, this.pos + 4);
470 const code = parseInt(hex, 16);
471 if (hex.length === 2 && !isNaN(code)) {
472 value += String.fromCharCode(code);
473 this.pos += 4;
474 } else {
475 value += '\\x';
476 this.pos += 2;
477 }
478 trailingLiteralWs = 0;
479 continue;
480 }
481 > case 'u': { yaml.ts ×5
482 // \uNNNN - 4-digit unicode escape
483 const hex = this.input.substring(this.pos + 2, this.pos + 6);
484 const code = parseInt(hex, 16);
485 if (hex.length === 4 && !isNaN(code)) {
486 value += String.fromCodePoint(code);
487 this.pos += 6;
488 } else {
489 value += '\\u';
490 this.pos += 2;
491 }
492 trailingLiteralWs = 0;
493 continue;
494 }
495 > case 'U': { yaml.ts ×5
496 // \UNNNNNNNN - 8-digit unicode escape
497 const hex = this.input.substring(this.pos + 2, this.pos + 10);
498 const code = parseInt(hex, 16);
499 if (hex.length === 8 && !isNaN(code)) {
500 value += String.fromCodePoint(code);
501 this.pos += 10;
502 } else {
503 value += '\\U';
504 this.pos += 2;
505 }
506 trailingLiteralWs = 0;
507 continue;
508 }
509 > default: value += '\\' + (next ?? ''); break; yaml.ts ×5
510 > }
511 > this.pos += 2;
512 > trailingLiteralWs = 0;
513 > continue;
514 > }
515 > yaml.ts ×7
516 > // Flow folding: handle newlines inside quoted scalars (both single and double)
517 > if (ch === '\n' || ch === '\r') {
518 // Trim trailing literal whitespace (not escape-produced whitespace)
519 if (trailingLiteralWs > 0) {
520 value = value.substring(0, value.length - trailingLiteralWs);
521 }
522 trailingLiteralWs = 0;
523
524 // Skip the newline
525 this.consumeNewline();
526
527 // Count empty lines (lines with only whitespace)
528 let emptyLineCount = 0;
529 while (this.pos < this.input.length) {
530 // Skip whitespace at start of line
531 this.skipInlineWhitespace();
532 // Check if this line is empty (another newline follows)
533 const c = this.input[this.pos];
534 if (c === '\n' || c === '\r') {
535 emptyLineCount++;
536 this.consumeNewline();
537 } else {
538 break;
539 }
540 }
541
542 // Apply folding: empty lines → \n each, otherwise single newline → space
543 if (emptyLineCount > 0) {
544 value += '\n'.repeat(emptyLineCount);
545 } else {
546 value += ' ';
547 }
548 continue;
549 }
550 > yaml.ts ×7
551 > // Track literal whitespace for folding purposes
552 > if (ch === ' ' || ch === '\t') {
553 > trailingLiteralWs++; yaml.ts ×1
554 > } else { yaml.ts ×7
555 > trailingLiteralWs = 0;
556 > }
557 > value += ch;
558 > this.pos++;
559 > }
560 > yaml.ts ×1
561 > // Unterminated string - emit what we have
562 > const rawValue = this.input.substring(start, this.pos);
563 > this.tokens.push(makeToken(TokenType.Scalar, start, this.pos, {
564 > rawValue,
565 > value,
566 > format: quote === '\'' ? 'single' : 'double', yaml.ts ×7
567 > }));
568 > }
569 > yaml.ts ×54
570 > private scanUnquotedScalar(): void {
571 > const start = this.pos; yaml.ts ×2
572 > let end = this.pos;
573 >
574 > while (this.pos < this.input.length) {
575 > const ch = this.input[this.pos];
576 > // Stop at newline
577 > if (ch === '\n' || ch === '\r') { break; }
578 > // Stop at flow indicators (only inside flow collections)
579 > if (this.flowDepth > 0 && (ch === ',' || ch === '}' || ch === ']')) { break; }
580 > if (this.flowDepth > 0 && (ch === '{' || ch === '[')) { break; }
581 > // Stop at ': ' or ':' at end-of-line (mapping value indicator)
582 > if (ch === ':' && this.isBlockColon()) { break; }
583 > // Stop at ' #' (comment)
584 > if (ch === '#' && this.pos > start && (this.input[this.pos - 1] === ' ' || this.input[this.pos - 1] === '\t')) { break; }
585 >
586 > this.pos++;
587 > // Track the last non-whitespace position to trim trailing whitespace
588 > if (ch !== ' ' && ch !== '\t') {
589 > end = this.pos;
590 > }
591 > }
592 >
593 > const rawValue = this.input.substring(start, end);
594 > this.tokens.push(makeToken(TokenType.Scalar, start, end, {
595 > rawValue,
596 > value: rawValue,
597 > format: 'none',
598 > }));
599 > }
600 > yaml.ts ×54
601 > /**
602 > * Check if '|' or '>' at the current position is a block scalar indicator.
603 > * Must be followed by optional indentation/chomping indicators, optional comment, then newline.
604 > */
605 > private isBlockScalarStart(): boolean {
606 > let p = this.pos + 1; yaml.ts ×22
607 > // Skip optional indentation indicator (digit 1-9) and chomping indicator (+/-)
608 > while (p < this.input.length) {
609 > const c = this.input[p];
610 > if (c >= '1' && c <= '9') { p++; continue; }
611 > if (c === '+' || c === '-') { p++; continue; }
612 > break;
613 > }
614 > // Skip optional whitespace
615 > while (p < this.input.length && (this.input[p] === ' ' || this.input[p] === '\t')) { p++; }
616 > // Must be at newline, EOF, or comment
617 > if (p >= this.input.length) { return true; }
618 > const c = this.input[p];
619 > return c === '\n' || c === '\r' || c === '#';
620 > }
621 > yaml.ts ×54
622 > /**
623 > * Scan a block scalar (literal '|' or folded '>').
624 > * Parses the header line for indentation indicator and chomping mode,
625 > * then collects all content lines that are indented beyond the detected indentation.
626 > */
627 > private scanBlockScalar(style: '|' | '>'): void {
628 > const start = this.pos; yaml.ts ×22
629 > this.pos++; // skip '|' or '>'
630 >
631 > // Parse header: optional indentation indicator (1-9) and chomping indicator (+/-)
632 > let explicitIndent = 0;
633 > let chomping: 'clip' | 'strip' | 'keep' = 'clip';
634 >
635 > // The order of indent indicator and chomping indicator can vary (D83L test)
636 > for (let i = 0; i < 2; i++) {
637 > if (this.pos < this.input.length) {
638 > const c = this.input[this.pos];
639 > if (c >= '1' && c <= '9' && explicitIndent === 0) {
640 explicitIndent = parseInt(c, 10);
641 this.pos++;
642 > } else if (c === '-' && chomping === 'clip') { yaml.ts ×22
643 > chomping = 'strip'; yaml.ts ×2
644 > this.pos++;
645 > } else if (c === '+' && chomping === 'clip') { yaml.ts ×22
646 > chomping = 'keep'; yaml.ts ×3
647 > this.pos++;
648 > }
649 > } yaml.ts ×22
650 > }
651 >
652 > // Skip any trailing whitespace on the header line
653 > while (this.pos < this.input.length && (this.input[this.pos] === ' ' || this.input[this.pos] === '\t')) {
654 this.pos++;
655 }
656 > yaml.ts ×22
657 > // Skip optional comment on header line
658 > if (this.pos < this.input.length && this.input[this.pos] === '#') {
659 while (this.pos < this.input.length && this.input[this.pos] !== '\n' && this.input[this.pos] !== '\r') {
660 this.pos++;
661 }
662 }
663 > yaml.ts ×22
664 > // Skip the header line's newline
665 > this.consumeNewline();
666 >
667 > // Determine the parent block's indentation level.
668 > // Per YAML spec 8.1.1.1, content indentation = parent_block_indent + N
669 > // where N is the explicit indent indicator (or auto-detected).
670 > // Also used to establish a minimum content indent for auto-detection.
671 > const parentBlockIndent = this.getParentBlockIndent(start);
672 >
673 > // Compute the content indentation level
674 > let contentIndent = explicitIndent > 0 ? parentBlockIndent + explicitIndent : 0;
675 > const lines: string[] = [];
676 > let trailingNewlines = 0;
677 >
678 > while (this.pos < this.input.length) {
679 > const lineStart = this.pos;
680 >
681 > // Count leading spaces on this line (tabs are not valid YAML indentation)
682 > let lineIndent = 0;
683 > while (this.pos < this.input.length && this.input[this.pos] === ' ') {
684 > lineIndent++;
685 > this.pos++;
686 > }
687 >
688 > // Check if this is an empty or whitespace-only line
689 > if (this.pos >= this.input.length || this.input[this.pos] === '\n' || this.input[this.pos] === '\r') {
690 if (contentIndent > 0 && lineIndent >= contentIndent) {
691 // Whitespace-only line with enough indent - preserve excess whitespace
692 const preserved = this.input.substring(lineStart + contentIndent, this.pos);
693 lines.push(preserved);
694 if (preserved === '') {
695 // Effectively an empty line - counts as trailing
696 trailingNewlines++;
697 } else {
698 trailingNewlines = 0;
699 }
700 } else {
701 // Truly empty line - part of scalar content
702 lines.push('');
703 trailingNewlines++;
704 }
705 // Skip newline
706 this.consumeNewline();
707 continue;
708 }
709 > yaml.ts ×22
710 > // Check for document markers at column 0 - they terminate the block scalar
711 > if (lineIndent === 0 && this.input.length - this.pos >= 3) {
712 const c0 = this.input[this.pos];
713 const c1 = this.input[this.pos + 1];
714 const c2 = this.input[this.pos + 2];
715 const c3 = this.input[this.pos + 3];
716 const isTerm = c3 === undefined || c3 === ' ' || c3 === '\t' || c3 === '\n' || c3 === '\r';
717 if ((c0 === '-' && c1 === '-' && c2 === '-' && isTerm) ||
718 (c0 === '.' && c1 === '.' && c2 === '.' && isTerm)) {
719 this.pos = lineStart;
720 break;
721 }
722 }
723 > yaml.ts ×22
724 > // Auto-detect content indent from first non-empty line.
725 > // Content must be more indented than the parent block.
726 > if (contentIndent === 0) {
727 > if (lineIndent <= parentBlockIndent) {
728 // Not enough indentation - terminates the block scalar
729 this.pos = lineStart;
730 break;
731 }
732 > contentIndent = lineIndent; yaml.ts ×22
733 > }
734 >
735 > // If this line's indentation is less than the content indent, the block scalar is done
736 > if (lineIndent < contentIndent) {
737 this.pos = lineStart;
738 break;
739 }
740 > yaml.ts ×22
741 > // Read the rest of the line (the content)
742 > const contentStart = lineStart + contentIndent;
743 > while (this.pos < this.input.length && this.input[this.pos] !== '\n' && this.input[this.pos] !== '\r') {
744 > this.pos++;
745 > }
746 > // The line content includes any extra indentation beyond contentIndent
747 > const lineContent = this.input.substring(contentStart, this.pos);
748 > lines.push(lineContent);
749 > trailingNewlines = 0;
750 >
751 > // Skip newline
752 > this.consumeNewline();
753 > }
754 >
755 > // Process the collected lines according to the block scalar style
756 > let value: string;
757 > if (style === '|') {
758 > // Literal: join lines with newlines (preserving all line breaks as-is) yaml.ts ×1
759 > value = lines.join('\n');
760 > } else { yaml.ts ×22
761 > // Folded: per YAML spec, line breaks between adjacent non-more-indented yaml.ts ×4
762 > // content lines are folded into spaces. More-indented lines preserve breaks.
763 > // Empty lines produce \n each. The break from content into an empty run
764 > // is "trimmed" (absorbed) for non-more-indented lines, but preserved
765 > // for more-indented lines.
766 > value = '';
767 > let lastNonEmptyIsMoreIndented = false;
768 > let inEmptyRun = false;
769 > let seenNonEmpty = false;
770 >
771 > for (let i = 0; i < lines.length; i++) {
772 > const line = lines[i];
773 > const isMoreIndented = line.length > 0 && (line[0] === ' ' || line[0] === '\t');
774 >
775 > if (line === '') {
776 // Empty line → contributes one \n
777 value += '\n';
778 inEmptyRun = true;
779 > } else if (i === 0) { yaml.ts ×4
780 > value = line;
781 > lastNonEmptyIsMoreIndented = isMoreIndented;
782 > seenNonEmpty = true;
783 > } else if (inEmptyRun) {
784 // Transitioning from empty lines back to content.
785 // If the previous content or current line is more-indented
786 // AND we've seen content before, the break is preserved.
787 // Otherwise the empties already provided all needed line breaks.
788 if ((lastNonEmptyIsMoreIndented || isMoreIndented) && seenNonEmpty) {
789 value += '\n' + line;
790 } else {
791 value += line;
792 }
793 lastNonEmptyIsMoreIndented = isMoreIndented;
794 inEmptyRun = false;
795 seenNonEmpty = true;
796 > } else if (isMoreIndented || lastNonEmptyIsMoreIndented) { yaml.ts ×4
797 // More-indented line → preserve newline
798 value += '\n' + line;
799 lastNonEmptyIsMoreIndented = isMoreIndented;
800 seenNonEmpty = true;
801 > } else { yaml.ts ×4
802 > // Normal adjacent non-more-indented lines → fold to space
803 > value += ' ' + line;
804 > lastNonEmptyIsMoreIndented = false;
805 > seenNonEmpty = true;
806 > }
807 > }
808 > }
809 > yaml.ts ×22
810 > // Apply chomping to trailing newlines
811 > if (trailingNewlines > 0) {
812 // Strip all trailing newlines from the value
813 let end = value.length;
814 while (end > 0 && value[end - 1] === '\n') {
815 end--;
816 }
817 value = value.substring(0, end);
818 }
819 > yaml.ts ×22
820 > // Determine if there was any actual (non-empty) content
821 > const hasContent = lines.some(l => l !== '');
822 >
823 > switch (chomping) {
824 > case 'clip':
825 > if (hasContent) { yaml.ts ×1
826 > // Add exactly one trailing newline
827 > value += '\n';
828 > }
829 > break;
830 > case 'keep': yaml.ts ×22
831 > if (hasContent) { yaml.ts ×3
832 > // Content + trailing: final line break + trailing empty line breaks
833 > value += '\n'.repeat(trailingNewlines + 1);
834 > } else {
835 // No content, only trailing empties
836 value = '\n'.repeat(trailingNewlines);
837 }
838 > break; yaml.ts ×3
839 > case 'strip': yaml.ts ×22
840 > // No trailing newline yaml.ts ×2
841 > break;
842 > } yaml.ts ×22
843 >
844 > const rawValue = this.input.substring(start, this.pos);
845 > this.tokens.push(makeToken(TokenType.Scalar, start, this.pos, {
846 > rawValue,
847 > value,
848 > format: style === '|' ? 'literal' : 'folded',
849 > }));
850 > }
851 > yaml.ts ×54
852 > /**
853 > * Determine the parent block's indentation level for a block scalar.
854 > * Looks at preceding tokens to find the context:
855 > * - After Colon: the indentation of the line containing the mapping key
856 > * - After Dash: the column of the dash
857 > * - At document level: -1 (allows content at indent 0)
858 > */
859 > private getParentBlockIndent(blockScalarPos: number): number {
860 > for (let i = this.tokens.length - 1; i >= 0; i--) { yaml.ts ×22
861 > const t = this.tokens[i];
862 > if (t.type === TokenType.Newline || t.type === TokenType.Comment || t.type === TokenType.Indent) { continue; }
863 > if (t.type === TokenType.Colon) {
864 > // Block scalar is a mapping value. The parent indentation
865 > // is the column of the mapping key (the scalar before the colon).
866 > for (let j = i - 1; j >= 0; j--) {
867 > const kt = this.tokens[j];
868 > if (kt.type === TokenType.Newline || kt.type === TokenType.Comment || kt.type === TokenType.Indent) { continue; }
869 > // Found the key token - return its column
870 > return this.getColumnAt(kt.startOffset);
871 > }
872 return 0;
873 }
874 if (t.type === TokenType.Dash) {
875 // Block scalar is a sequence item. Parent indent = column of the dash.
876 return this.getColumnAt(t.startOffset);
877 }
878 // Document root - content at indent 0 is valid
879 if (t.type === TokenType.DocumentStart) { return -1; }
880 // For any other token, use 0
881 break;
882 }
883 return 0;
884 > } yaml.ts ×22
885 > yaml.ts ×54
886 > /**
887 > * Get the column (0-based offset from start of line) for a position in the input.
888 > */
889 > private getColumnAt(offset: number): number {
890 > let col = 0; yaml.ts ×22
891 > let p = offset - 1;
892 > while (p >= 0 && this.input[p] !== '\n' && this.input[p] !== '\r') {
893 col++;
894 p--;
895 }
896 > return col; yaml.ts ×22
897 > }
898 > yaml.ts ×54
899 > private scanComment(): void {
900 > const start = this.pos; yaml.ts ×1
901 > while (this.pos < this.input.length && this.input[this.pos] !== '\n' && this.input[this.pos] !== '\r') {
902 > this.pos++;
903 > }
904 > this.tokens.push(makeToken(TokenType.Comment, start, this.pos, {
905 > rawValue: this.input.substring(start, this.pos),
906 > value: this.input.substring(start, this.pos),
907 > }));
908 > }
909 > yaml.ts ×54
910 > private scanNewline(): void {
911 > const start = this.pos; yaml.ts ×6
912 > if (this.consumeNewline()) {
913 > this.tokens.push(makeToken(TokenType.Newline, start, this.pos)); yaml.ts ×1
914 > }
915 > } yaml.ts ×6
916 > yaml.ts ×54
917 > private skipInlineWhitespace(): void {
918 > while (this.pos < this.input.length) { yaml.ts ×10
919 > const ch = this.input[this.pos];
920 > if (ch === ' ' || ch === '\t') {
921 > this.pos++; yaml.ts ×1
922 > } else { yaml.ts ×10
923 > break;
924 > }
925 > }
926 > }
927 > yaml.ts ×54
928 > /** Advance past a newline sequence (\r\n, \n, or \r). Returns true if a newline was consumed. */
929 > private consumeNewline(): boolean {
930 > if (this.pos >= this.input.length) { return false; } yaml.ts ×6
931 > if (this.input[this.pos] === '\r' && this.input[this.pos + 1] === '\n') {
932 > this.pos += 2; yaml.ts ×1
933 > return true;
934 > }
935 > if (this.input[this.pos] === '\n' || this.input[this.pos] === '\r') { yaml.ts ×6
936 > this.pos++; yaml.ts ×1
937 > return true;
938 > }
939 return false;
940 > } yaml.ts ×6
941 > yaml.ts ×54
942 > private peekChar(): string {
943 > return this.input[this.pos]; yaml.ts ×8
944 > }
945 > } yaml.ts ×54
946 >
947 > // -- Parser ------------------------------------------------------------------
948 >
949 > class YamlParser {
950 > private pos = 0;
951 >
952 > constructor(
953 > private readonly tokens: Token[], yaml.ts ×7
954 > private readonly input: string,
955 > private readonly errors: YamlParseError[],
956 > private readonly options: ParseOptions,
957 > ) { }
958 > yaml.ts ×54
959 > parse(): YamlNode | undefined {
960 > this.skipNewlinesAndComments(); yaml.ts ×7
961 > // Skip document start marker (---) if present
962 > if (this.currentToken().type === TokenType.DocumentStart) {
963 > this.advance(); yaml.ts ×3
964 > this.skipNewlinesAndComments();
965 > }
966 > if (this.currentToken().type === TokenType.EOF || this.currentToken().type === TokenType.DocumentEnd) { yaml.ts ×7
967 > return undefined; yaml.ts ×1
968 > }
969 > const result = this.parseValue(-1); yaml.ts ×4
970 > return result;
971 > } yaml.ts ×7
972 > yaml.ts ×54
973 > // -- helpers ----------------------------------------------------------
974 >
975 > private currentToken(): Token {
976 > return this.tokens[this.pos]; yaml.ts ×7
977 > }
978 > yaml.ts ×54
979 > private peek(offset = 0): Token {
980 > return this.tokens[Math.min(this.pos + offset, this.tokens.length - 1)]; yaml.ts ×1
981 > }
982 > yaml.ts ×54
983 > private advance(): Token {
984 > const t = this.tokens[this.pos]; yaml.ts ×1
985 > if (t.type !== TokenType.EOF) {
986 > this.pos++;
987 > }
988 > return t;
989 > }
990 > yaml.ts ×54
991 > private expect(type: TokenType): Token {
992 > const t = this.currentToken(); yaml.ts ×2
993 > if (t.type === type) {
994 > return this.advance();
995 > }
996 return t;
997 > } yaml.ts ×2
998 > yaml.ts ×54
999 > private emitError(message: string, startOffset: number, endOffset: number, code: string): void {
1000 > this.errors.push({ message, startOffset, endOffset, code }); yaml.ts ×1
1001 > }
1002 > yaml.ts ×54
1003 > private skipNewlinesAndComments(): void {
1004 > while ( yaml.ts ×7
1005 > this.currentToken().type === TokenType.Newline ||
1006 > this.currentToken().type === TokenType.Comment ||
1007 > (this.currentToken().type === TokenType.Indent && this.isFollowedByNewlineOrComment())
1008 > ) {
1009 > this.advance(); yaml.ts ×1
1010 > }
1011 > } yaml.ts ×7
1012 > yaml.ts ×54
1013 > /** Returns true if the current Indent token is followed immediately by Newline/Comment/EOF */
1014 > private isFollowedByNewlineOrComment(): boolean {
1015 > const next = this.peek(1); yaml.ts ×1
1016 > return next.type === TokenType.Newline || next.type === TokenType.Comment || next.type === TokenType.EOF;
1017 > }
1018 > yaml.ts ×54
1019 > /**
1020 > * Determines the current indentation level.
1021 > * If the current token is an Indent, returns its indent value.
1022 > * Otherwise returns 0 (token is at column 0).
1023 > */
1024 > private currentIndent(): number {
1025 > if (this.currentToken().type === TokenType.Indent) { yaml.ts ×5
1026 > return this.currentToken().indent; yaml.ts ×1
1027 > }
1028 > return 0; yaml.ts ×5
1029 > }
1030 > yaml.ts ×54
1031 > // -- Main parse entry for a value at a given indentation --------------
1032 >
1033 > private parseValue(parentIndent: number): YamlNode | undefined {
1034 > this.skipNewlinesAndComments(); yaml.ts ×4
1035 > const token = this.currentToken();
1036 >
1037 > // Flow collections (also check past indent)
1038 > const flowToken = token.type === TokenType.Indent ? this.peek(1) : token;
1039 > if (flowToken.type === TokenType.FlowMapStart || flowToken.type === TokenType.FlowSeqStart) {
1040 > if (token.type === TokenType.Indent) { this.advance(); } yaml.ts ×1
1041 > if (flowToken.type === TokenType.FlowMapStart) { return this.parseFlowMap(); }
1042 > return this.parseFlowSeq(); yaml.ts ×1
1043 > }
1044 > yaml.ts ×5
1045 > // Block-level: detect if this is a sequence or mapping
1046 > const indent = this.currentIndent();
1047 >
1048 > // Determine what the first meaningful token is at this indent
1049 > const firstContentToken = this.peekPastIndent();
1050 >
1051 > if (firstContentToken.type === TokenType.Dash) {
1052 > return this.parseBlockSequence(indent); yaml.ts ×11
1053 > }
1054 > yaml.ts ×1
1055 > // Check if this looks like a mapping (scalar followed by colon)
1056 > if (this.looksLikeMapping()) {
1057 > return this.parseBlockMapping(indent); yaml.ts ×1
1058 > }
1059 > yaml.ts ×1
1060 > // Otherwise it's a scalar
1061 > if (token.type === TokenType.Scalar || token.type === TokenType.Indent) { yaml.ts ×4
1062 > return this.parseScalar(parentIndent); yaml.ts ×1
1063 > }
1064 > yaml.ts ×1
1065 > return undefined;
1066 > } yaml.ts ×4
1067 > yaml.ts ×54
1068 > /** Peek past an optional Indent token to see the first content token */
1069 > private peekPastIndent(): Token {
1070 > if (this.currentToken().type === TokenType.Indent) { yaml.ts ×5
1071 > return this.peek(1); yaml.ts ×1
1072 > }
1073 > return this.currentToken(); yaml.ts ×5
1074 > }
1075 > yaml.ts ×54
1076 > /** Check if tokens at current position look like a mapping entry (key: value) */
1077 > private looksLikeMapping(): boolean {
1078 > let offset = 0; yaml.ts ×2
1079 > if (this.peek(offset).type === TokenType.Indent) { offset++; }
1080 > if (this.peek(offset).type === TokenType.Scalar) {
1081 > offset++; yaml.ts ×1
1082 > if (this.peek(offset).type === TokenType.Colon) { return true; }
1083 > }
1084 > return false; yaml.ts ×1
1085 > } yaml.ts ×2
1086 > yaml.ts ×54
1087 > // -- Scalar ----------------------------------------------------------
1088 >
1089 > private parseScalar(parentIndent: number = -1): YamlScalarNode {
1090 > // Skip indent if present yaml.ts ×3
1091 > if (this.currentToken().type === TokenType.Indent) {
1092 > this.advance(); yaml.ts ×1
1093 > }
1094 > const token = this.expect(TokenType.Scalar); yaml.ts ×3
1095 > // Quoted scalars are complete as-is (scanner handles their multiline)
1096 > if (token.format !== 'none') {
1097 > return this.scalarFromToken(token); yaml.ts ×1
1098 > }
1099 > // For unquoted (plain) scalars, check for multiline continuation yaml.ts ×1
1100 > return this.parsePlainMultiline(token, parentIndent);
1101 > } yaml.ts ×3
1102 > yaml.ts ×54
1103 > /**
1104 > * Parse a multiline plain scalar. The first line's token is already consumed.
1105 > * Continuation lines must be indented deeper than `parentIndent`.
1106 > * Line folding rules:
1107 > * - Single line break → space
1108 > * - Each empty line → preserved as \n
1109 > */
1110 > private parsePlainMultiline(firstToken: Token, parentIndent: number): YamlScalarNode {
1111 > let value = firstToken.value; yaml.ts ×4
1112 > let endOffset = firstToken.endOffset;
1113 >
1114 > while (true) {
1115 > // Save position to backtrack if continuation is not valid
1116 > const savedPos = this.pos;
1117 >
1118 > // Count empty lines (newlines with only whitespace between)
1119 > let emptyLineCount = 0;
1120 > let foundContent = false;
1121 >
1122 > while (this.pos < this.tokens.length) {
1123 > const t = this.currentToken();
1124 > if (t.type === TokenType.Comment) {
1125 > // Comment terminates a plain scalar yaml.ts ×1
1126 > break;
1127 > }
1128 > if (t.type === TokenType.Newline) { yaml.ts ×4
1129 > this.advance(); yaml.ts ×3
1130 > // Check if the next thing after this newline is blank or content
1131 > const afterNewline = this.currentToken();
1132 > if (afterNewline.type === TokenType.Newline) {
1133 > // Another newline means an empty line yaml.ts ×2
1134 > emptyLineCount++;
1135 > continue;
1136 > }
1137 > if (afterNewline.type === TokenType.Indent) { yaml.ts ×3
1138 > // Check what follows the indent yaml.ts ×5
1139 > const afterIndent = this.peek(1);
1140 > if (afterIndent.type === TokenType.Newline || afterIndent.type === TokenType.EOF) {
1141 // Indent followed by newline = empty line
1142 emptyLineCount++;
1143 this.advance(); // skip the indent
1144 continue;
1145 }
1146 > if (afterIndent.type === TokenType.Comment) { yaml.ts ×5
1147 // Comment terminates scalar
1148 break;
1149 }
1150 > // Content on this line - check indentation yaml.ts ×5
1151 > if (afterNewline.indent > parentIndent) {
1152 > // Valid continuation line yaml.ts ×3
1153 > foundContent = true;
1154 > break;
1155 > } else { yaml.ts ×5
1156 > // Not deep enough - not a continuation yaml.ts ×1
1157 > break;
1158 > }
1159 > } yaml.ts ×5
1160 > if (afterNewline.type === TokenType.EOF) { yaml.ts ×1
1161 > break; yaml.ts ×1
1162 > }
1163 > // Document markers terminate plain scalars yaml.ts ×1
1164 > if (afterNewline.type === TokenType.DocumentStart || afterNewline.type === TokenType.DocumentEnd) { yaml.ts ×3
1165 > break; yaml.ts ×1
1166 > }
1167 > // Content at column 0 yaml.ts ×1
1168 > if (parentIndent < 0) {
1169 > // Top-level: column 0 is valid continuation for parentIndent = -1 yaml.ts ×1
1170 > foundContent = true;
1171 > break;
1172 > }
1173 > break; yaml.ts ×1
1174 > }
1175 > if (t.type === TokenType.Indent) { yaml.ts ×2
1176 // We should only get here at the very start of lookahead when
1177 // the first token after the scalar's end is Indent (no newline before it),
1178 // which shouldn't happen. Break to be safe.
1179 break;
1180 }
1181 > // Any other token (EOF, structural) = end of scalar yaml.ts ×2
1182 > break;
1183 > }
1184 > yaml.ts ×4
1185 > if (!foundContent) {
1186 > // No continuation found - restore position yaml.ts ×1
1187 > this.pos = savedPos;
1188 > break;
1189 > }
1190 > yaml.ts ×2
1191 > // We found a continuation line. Skip optional indent.
1192 > if (this.currentToken().type === TokenType.Indent) {
1193 > this.advance(); yaml.ts ×3
1194 > }
1195 > yaml.ts ×2
1196 > // The next token must be a Scalar for continuation
1197 > if (this.currentToken().type !== TokenType.Scalar) {
1198 > // A dash at a deeper indent than the parent is text content, not a sequence indicator yaml.ts ×2
1199 > // (e.g., "- single multiline\n - sequence entry" → one scalar "single multiline - sequence entry")
1200 > if (this.currentToken().type === TokenType.Dash) {
1201 const dashToken = this.advance();
1202 let lineText = '-';
1203 if (this.currentToken().type === TokenType.Scalar) {
1204 const restToken = this.advance();
1205 lineText = '- ' + restToken.value;
1206 endOffset = restToken.endOffset;
1207 } else {
1208 endOffset = dashToken.endOffset;
1209 }
1210 if (emptyLineCount > 0) {
1211 value += '\n'.repeat(emptyLineCount);
1212 } else {
1213 value += ' ';
1214 }
1215 value += lineText;
1216 continue;
1217 }
1218 > // Not a scalar continuation (could be Colon, etc.) yaml.ts ×2
1219 > this.pos = savedPos;
1220 > break;
1221 > }
1222 > yaml.ts ×3
1223 > // Check that this line doesn't look like a mapping key (scalar followed by colon)
1224 > // which would mean the scalar ended and a new mapping entry starts
1225 > if (this.peek(1).type === TokenType.Colon) {
1226 > this.pos = savedPos; yaml.ts ×3
1227 > break;
1228 > }
1229 > yaml.ts ×2
1230 > const contToken = this.advance();
1231 >
1232 > // Apply line folding: empty lines become \n, single line break becomes space
1233 > if (emptyLineCount > 0) {
1234 > value += '\n'.repeat(emptyLineCount); yaml.ts ×2
1235 > } else { yaml.ts ×2
1236 > value += ' ';
1237 > }
1238 > value += contToken.value;
1239 > endOffset = contToken.endOffset;
1240 > }
1241 > yaml.ts ×4
1242 > return {
1243 > type: 'scalar',
1244 > value,
1245 > rawValue: this.input.substring(firstToken.startOffset, endOffset),
1246 > startOffset: firstToken.startOffset,
1247 > endOffset,
1248 > format: 'none',
1249 > };
1250 > }
1251 > yaml.ts ×54
1252 > // -- Block mapping ---------------------------------------------------
1253 >
1254 > private parseBlockMapping(baseIndent: number, inlineFirstEntry = false): YamlMapNode {
1255 > const startOffset = this.currentToken().startOffset; yaml.ts ×11
1256 > const properties: { key: YamlScalarNode; value: YamlNode }[] = [];
1257 > const seenKeys = new Set<string>();
1258 >
1259 > // When called after a sequence dash, the first key is already at the current position
1260 > if (inlineFirstEntry) {
1261 > const firstEntry = this.parseMappingEntry(baseIndent); yaml.ts ×2
1262 > if (firstEntry) {
1263 > seenKeys.add(firstEntry.key.value);
1264 > properties.push(firstEntry);
1265 > }
1266 > }
1267 > yaml.ts ×11
1268 > while (this.currentToken().type !== TokenType.EOF) {
1269 > this.skipNewlinesAndComments();
1270 > if (this.currentToken().type === TokenType.EOF) { break; }
1271 >
1272 > const indent = this.currentIndent();
1273 > if (indent < baseIndent) { break; }
1274 > if (indent !== baseIndent) {
1275 > if (indent > baseIndent) { yaml.ts ×3
1276 > this.emitError(
1277 > localize('unexpectedIndentation', 'Unexpected indentation (expected {0}, got {1})', baseIndent, indent),
1278 > this.currentToken().startOffset,
1279 > this.currentToken().endOffset,
1280 > 'unexpected-indentation',
1281 > );
1282 > } else {
1283 break;
1284 }
1285 > } yaml.ts ×3
1286 > if (!this.looksLikeMapping()) { break; } yaml.ts ×11
1287 >
1288 > const entry = this.parseMappingEntry(baseIndent);
1289 > if (!entry) { break; }
1290 >
1291 > if (!this.options.allowDuplicateKeys && seenKeys.has(entry.key.value)) {
1292 > this.emitError( yaml.ts ×1
1293 > localize('duplicateKey', 'Duplicate key: "{0}"', entry.key.value),
1294 > entry.key.startOffset,
1295 > entry.key.endOffset,
1296 > 'duplicate-key',
1297 > );
1298 > }
1299 > seenKeys.add(entry.key.value); yaml.ts ×11
1300 > properties.push(entry);
1301 > }
1302 >
1303 > const endOffset = properties.length > 0 ? properties[properties.length - 1].value.endOffset : startOffset;
1304 > return { type: 'map', properties, style: 'block', startOffset, endOffset };
1305 > }
1306 > yaml.ts ×54
1307 > private parseMappingEntry(baseIndent: number): { key: YamlScalarNode; value: YamlNode } | undefined {
1308 > // Skip indent yaml.ts ×11
1309 > if (this.currentToken().type === TokenType.Indent) {
1310 > this.advance(); yaml.ts ×1
1311 > }
1312 > yaml.ts ×11
1313 > // Parse key
1314 > const keyToken = this.expect(TokenType.Scalar);
1315 > const key = this.scalarFromToken(keyToken);
1316 >
1317 > // Expect colon
1318 > const colon = this.expect(TokenType.Colon);
1319 > if (colon.type !== TokenType.Colon) {
1320 this.emitError(localize('expectedColon', 'Expected ":"'), colon.startOffset, colon.endOffset, 'expected-colon');
1321 return undefined;
1322 }
1323 > yaml.ts ×11
1324 > // Parse value: could be on same line or next line (indented)
1325 > const value = this.parseMappingValue(baseIndent, colon);
1326 >
1327 > return { key, value };
1328 > }
1329 > yaml.ts ×54
1330 > private parseMappingValue(baseIndent: number, colonToken: Token): YamlNode {
1331 > // Check if there's a value on the same line after the colon yaml.ts ×11
1332 > const next = this.currentToken();
1333 >
1334 > // Same-line flow collections
1335 > if (next.type === TokenType.FlowMapStart) { return this.parseFlowMap(); }
1336 > if (next.type === TokenType.FlowSeqStart) { return this.parseFlowSeq(); } yaml.ts ×1
1337 > yaml.ts ×1
1338 > // Same-line scalar (may be multiline with continuation)
1339 > if (next.type === TokenType.Scalar) {
1340 > // Skip indent if present (shouldn't be here, but be safe) yaml.ts ×2
1341 > if (this.currentToken().type === TokenType.Indent) {
1342 this.advance();
1343 }
1344 > const token = this.advance(); yaml.ts ×2
1345 > if (token.format !== 'none') {
1346 > return this.scalarFromToken(token); yaml.ts ×1
1347 > }
1348 > // Plain scalar - allow multiline continuation deeper than baseIndent yaml.ts ×1
1349 > return this.parsePlainMultiline(token, baseIndent);
1350 > }
1351 > yaml.ts ×2
1352 > // Value is on the next line (skip newlines/comments and check indentation)
1353 > this.skipNewlinesAndComments();
1354 > const afterNewline = this.currentToken();
1355 >
1356 > if (afterNewline.type === TokenType.EOF) {
1357 > // Missing value at end of input yaml.ts ×1
1358 > this.emitError(localize('missingValue', 'Missing value'), colonToken.startOffset, colonToken.endOffset, 'missing-value');
1359 > return this.makeEmptyScalar(colonToken.endOffset);
1360 > }
1361 > yaml.ts ×2
1362 > const nextIndent = this.currentIndent();
1363 >
1364 > // Special case: a sequence at the same indent as the mapping key is allowed
1365 > // as the mapping value (e.g., "foo:\n- 42")
1366 > if (nextIndent === baseIndent && this.peekPastIndent().type === TokenType.Dash) { yaml.ts ×11
1367 > return this.parseValue(baseIndent) ?? this.makeEmptyScalar(colonToken.endOffset); yaml.ts ×1
1368 > }
1369 > yaml.ts ×1
1370 > if (nextIndent <= baseIndent) {
1371 > // No deeper indentation → missing value yaml.ts ×1
1372 > this.emitError(localize('missingValue', 'Missing value'), colonToken.startOffset, colonToken.endOffset, 'missing-value');
1373 > return this.makeEmptyScalar(colonToken.endOffset);
1374 > }
1375 > yaml.ts ×1
1376 > // Parse the nested value
1377 > return this.parseValue(baseIndent) ?? this.makeEmptyScalar(colonToken.endOffset);
1378 > } yaml.ts ×11
1379 > yaml.ts ×54
1380 > // -- Block sequence --------------------------------------------------
1381 >
1382 > private parseBlockSequence(baseIndent: number): YamlSequenceNode {
1383 > const items: YamlNode[] = []; yaml.ts ×11
1384 > const startOffset = this.currentToken().startOffset;
1385 > let endOffset = startOffset;
1386 > let isFirstItem = true;
1387 >
1388 > while (this.currentToken().type !== TokenType.EOF) {
1389 > this.skipNewlinesAndComments();
1390 > if (this.currentToken().type === TokenType.EOF) { break; }
1391 >
1392 > // For the first item, the dash may be on the same line (no Indent token).
1393 > // Compute the actual column to check against baseIndent.
1394 > let indent: number;
1395 > if (isFirstItem && this.currentToken().type === TokenType.Dash) {
1396 > indent = this.currentToken().startOffset - this.getLineStart(this.currentToken().startOffset); yaml.ts ×1
1397 > } else { yaml.ts ×11
1398 > indent = this.currentIndent(); yaml.ts ×1
1399 > }
1400 > isFirstItem = false; yaml.ts ×11
1401 >
1402 > if (indent < baseIndent) { break; }
1403 >
1404 > if (indent !== baseIndent) {
1405 if (indent > baseIndent) {
1406 this.emitError(
1407 localize('unexpectedIndentation', 'Unexpected indentation (expected {0}, got {1})', baseIndent, indent),
1408 this.currentToken().startOffset,
1409 this.currentToken().endOffset,
1410 'unexpected-indentation',
1411 );
1412 } else {
1413 break;
1414 }
1415 }
1416 > yaml.ts ×11
1417 > const contentToken = this.peekPastIndent();
1418 > if (contentToken.type !== TokenType.Dash) { break; }
1419 >
1420 > // Skip indent
1421 > if (this.currentToken().type === TokenType.Indent) {
1422 > this.advance(); yaml.ts ×1
1423 > }
1424 > yaml.ts ×11
1425 > // Consume the dash
1426 > const dashToken = this.advance();
1427 >
1428 > // Parse the item value
1429 > const itemValue = this.parseSequenceItemValue(baseIndent, dashToken);
1430 > items.push(itemValue);
1431 > endOffset = itemValue.endOffset;
1432 > }
1433 >
1434 > return { type: 'sequence', items, style: 'block', startOffset, endOffset };
1435 > }
1436 > yaml.ts ×54
1437 > private parseSequenceItemValue(baseIndent: number, dashToken: Token): YamlNode {
1438 > const next = this.currentToken(); yaml.ts ×11
1439 >
1440 > // Skip comment after dash
1441 > if (next.type === TokenType.Comment) {
1442 this.advance();
1443 }
1444 > yaml.ts ×11
1445 > // Flow collections on same line
1446 > if (next.type === TokenType.FlowMapStart) { return this.parseFlowMap(); }
1447 > if (next.type === TokenType.FlowSeqStart) { return this.parseFlowSeq(); }
1448 > yaml.ts ×2
1449 > // Nested sequence on same line (e.g., '- - value')
1450 > if (next.type === TokenType.Dash) {
1451 > // The nested sequence's base indent is the column of the dash yaml.ts ×1
1452 > const nestedIndent = next.startOffset - this.getLineStart(next.startOffset);
1453 > return this.parseBlockSequence(nestedIndent);
1454 > }
1455 > yaml.ts ×2
1456 > // Inline scalar on same line
1457 > if (next.type === TokenType.Scalar) {
1458 > // Check if this is actually a mapping (key: value on same line after dash) yaml.ts ×1
1459 > if (this.peek(1).type === TokenType.Colon) {
1460 > // It's an inline mapping after '- ' like '- name: John' yaml.ts ×2
1461 > // The implicit indent for continuation lines is the column of the key
1462 > const itemIndent = next.startOffset - this.getLineStart(next.startOffset);
1463 > return this.parseBlockMapping(itemIndent, true);
1464 > }
1465 > return this.parseScalar(baseIndent); yaml.ts ×1
1466 > }
1467 > yaml.ts ×3
1468 > // Value on next line
1469 > this.skipNewlinesAndComments();
1470 > if (this.currentToken().type === TokenType.EOF) {
1471 this.emitError(localize('missingSeqItemValue', 'Missing sequence item value'), dashToken.startOffset, dashToken.endOffset, 'missing-value');
1472 return this.makeEmptyScalar(dashToken.endOffset);
1473 }
1474 > yaml.ts ×3
1475 > const nextIndent = this.currentIndent();
1476 > if (nextIndent <= baseIndent) {
1477 // Empty item (just a dash)
1478 this.emitError(localize('missingSeqItemValue', 'Missing sequence item value'), dashToken.startOffset, dashToken.endOffset, 'missing-value');
1479 return this.makeEmptyScalar(dashToken.endOffset);
1480 }
1481 > yaml.ts ×3
1482 > return this.parseValue(baseIndent) ?? this.makeEmptyScalar(dashToken.endOffset);
1483 > } yaml.ts ×11
1484 > yaml.ts ×54
1485 > /** Calculate the start of the line containing the given offset */
1486 > private getLineStart(offset: number): number {
1487 > let i = offset - 1; yaml.ts ×2
1488 > while (i >= 0 && this.input[i] !== '\n' && this.input[i] !== '\r') {
1489 > i--; yaml.ts ×1
1490 > }
1491 > return i + 1; yaml.ts ×2
1492 > }
1493 > yaml.ts ×54
1494 > // -- Flow map --------------------------------------------------------
1495 >
1496 > private parseFlowMap(): YamlMapNode {
1497 > const startToken = this.advance(); // consume '{' yaml.ts ×5
1498 > const properties: { key: YamlScalarNode; value: YamlNode }[] = [];
1499 >
1500 > this.skipFlowWhitespace();
1501 >
1502 > while (this.currentToken().type !== TokenType.FlowMapEnd && this.currentToken().type !== TokenType.EOF) {
1503 > // Parse key (must be a scalar) yaml.ts ×8
1504 > let key: YamlScalarNode;
1505 > if (this.currentToken().type === TokenType.Scalar) {
1506 > key = this.parseFlowScalar();
1507 > } else {
1508 this.emitError(localize('expectedMappingKey', 'Expected mapping key'), this.currentToken().startOffset, this.currentToken().endOffset, 'expected-key');
1509 break;
1510 }
1511 > yaml.ts ×8
1512 > this.skipFlowWhitespace();
1513 >
1514 > // Check for colon - if missing, the key has an empty value (terminated by comma or })
1515 > let value: YamlNode;
1516 > if (this.currentToken().type === TokenType.Colon) {
1517 > this.advance();
1518 > this.skipFlowWhitespace();
1519 >
1520 > // Parse value
1521 > value = this.parseFlowValue();
1522 > } else {
1523 // Key without value (e.g., { key, other: val })
1524 value = this.makeEmptyScalar(key.endOffset);
1525 }
1526 > yaml.ts ×8
1527 > properties.push({ key, value });
1528 >
1529 > this.skipFlowWhitespace();
1530 >
1531 > // Consume comma if present
1532 > if (this.currentToken().type === TokenType.Comma) {
1533 > this.advance(); yaml.ts ×1
1534 > this.skipFlowWhitespace();
1535 > }
1536 > } yaml.ts ×8
1537 > yaml.ts ×5
1538 > const endToken = this.currentToken();
1539 > if (endToken.type === TokenType.FlowMapEnd) {
1540 > this.advance();
1541 > } else {
1542 this.emitError(localize('expectedFlowMapEnd', 'Expected "}"'), endToken.startOffset, endToken.endOffset, 'expected-flow-map-end');
1543 }
1544 > yaml.ts ×5
1545 > return {
1546 > type: 'map',
1547 > properties,
1548 > style: 'flow',
1549 > startOffset: startToken.startOffset,
1550 > endOffset: endToken.type === TokenType.FlowMapEnd ? endToken.endOffset : endToken.startOffset,
1551 > };
1552 > }
1553 > yaml.ts ×54
1554 > // -- Flow sequence ---------------------------------------------------
1555 >
1556 > private parseFlowSeq(): YamlSequenceNode {
1557 > const startToken = this.advance(); // consume '[' yaml.ts ×5
1558 > const items: YamlNode[] = [];
1559 >
1560 > this.skipFlowWhitespace();
1561 >
1562 > while (this.currentToken().type !== TokenType.FlowSeqEnd && this.currentToken().type !== TokenType.EOF) {
1563 > let item: YamlNode; yaml.ts ×4
1564 > if (this.currentToken().type === TokenType.FlowMapStart) {
1565 > item = this.parseFlowMap(); yaml.ts ×1
1566 > } else if (this.currentToken().type === TokenType.FlowSeqStart) { yaml.ts ×4
1567 > item = this.parseFlowSeq(); yaml.ts ×1
1568 > } else if (this.currentToken().type === TokenType.Scalar) { yaml.ts ×1
1569 > item = this.parseFlowScalar();
1570 > } else {
1571 > this.emitError(localize('unexpectedTokenInFlowSeq', 'Unexpected token in flow sequence'), this.currentToken().startOffset, this.currentToken().endOffset, 'unexpected-token'); yaml.ts ×1
1572 > this.advance();
1573 > continue;
1574 > }
1575 > yaml.ts ×4
1576 > items.push(item);
1577 > this.skipFlowWhitespace();
1578 >
1579 > if (this.currentToken().type === TokenType.Comma) {
1580 > this.advance(); yaml.ts ×1
1581 > this.skipFlowWhitespace();
1582 > }
1583 > } yaml.ts ×4
1584 > yaml.ts ×5
1585 > const endToken = this.currentToken();
1586 > if (endToken.type === TokenType.FlowSeqEnd) {
1587 > this.advance(); yaml.ts ×2
1588 > } else { yaml.ts ×5
1589 > this.emitError(localize('expectedFlowSeqEnd', 'Expected "]"'), endToken.startOffset, endToken.endOffset, 'expected-flow-seq-end'); yaml.ts ×1
1590 > }
1591 > yaml.ts ×5
1592 > return {
1593 > type: 'sequence',
1594 > items,
1595 > style: 'flow',
1596 > startOffset: startToken.startOffset,
1597 > endOffset: endToken.type === TokenType.FlowSeqEnd ? endToken.endOffset : endToken.startOffset,
1598 > };
1599 > }
1600 > yaml.ts ×54
1601 > /**
1602 > * Parse a scalar inside a flow collection, handling multiline plain scalars.
1603 > * In flow context, plain (unquoted) scalars can span multiple lines;
1604 > * line breaks are folded into spaces.
1605 > */
1606 > private parseFlowScalar(): YamlScalarNode {
1607 > const token = this.advance(); yaml.ts ×2
1608 > // Quoted scalars are complete as-is (scanner handles their multiline folding)
1609 > if (token.format !== 'none') {
1610 > return this.scalarFromToken(token); yaml.ts ×1
1611 > }
1612 > // For unquoted (plain) scalars, fold continuation lines across newlines yaml.ts ×5
1613 > let value = token.value;
1614 > let endOffset = token.endOffset;
1615 >
1616 > while (true) {
1617 > // Look ahead for a newline followed by a plain scalar continuation
1618 > let hasNewline = false;
1619 > let p = this.pos;
1620 > while (p < this.tokens.length) {
1621 > const t = this.tokens[p];
1622 > if (t.type === TokenType.Newline) {
1623 > hasNewline = true; yaml.ts ×3
1624 > p++;
1625 > } else if (t.type === TokenType.Indent || t.type === TokenType.Comment) { yaml.ts ×5
1626 > p++; yaml.ts ×1
1627 > } else { yaml.ts ×5
1628 > break;
1629 > }
1630 > }
1631 >
1632 > if (!hasNewline || p >= this.tokens.length) { break; }
1633 > yaml.ts ×3
1634 > const nextToken = this.tokens[p];
1635 > if (nextToken.type === TokenType.Scalar && nextToken.format === 'none') { yaml.ts ×5
1636 // Fold continuation line into the scalar
1637 this.pos = p + 1;
1638 value += ' ' + nextToken.value;
1639 endOffset = nextToken.endOffset;
1640 > } else { yaml.ts ×3
1641 > break;
1642 > }
1643 > } yaml.ts ×5
1644 >
1645 > return {
1646 > type: 'scalar',
1647 > value,
1648 > rawValue: this.input.substring(token.startOffset, endOffset),
1649 > startOffset: token.startOffset,
1650 > endOffset,
1651 > format: 'none',
1652 > };
1653 > } yaml.ts ×2
1654 > yaml.ts ×54
1655 > /** Parse a value in flow context (used after colon in flow mappings/implicit mappings) */
1656 > private parseFlowValue(): YamlNode {
1657 > if (this.currentToken().type === TokenType.FlowMapStart) { yaml.ts ×8
1658 return this.parseFlowMap();
1659 > } else if (this.currentToken().type === TokenType.FlowSeqStart) { yaml.ts ×8
1660 > return this.parseFlowSeq(); yaml.ts ×1
1661 > } else if (this.currentToken().type === TokenType.Scalar) { yaml.ts ×8
1662 > return this.parseFlowScalar();
1663 > } else {
1664 return this.makeEmptyScalar(this.currentToken().startOffset);
1665 }
1666 > } yaml.ts ×8
1667 > yaml.ts ×54
1668 > /** Skip whitespace, newlines, and comments inside flow collections */
1669 > private skipFlowWhitespace(): void {
1670 > while (true) { yaml.ts ×2
1671 > const t = this.currentToken().type;
1672 > if (t === TokenType.Newline || t === TokenType.Indent || t === TokenType.Comment) {
1673 > this.advance(); yaml.ts ×1
1674 > } else { yaml.ts ×2
1675 > break;
1676 > }
1677 > }
1678 > }
1679 > yaml.ts ×54
1680 > private scalarFromToken(token: Token): YamlScalarNode {
1681 > return { yaml.ts ×1
1682 > type: 'scalar',
1683 > value: token.value,
1684 > rawValue: token.rawValue,
1685 > startOffset: token.startOffset,
1686 > endOffset: token.endOffset,
1687 > format: token.format,
1688 > };
1689 > }
1690 > yaml.ts ×54
1691 > private makeEmptyScalar(offset: number): YamlScalarNode {
1692 > return { yaml.ts ×1
1693 > type: 'scalar',
1694 > value: '',
1695 > rawValue: '',
1696 > startOffset: offset,
1697 > endOffset: offset,
1698 > format: 'none',
1699 > };
1700 > }
1701 > } yaml.ts ×54