CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yaml

JavaScript parser and stringifier for YAML documents with complete YAML 1.1 and 1.2 support

Overview
Eval results
Files

tree-traversal.mddocs/

Tree Traversal

Visitor pattern implementation for traversing and transforming YAML ASTs. These functions provide powerful capabilities for analyzing, modifying, and processing YAML document structures programmatically.

Capabilities

Synchronous Visitor

Traverse YAML nodes synchronously with full control over the traversal process.

/**
 * Visit nodes in a YAML tree synchronously
 * @param node - Root node to start traversal
 * @param visitor - Visitor function or object
 */
function visit(node: Node, visitor: visitor): void;

type visitor = visitorFn | { [key: string]: visitor };

type visitorFn<T = unknown> = (
  key: number | string | null,
  node: T,
  path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T>;

// Control symbols for visitor functions
declare namespace visit {
  /** Skip visiting children of current node */
  const SKIP: unique symbol;
  /** Stop traversal entirely */
  const BREAK: unique symbol;
  /** Remove current node from tree */
  const REMOVE: unique symbol;
}

Usage Examples:

import { parseDocument, visit, isScalar, isMap, isSeq } from "yaml";

const doc = parseDocument(`
config:
  name: MyApp
  version: "1.0.0"
  features:
    - authentication
    - logging
    - monitoring
  database:
    host: localhost
    port: 5432
    ssl: true
`);

// Basic traversal - log all nodes
visit(doc.contents, (key, node, path) => {
  console.log(`Path: ${path.join('.')} | Key: ${key} | Type: ${typeof node}`);
  
  if (isScalar(node)) {
    console.log(`  Scalar value: ${node.value}`);
  }
});

// Find and collect all string values
const strings: string[] = [];
visit(doc.contents, (key, node) => {
  if (isScalar(node) && typeof node.value === 'string') {
    strings.push(node.value);
  }
});
console.log('All strings:', strings);

// Transform values during traversal
visit(doc.contents, (key, node) => {
  if (isScalar(node) && node.value === 'localhost') {
    // Return modified scalar
    return new Scalar('127.0.0.1');
  }
  
  if (isScalar(node) && typeof node.value === 'string' && node.value.startsWith('My')) {
    // Transform string values
    const newScalar = new Scalar(node.value.toUpperCase());
    return newScalar;
  }
});

Asynchronous Visitor

Traverse YAML nodes asynchronously, enabling I/O operations and async processing during traversal.

/**
 * Visit nodes in a YAML tree asynchronously
 * @param node - Root node to start traversal
 * @param visitor - Async visitor function or object
 */
function visitAsync(node: Node, visitor: asyncVisitor): Promise<void>;

type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };

type asyncVisitorFn<T = unknown> = (
  key: number | string | null,
  node: T,
  path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;

// Control symbols for async visitor functions
declare namespace visitAsync {
  /** Skip visiting children of current node */
  const SKIP: unique symbol;
  /** Stop traversal entirely */
  const BREAK: unique symbol;
  /** Remove current node from tree */
  const REMOVE: unique symbol;
}

Usage Examples:

import { parseDocument, visitAsync, isScalar, isMap } from "yaml";

const doc = parseDocument(`
users:
  - id: 1
    name: Alice
    email: alice@example.com
  - id: 2
    name: Bob
    email: bob@example.com
config:
  api_endpoint: "https://api.example.com"
  timeout: 30
`);

// Async processing with external API calls
async function validateEmails() {
  await visitAsync(doc.contents, async (key, node, path) => {
    if (isScalar(node) && typeof node.value === 'string' && node.value.includes('@')) {
      console.log(`Validating email at ${path.join('.')}: ${node.value}`);
      
      // Simulate async email validation
      const isValid = await simulateEmailValidation(node.value);
      
      if (!isValid) {
        console.log(`Invalid email found: ${node.value}`);
        // Could transform or mark for removal
        return new Scalar(`INVALID:${node.value}`);
      }
    }
  });
}

async function simulateEmailValidation(email: string): Promise<boolean> {
  // Simulate async operation
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(email.includes('@') && email.includes('.'));
    }, 100);
  });
}

// Execute async traversal
await validateEmails();

// Async data enrichment
await visitAsync(doc.contents, async (key, node, path) => {
  if (isScalar(node) && key === 'name' && typeof node.value === 'string') {
    // Simulate fetching additional user data
    const userData = await fetchUserData(node.value);
    
    // Add data to parent if it's a map
    const parentPath = path.slice(0, -1);
    const parent = doc.getIn(parentPath, true);
    
    if (isMap(parent)) {
      parent.set('last_seen', userData.lastSeen);
      parent.set('status', userData.status);
    }
  }
});

async function fetchUserData(name: string) {
  // Simulate API call
  return {
    lastSeen: new Date().toISOString(),
    status: 'active'
  };
}

Visitor Objects

Use visitor objects for complex traversal logic with different handlers for different paths.

// Visitor object type
type visitor = visitorFn | { [key: string]: visitor };
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };

Usage Examples:

import { parseDocument, visit, isScalar, isSeq } from "yaml";

