or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-nodes.mddocument-processing.mderror-handling.mdindex.mdparse-stringify.mdparser-infrastructure.mdschema-configuration.mdtree-traversal.mdtype-guards.mdutilities.md

tree-traversal.mddocs/

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

```