yaml.ts ×54

Frontier kind: Code frontier

unlabeled · c_622d3efbafa8

1521 tests · 3679 LOC · 19 files · introduces 0 tests · 301 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
54 ranges301 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
534 ranges3679 lines · 19 files · Browse complete extent
All tests (intent)
1521 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 301 introduced LOC across 54 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/base/common/yaml.ts 301 introduced LOC · 54 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- yaml.ts
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);
24 const tokens = scanner.scan();
26 return parser.parse();
27 }
28 > yaml.ts
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();
35 if (tokens.length === 0 || tokens[0].type !== TokenType.DocumentStart) {
46 return new MarkdownNode(header, body);
47 }
48 > yaml.ts
49 > export class MarkdownNode {
50 > constructor(public readonly header: YamlNode | undefined, public readonly body: string) {
51 }
52 > yaml.ts
53 > getStringValue(name: string): string | undefined {
54 if (this.header && this.header.type === 'map') {
55 const property = this.header.properties.find(p => p.key.value === name);
60 return undefined;
61 }
62 > yaml.ts
63 > getStringArrayValue(name: string): string[] | undefined {
64 if (this.header && this.header.type === 'map') {
65 const property = this.header.properties.find(p => p.key.value === name);
76 return undefined;
77 }
78 > yaml.ts
79 > getBooleanValue(name: string): boolean | undefined {
80 const value = this.getStringValue(name);
81 if (value === 'true') {
86 return undefined;
87 }
88 > } yaml.ts
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.
104 const parsed = parse(`[${value}]`);
118 return items;
119 }
120 > yaml.ts
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(
198 type: TokenType,
211 };
212 }
213 > yaml.ts
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) {
230 this.scanLine();
236 return this.tokens;
237 }
238 > yaml.ts
239 > // Scan a single logical line (up to and including the newline character)
240 > private scanLine(): void {
241 this.seenBlockColon = false;
242 // Handle blank lines / lines that are only whitespace
319 this.scanNewline();
320 }
321 > yaml.ts
322 > private scanLineContent(): void {
323 while (this.pos < this.input.length && this.peekChar() !== '\n' && this.peekChar() !== '\r') {
324 this.skipInlineWhitespace();
376 }
377 }
378 > yaml.ts
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];
382 return next === undefined || next === ' ' || next === '\t' || next === '\n' || next === '\r';
383 }
384 > yaml.ts
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,
388 // subsequent ': ' is part of the scalar value, not a mapping indicator.
394 return false;
395 }
396 > yaml.ts
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];
409 return false;
410 }
411 > yaml.ts
412 > private scanQuotedScalar(quote: '\'' | '"'): void {
413 const start = this.pos;
414 this.pos++; // skip opening quote
567 }));
568 }
569 > yaml.ts
570 > private scanUnquotedScalar(): void {
571 const start = this.pos;
572 let end = this.pos;
598 }));
599 }
600 > yaml.ts
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;
607 // Skip optional indentation indicator (digit 1-9) and chomping indicator (+/-)
619 return c === '\n' || c === '\r' || c === '#';
620 }
621 > yaml.ts
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;
629 this.pos++; // skip '|' or '>'
849 }));
850 }
851 > yaml.ts
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--) {
861 const t = this.tokens[i];
883 return 0;
884 }
885 > yaml.ts
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;
891 let p = offset - 1;
896 return col;
897 }
898 > yaml.ts
899 > private scanComment(): void {
900 const start = this.pos;
901 while (this.pos < this.input.length && this.input[this.pos] !== '\n' && this.input[this.pos] !== '\r') {
907 }));
908 }
909 > yaml.ts
910 > private scanNewline(): void {
911 const start = this.pos;
912 if (this.consumeNewline()) {
914 }
915 }
916 > yaml.ts
917 > private skipInlineWhitespace(): void {
918 while (this.pos < this.input.length) {
919 const ch = this.input[this.pos];
925 }
926 }
927 > yaml.ts
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; }
931 if (this.input[this.pos] === '\r' && this.input[this.pos + 1] === '\n') {
939 return false;
940 }
941 > yaml.ts
942 > private peekChar(): string {
943 return this.input[this.pos];
944 }
945 > } yaml.ts
946 >
947 > // -- Parser ------------------------------------------------------------------
948 >
949 > class YamlParser {
950 > private pos = 0;
951 >
952 > constructor(
953 private readonly tokens: Token[],
954 private readonly input: string,
956 private readonly options: ParseOptions,
957 ) { }
958 > yaml.ts
959 > parse(): YamlNode | undefined {
960 this.skipNewlinesAndComments();
961 // Skip document start marker (---) if present
970 return result;
971 }
972 > yaml.ts
973 > // -- helpers ----------------------------------------------------------
974 >
975 > private currentToken(): Token {
976 return this.tokens[this.pos];
977 }
978 > yaml.ts
979 > private peek(offset = 0): Token {
980 return this.tokens[Math.min(this.pos + offset, this.tokens.length - 1)];
981 }
982 > yaml.ts
983 > private advance(): Token {
984 const t = this.tokens[this.pos];
985 if (t.type !== TokenType.EOF) {
988 return t;
989 }
990 > yaml.ts
991 > private expect(type: TokenType): Token {
992 const t = this.currentToken();
993 if (t.type === type) {
996 return t;
997 }
998 > yaml.ts
999 > private emitError(message: string, startOffset: number, endOffset: number, code: string): void {
1000 this.errors.push({ message, startOffset, endOffset, code });
1001 }
1002 > yaml.ts
1003 > private skipNewlinesAndComments(): void {
1004 while (
1005 this.currentToken().type === TokenType.Newline ||
1010 }
1011 }
1012 > yaml.ts
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);
1016 return next.type === TokenType.Newline || next.type === TokenType.Comment || next.type === TokenType.EOF;
1017 }
1018 > yaml.ts
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) {
1026 return this.currentToken().indent;
1028 return 0;
1029 }
1030 > yaml.ts
1031 > // -- Main parse entry for a value at a given indentation --------------
1032 >
1033 > private parseValue(parentIndent: number): YamlNode | undefined {
1034 this.skipNewlinesAndComments();
1035 const token = this.currentToken();
1065 return undefined;
1066 }
1067 > yaml.ts
1068 > /** Peek past an optional Indent token to see the first content token */
1069 > private peekPastIndent(): Token {
1070 if (this.currentToken().type === TokenType.Indent) {
1071 return this.peek(1);
1073 return this.currentToken();
1074 }
1075 > yaml.ts
1076 > /** Check if tokens at current position look like a mapping entry (key: value) */
1077 > private looksLikeMapping(): boolean {
1078 let offset = 0;
1079 if (this.peek(offset).type === TokenType.Indent) { offset++; }
1084 return false;
1085 }
1086 > yaml.ts
1087 > // -- Scalar ----------------------------------------------------------
1088 >
1089 > private parseScalar(parentIndent: number = -1): YamlScalarNode {
1090 // Skip indent if present
1091 if (this.currentToken().type === TokenType.Indent) {
1100 return this.parsePlainMultiline(token, parentIndent);
1101 }
1102 > yaml.ts
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;
1112 let endOffset = firstToken.endOffset;
1249 };
1250 }
1251 > yaml.ts
1252 > // -- Block mapping ---------------------------------------------------
1253 >
1254 > private parseBlockMapping(baseIndent: number, inlineFirstEntry = false): YamlMapNode {
1255 const startOffset = this.currentToken().startOffset;
1256 const properties: { key: YamlScalarNode; value: YamlNode }[] = [];
1304 return { type: 'map', properties, style: 'block', startOffset, endOffset };
1305 }
1306 > yaml.ts
1307 > private parseMappingEntry(baseIndent: number): { key: YamlScalarNode; value: YamlNode } | undefined {
1308 // Skip indent
1309 if (this.currentToken().type === TokenType.Indent) {
1327 return { key, value };
1328 }
1329 > yaml.ts
1330 > private parseMappingValue(baseIndent: number, colonToken: Token): YamlNode {
1331 // Check if there's a value on the same line after the colon
1332 const next = this.currentToken();
1377 return this.parseValue(baseIndent) ?? this.makeEmptyScalar(colonToken.endOffset);
1378 }
1379 > yaml.ts
1380 > // -- Block sequence --------------------------------------------------
1381 >
1382 > private parseBlockSequence(baseIndent: number): YamlSequenceNode {
1383 const items: YamlNode[] = [];
1384 const startOffset = this.currentToken().startOffset;
1434 return { type: 'sequence', items, style: 'block', startOffset, endOffset };
1435 }
1436 > yaml.ts
1437 > private parseSequenceItemValue(baseIndent: number, dashToken: Token): YamlNode {
1438 const next = this.currentToken();
1439
1482 return this.parseValue(baseIndent) ?? this.makeEmptyScalar(dashToken.endOffset);
1483 }
1484 > yaml.ts
1485 > /** Calculate the start of the line containing the given offset */
1486 > private getLineStart(offset: number): number {
1487 let i = offset - 1;
1488 while (i >= 0 && this.input[i] !== '\n' && this.input[i] !== '\r') {
1491 return i + 1;
1492 }
1493 > yaml.ts
1494 > // -- Flow map --------------------------------------------------------
1495 >
1496 > private parseFlowMap(): YamlMapNode {
1497 const startToken = this.advance(); // consume '{'
1498 const properties: { key: YamlScalarNode; value: YamlNode }[] = [];
1551 };
1552 }
1553 > yaml.ts
1554 > // -- Flow sequence ---------------------------------------------------
1555 >
1556 > private parseFlowSeq(): YamlSequenceNode {
1557 const startToken = this.advance(); // consume '['
1558 const items: YamlNode[] = [];
1598 };
1599 }
1600 > yaml.ts
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();
1608 // Quoted scalars are complete as-is (scanner handles their multiline folding)
1652 };
1653 }
1654 > yaml.ts
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) {
1658 return this.parseFlowMap();
1665 }
1666 }
1667 > yaml.ts
1668 > /** Skip whitespace, newlines, and comments inside flow collections */
1669 > private skipFlowWhitespace(): void {
1670 while (true) {
1671 const t = this.currentToken().type;
1677 }
1678 }
1679 > yaml.ts
1680 > private scalarFromToken(token: Token): YamlScalarNode {
1681 return {
1682 type: 'scalar',
1688 };
1689 }
1690 > yaml.ts
1691 > private makeEmptyScalar(offset: number): YamlScalarNode {
1692 return {
1693 type: 'scalar',