const doc = parseDocument(`
database:
  connections:
    - host: db1.example.com
      port: 5432
    - host: db2.example.com
      port: 5432
  settings:
    timeout: 30
    pool_size: 10
api:
  endpoints:
    - /users
    - /products
  rate_limit: 1000
`);

// Complex visitor object with different handlers
const configVisitor = {
  // Handle database configuration
  database: {
    connections: (key, node) => {
      console.log('Processing database connections');
      if (isSeq(node)) {
        console.log(`Found ${node.items.length} database connections`);
      }
    },
    
    settings: {
      timeout: (key, node) => {
        if (isScalar(node) && typeof node.value === 'number') {
          console.log(`Database timeout: ${node.value}s`);
          
          // Warn about low timeouts
          if (node.value < 10) {
            console.warn('Database timeout is very low!');
          }
        }
      }
    }
  },
  
  // Handle API configuration
  api: {
    endpoints: (key, node) => {
      if (isSeq(node)) {
        console.log(`API has ${node.items.length} endpoints`);
        node.items.forEach((endpoint, index) => {
          if (isScalar(endpoint)) {
            console.log(`  ${index + 1}: ${endpoint.value}`);
          }
        });
      }
    },
    
    rate_limit: (key, node) => {
      if (isScalar(node) && typeof node.value === 'number') {
        console.log(`API rate limit: ${node.value} requests`);
      }
    }
  }
};

// Apply visitor object
visit(doc.contents, configVisitor);

Control Flow

Control traversal behavior using special return symbols.

import { parseDocument, visit, isMap, isScalar } from "yaml";

const doc = parseDocument(`
public_config:
  app_name: MyApp
  version: 1.0.0
  
private_config:
  api_key: secret123
  database_password: supersecret
  
debug_info:
  logs: enabled
  trace: disabled
`);

// Skip sensitive sections
visit(doc.contents, (key, node, path) => {
  // Skip private configuration entirely
  if (key === 'private_config') {
    console.log('Skipping private configuration');
    return visit.SKIP; // Don't traverse children
  }
  
  // Stop processing after finding debug info
  if (key === 'debug_info') {
    console.log('Found debug section, stopping traversal');
    return visit.BREAK; // Stop entire traversal
  }
  
  // Remove trace setting
  if (key === 'trace') {
    console.log('Removing trace setting');
    return visit.REMOVE; // Remove this node
  }
  
  // Log other values
  if (isScalar(node)) {
    console.log(`${path.join('.')}.${key}: ${node.value}`);
  }
});

console.log('Final document:');
console.log(doc.toString());

Tree Analysis

Analyze document structure and collect statistics.

import { parseDocument, visit, isScalar, isMap, isSeq, isPair } from "yaml";

interface DocumentStats {
  scalars: number;
  maps: number;
  sequences: number;
  pairs: number;
  maxDepth: number;
  stringValues: string[];
  numericValues: number[];
}

function analyzeDocument(doc: Document): DocumentStats {
  const stats: DocumentStats = {
    scalars: 0,
    maps: 0,
    sequences: 0,
    pairs: 0,
    maxDepth: 0,
    stringValues: [],
    numericValues: []
  };
  
  visit(doc.contents, (key, node, path) => {
    // Track maximum depth
    stats.maxDepth = Math.max(stats.maxDepth, path.length);
    
    // Count node types
    if (isScalar(node)) {
      stats.scalars++;
      
      if (typeof node.value === 'string') {
        stats.stringValues.push(node.value);
      } else if (typeof node.value === 'number') {
        stats.numericValues.push(node.value);
      }
    }
    
    else if (isMap(node)) {
      stats.maps++;
    }
    
    else if (isSeq(node)) {
      stats.sequences++;
    }
    
    else if (isPair(node)) {
      stats.pairs++;
    }
  });
  
  return stats;
}

// Analyze document
const doc = parseDocument(`
app:
  name: MyApp
  version: 1.0
  features: [auth, logging]
users:
  - name: Alice
    age: 30
  - name: Bob
    age: 25
`);

const stats = analyzeDocument(doc);
console.log('Document statistics:', stats);

Visitor Function Types

// Core visitor types
type visitorFn<T = unknown> = (
  key: number | string | null,
  node: T,
  path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T>;

type asyncVisitorFn<T = unknown> = (
  key: number | string | null, 
  node: T,
  path: readonly (number | string)[]
) => void | symbol | T | Pair<any, T> | Promise<void | symbol | T | Pair<any, T>>;

// Visitor object types
type visitor = visitorFn | { [key: string]: visitor };
type asyncVisitor = asyncVisitorFn | { [key: string]: asyncVisitor };

// Control symbols
type VisitControlSymbol = typeof visit.SKIP | typeof visit.BREAK | typeof visit.REMOVE;
type AsyncVisitControlSymbol = typeof visitAsync.SKIP | typeof visitAsync.BREAK | typeof visitAsync.REMOVE;

Install with Tessl CLI

npx tessl i tessl/npm-yaml

docs

ast-nodes.md

document-processing.md

error-handling.md

index.md

parse-stringify.md

parser-infrastructure.md

schema-configuration.md

tree-traversal.md

type-guards.md

utilities.md

tile.json