0
# Tree Traversal
1
2
Visitor pattern implementation for traversing and transforming YAML ASTs. These functions provide powerful capabilities for analyzing, modifying, and processing YAML document structures programmatically.
3
4
## Capabilities
5
6
### Synchronous Visitor
7
8
Traverse YAML nodes synchronously with full control over the traversal process.
9
10
```typescript { .api }
11
/**
12
* Visit nodes in a YAML tree synchronously
13
* @param node - Root node to start traversal
14
* @param visitor - Visitor function or object
15
*/
16
function visit(node: Node, visitor: visitor): void;
17
18
type visitor = visitorFn | { [key: string]: visitor };
19
20
type visitorFn<T = unknown> = (
21
key: number | string | null,
22
node: T,
23
path: readonly (number | string)[]
24
) => void | symbol | T | Pair<any, T>;
25
26
// Control symbols for visitor functions
27
declare namespace visit {
28
/** Skip visiting children of current node */
29
const SKIP: unique symbol;
30
/** Stop traversal entirely */
31
const BREAK: unique symbol;
32
/** Remove current node from tree */
33
const REMOVE: unique symbol;
34
}
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import { parseDocument, visit, isScalar, isMap, isSeq } from "yaml";
41
42
const doc = parseDocument(`
43
config:
44
name: MyApp
45
version: "1.0.0"
46
features:
47
- authentication
48
- logging
49
- monitoring
50
database:
51
host: localhost
52
port: 5432
53
ssl: true
54
`);
55
56
// Basic traversal - log all nodes
57
visit(doc.contents, (key, node, path) => {
58
console.log(`Path: ${path.join('.')} | Key: ${key} | Type: ${typeof node}`);
59
60
if (isScalar(node)) {
61
console.log(` Scalar value: ${node.value}`);
62
}
63
});
64
65
// Find and collect all string values
66
const strings: string[] = [];
67
visit(doc.contents, (key, node) => {
68
if (isScalar(node) && typeof node.value === 'string') {
69
strings.push(node.value);
70
}
71
});
72
console.log('All strings:', strings);
73
74
// Transform values during traversal
75
visit(doc.contents, (key, node) => {
76
if (isScalar(node) && node.value === 'localhost') {
77
// Return modified scalar
78
return new Scalar('127.0.0.1');
79
}
80
81
if (isScalar(node) && typeof node.value === 'string' && node.value.startsWith('My')) {
82
// Transform string values
83
const newScalar = new Scalar(node.value.toUpperCase());
84
return newScalar;
85
}
86
});
87
```
88
89
### Asynchronous Visitor
90
91
Traverse YAML nodes asynchronously, enabling I/O operations and async processing during traversal.
92
93
```typescript { .api }
94
/**
95
* Visit nodes in a YAML tree asynchronously
96
* @param node - Root node to start traversal
97
* @param visitor - Async visitor function or object
98
*/
99
function visitAsync(node: Node, visitor: asyncVisitor): Promise<void>;
100
101
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };
102
103
type asyncVisitorFn<T = unknown> = (
104
key: number | string | null,
105
node: T,
106
path: readonly (number | string)[]
107
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;
108
109
// Control symbols for async visitor functions
110
declare namespace visitAsync {
111
/** Skip visiting children of current node */
112
const SKIP: unique symbol;
113
/** Stop traversal entirely */
114
const BREAK: unique symbol;
115
/** Remove current node from tree */
116
const REMOVE: unique symbol;
117
}
118
```
119
120
**Usage Examples:**
121
122
```typescript
123
import { parseDocument, visitAsync, isScalar, isMap } from "yaml";
124
125
const doc = parseDocument(`
126
users:
127
- id: 1
128
name: Alice
129
email: alice@example.com
130
- id: 2
131
name: Bob
132
email: bob@example.com
133
config:
134
api_endpoint: "https://api.example.com"
135
timeout: 30
136
`);
137
138
// Async processing with external API calls
139
async function validateEmails() {
140
await visitAsync(doc.contents, async (key, node, path) => {
141
if (isScalar(node) && typeof node.value === 'string' && node.value.includes('@')) {
142
console.log(`Validating email at ${path.join('.')}: ${node.value}`);
143
144
// Simulate async email validation
145
const isValid = await simulateEmailValidation(node.value);
146
147
if (!isValid) {
148
console.log(`Invalid email found: ${node.value}`);
149
// Could transform or mark for removal
150
return new Scalar(`INVALID:${node.value}`);
151
}
152
}
153
});
154
}
155
156
async function simulateEmailValidation(email: string): Promise<boolean> {
157
// Simulate async operation
158
return new Promise(resolve => {
159
setTimeout(() => {
160
resolve(email.includes('@') && email.includes('.'));
161
}, 100);
162
});
163
}
164
165
// Execute async traversal
166
await validateEmails();
167
168
// Async data enrichment
169
await visitAsync(doc.contents, async (key, node, path) => {
170
if (isScalar(node) && key === 'name' && typeof node.value === 'string') {
171
// Simulate fetching additional user data
172
const userData = await fetchUserData(node.value);
173
174
// Add data to parent if it's a map
175
const parentPath = path.slice(0, -1);
176
const parent = doc.getIn(parentPath, true);
177
178
if (isMap(parent)) {
179
parent.set('last_seen', userData.lastSeen);
180
parent.set('status', userData.status);
181
}
182
}
183
});
184
185
async function fetchUserData(name: string) {
186
// Simulate API call
187
return {
188
lastSeen: new Date().toISOString(),
189
status: 'active'
190
};
191
}
192
```
193
194
### Visitor Objects
195
196
Use visitor objects for complex traversal logic with different handlers for different paths.
197
198
```typescript { .api }
199
// Visitor object type
200
type visitor = visitorFn | { [key: string]: visitor };
201
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
import { parseDocument, visit, isScalar, isSeq } from "yaml";
208
209
const doc = parseDocument(`
210
database:
211
connections:
212
- host: db1.example.com
213
port: 5432
214
- host: db2.example.com
215
port: 5432
216
settings:
217
timeout: 30
218
pool_size: 10
219
api:
220
endpoints:
221
- /users
222
- /products
223
rate_limit: 1000
224
`);
225
226
// Complex visitor object with different handlers
227
const configVisitor = {
228
// Handle database configuration
229
database: {
230
connections: (key, node) => {
231
console.log('Processing database connections');
232
if (isSeq(node)) {
233
console.log(`Found ${node.items.length} database connections`);
234
}
235
},
236
237
settings: {
238
timeout: (key, node) => {
239
if (isScalar(node) && typeof node.value === 'number') {
240
console.log(`Database timeout: ${node.value}s`);
241
242
// Warn about low timeouts
243
if (node.value < 10) {
244
console.warn('Database timeout is very low!');
245
}
246
}
247
}
248
}
249
},
250
251
// Handle API configuration
252
api: {
253
endpoints: (key, node) => {
254
if (isSeq(node)) {
255
console.log(`API has ${node.items.length} endpoints`);
256
node.items.forEach((endpoint, index) => {
257
if (isScalar(endpoint)) {
258
console.log(` ${index + 1}: ${endpoint.value}`);
259
}
260
});
261
}
262
},
263
264
rate_limit: (key, node) => {
265
if (isScalar(node) && typeof node.value === 'number') {
266
console.log(`API rate limit: ${node.value} requests`);
267
}
268
}
269
}
270
};
271
272
// Apply visitor object
273
visit(doc.contents, configVisitor);
274
```
275
276
### Control Flow
277
278
Control traversal behavior using special return symbols.
279
280
```typescript
281
import { parseDocument, visit, isMap, isScalar } from "yaml";
282
283
const doc = parseDocument(`
284
public_config:
285
app_name: MyApp
286
version: 1.0.0
287
288
private_config:
289
api_key: secret123
290
database_password: supersecret
291
292
debug_info:
293
logs: enabled
294
trace: disabled
295
`);
296
297
// Skip sensitive sections
298
visit(doc.contents, (key, node, path) => {
299
// Skip private configuration entirely
300
if (key === 'private_config') {
301
console.log('Skipping private configuration');
302
return visit.SKIP; // Don't traverse children
303
}
304
305
// Stop processing after finding debug info
306
if (key === 'debug_info') {
307
console.log('Found debug section, stopping traversal');
308
return visit.BREAK; // Stop entire traversal
309
}
310
311
// Remove trace setting
312
if (key === 'trace') {
313
console.log('Removing trace setting');
314
return visit.REMOVE; // Remove this node
315
}
316
317
// Log other values
318
if (isScalar(node)) {
319
console.log(`${path.join('.')}.${key}: ${node.value}`);
320
}
321
});
322
323
console.log('Final document:');
324
console.log(doc.toString());
325
```
326
327
### Tree Analysis
328
329
Analyze document structure and collect statistics.
330
331
```typescript
332
import { parseDocument, visit, isScalar, isMap, isSeq, isPair } from "yaml";
333
334
interface DocumentStats {
335
scalars: number;
336
maps: number;
337
sequences: number;
338
pairs: number;
339
maxDepth: number;
340
stringValues: string[];
341
numericValues: number[];
342
}
343
344
function analyzeDocument(doc: Document): DocumentStats {
345
const stats: DocumentStats = {
346
scalars: 0,
347
maps: 0,
348
sequences: 0,
349
pairs: 0,
350
maxDepth: 0,
351
stringValues: [],
352
numericValues: []
353
};
354
355
visit(doc.contents, (key, node, path) => {
356
// Track maximum depth
357
stats.maxDepth = Math.max(stats.maxDepth, path.length);
358
359
// Count node types
360
if (isScalar(node)) {
361
stats.scalars++;
362
363
if (typeof node.value === 'string') {
364
stats.stringValues.push(node.value);
365
} else if (typeof node.value === 'number') {
366
stats.numericValues.push(node.value);
367
}
368
}
369
370
else if (isMap(node)) {
371
stats.maps++;
372
}
373
374
else if (isSeq(node)) {
375
stats.sequences++;
376
}
377
378
else if (isPair(node)) {
379
stats.pairs++;
380
}
381
});
382
383
return stats;
384
}
385
386
// Analyze document
387
const doc = parseDocument(`
388
app:
389
name: MyApp
390
version: 1.0
391
features: [auth, logging]
392
users:
393
- name: Alice
394
age: 30
395
- name: Bob
396
age: 25
397
`);
398
399
const stats = analyzeDocument(doc);
400
console.log('Document statistics:', stats);
401
```
402
403
## Visitor Function Types
404
405
```typescript { .api }
406
// Core visitor types
407
type visitorFn<T = unknown> = (
408
key: number | string | null,
409
node: T,
410
path: readonly (number | string)[]
411
) => void | symbol | T | Pair<any, T>;
412
413
type asyncVisitorFn<T = unknown> = (
414
key: number | string | null,
415
node: T,
416
path: readonly (number | string)[]
417
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;
418
419
// Visitor object types
420
type visitor = visitorFn | { [key: string]: visitor };
421
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };
422
423
// Control symbols
424
type VisitControlSymbol = typeof visit.SKIP | typeof visit.BREAK | typeof visit.REMOVE;
425
type AsyncVisitControlSymbol = typeof visitAsync.SKIP | typeof visitAsync.BREAK | typeof visitAsync.REMOVE;
426
```