or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

data-structures.mdgeneration.mdindex.mdparsing.mdtokenization.mdtraversal.mdutilities.mdvalidation.md
tile.json

traversal.mddocs/

AST Traversal

Comprehensive AST traversal utilities for walking, searching, and manipulating CSS Abstract Syntax Trees.

Capabilities

Walk Function

Traverses an AST using the visitor pattern, calling visitor functions for each node.

/**
 * Traverses AST nodes using visitor pattern
 * @param ast - AST node to traverse
 * @param visitor - Visitor function or visitor object with enter/leave methods
 */
function walk(ast: CssNode, visitor: WalkVisitor | VisitorObject): void;

type WalkVisitor = (node: CssNode, item: ListItem<CssNode>, list: List<CssNode>) => void;

interface VisitorObject {
  /** Called when entering a node */
  enter?: (node: CssNode, item: ListItem<CssNode>, list: List<CssNode>) => void;
  /** Called when leaving a node */
  leave?: (node: CssNode, item: ListItem<CssNode>, list: List<CssNode>) => void;
  /** Node-type specific visitors */
  [nodeType: string]: WalkVisitor;
}

interface ListItem<T> {
  prev: ListItem<T> | null;
  next: ListItem<T> | null;
  data: T;
}

Usage Examples:

import { parse, walk } from 'css-tree';

const ast = parse('.example { color: red; margin: 10px; }');

// Simple visitor function
walk(ast, (node) => {
  console.log(`Node type: ${node.type}`);
});

// Visitor with enter/leave
walk(ast, {
  enter: (node) => {
    console.log(`Entering: ${node.type}`);
  },
  leave: (node) => {
    console.log(`Leaving: ${node.type}`);
  }
});

// Node-type specific visitors
walk(ast, {
  Declaration: (node) => {
    console.log(`Property: ${node.property.name}`);
  },
  ClassSelector: (node) => {
    console.log(`Class: ${node.name}`);
  }
});

Find Functions

Search functions for locating specific nodes within an AST.

/**
 * Finds the first node matching the predicate
 * @param ast - AST node to search
 * @param predicate - Function that returns true for matching nodes
 * @returns First matching node or null
 */
function find(ast: CssNode, predicate: (node: CssNode) => boolean): CssNode | null;

/**
 * Finds the last node matching the predicate
 * @param ast - AST node to search
 * @param predicate - Function that returns true for matching nodes
 * @returns Last matching node or null
 */
function findLast(ast: CssNode, predicate: (node: CssNode) => boolean): CssNode | null;

/**
 * Finds all nodes matching the predicate
 * @param ast - AST node to search
 * @param predicate - Function that returns true for matching nodes
 * @returns Array of all matching nodes
 */
function findAll(ast: CssNode, predicate: (node: CssNode) => boolean): CssNode[];

Usage Examples:

import { parse, find, findLast, findAll } from 'css-tree';

const ast = parse(`
  .example { color: red; }
  .another { color: blue; margin: 10px; }
`);

// Find first color declaration
const firstColor = find(ast, (node) => 
  node.type === 'Declaration' && node.property.name === 'color'
);

// Find last declaration
const lastDeclaration = findLast(ast, (node) => 
  node.type === 'Declaration'
);

// Find all class selectors
const classSelectors = findAll(ast, (node) => 
  node.type === 'ClassSelector'
);

// Find declarations with specific values
const redDeclarations = findAll(ast, (node) => 
  node.type === 'Declaration' && 
  find(node.value, (valueNode) => 
    valueNode.type === 'Identifier' && valueNode.name === 'red'
  )
);

Advanced Traversal Patterns

Complex traversal patterns for AST manipulation and analysis:

// Modify AST during traversal
walk(ast, (node, item, list) => {
  if (node.type === 'Declaration' && node.property.name === 'color') {
    // Replace color value
    walk(node.value, (valueNode, valueItem, valueList) => {
      if (valueNode.type === 'Identifier' && valueNode.name === 'red') {
        valueList.replace(valueItem, {
          type: 'Identifier',
          name: 'blue'
        });
      }
    });
  }
});

// Collect statistics
const stats = {
  rules: 0,
  declarations: 0,
  selectors: 0,
  functions: 0
};

walk(ast, {
  Rule: () => stats.rules++,
  Declaration: () => stats.declarations++,
  Selector: () => stats.selectors++,
  Function: () => stats.functions++
});

// Build selector specificity map
const specificityMap = new Map();

walk(ast, {
  Rule: (node) => {
    const selector = generate(node.prelude);
    const declarations = [];
    
    walk(node.block, {
      Declaration: (declNode) => {
        declarations.push({
          property: declNode.property.name,
          value: generate(declNode.value)
        });
      }
    });
    
    specificityMap.set(selector, declarations);
  }
});

Tree Manipulation During Traversal

Safe AST modification patterns using List methods:

interface List<T> {
  /** Replace an item with new item or list */
  replace(item: ListItem<T>, newItemOrList: T | List<T>): void;
  /** Remove an item from the list */
  remove(item: ListItem<T>): void;
  /** Insert item before specified item */
  insert(item: ListItem<T>, beforeItem: ListItem<T>): void;
  /** Append item to end of list */
  append(item: T): ListItem<T>;
  /** Prepend item to beginning of list */
  prepend(item: T): ListItem<T>;
}

Tree Manipulation Examples:

// Remove declarations
walk(ast, (node, item, list) => {
  if (node.type === 'Declaration' && node.property.name === 'margin') {
    list.remove(item);
  }
});

// Add declarations
walk(ast, {
  Rule: (node) => {
    walk(node.block, {
      DeclarationList: (declList) => {
        // Add new declaration
        declList.children.append({
          type: 'Declaration',
          important: false,
          property: { type: 'Identifier', name: 'display' },
          value: { type: 'Identifier', name: 'block' }
        });
      }
    });
  }
});

// Replace selectors
walk(ast, (node, item, list) => {
  if (node.type === 'ClassSelector' && node.name === 'old-class') {
    list.replace(item, {
      type: 'ClassSelector',
      name: 'new-class'
    });
  }
});

Context-Aware Traversal

Track traversal context for complex transformations:

function walkWithContext(ast, visitors) {
  const context = {
    rule: null,
    atrule: null,
    declaration: null,
    depth: 0
  };
  
  walk(ast, {
    enter: (node) => {
      context.depth++;
      
      if (node.type === 'Rule') {
        context.rule = node;
      } else if (node.type === 'Atrule') {
        context.atrule = node;
      } else if (node.type === 'Declaration') {
        context.declaration = node;
      }
      
      if (visitors[node.type]) {
        visitors[node.type](node, context);
      }
    },
    
    leave: (node) => {
      context.depth--;
      
      if (node.type === 'Rule') {
        context.rule = null;
      } else if (node.type === 'Atrule') {
        context.atrule = null;
      } else if (node.type === 'Declaration') {
        context.declaration = null;
      }
    }
  });
}

// Usage
walkWithContext(ast, {
  Identifier: (node, context) => {
    if (context.declaration && context.declaration.property.name === 'color') {
      console.log(`Color value: ${node.name} in rule: ${generate(context.rule.prelude)}`);
    }
  }
});

Performance Optimization

Optimize traversal for large ASTs:

// Early termination
function findFirstMatch(ast, predicate) {
  let result = null;
  
  walk(ast, (node) => {
    if (result === null && predicate(node)) {
      result = node;
      return false; // Stop traversal
    }
  });
  
  return result;
}

// Selective traversal
function walkDeclarationsOnly(ast, visitor) {
  walk(ast, {
    Declaration: visitor,
    // Skip unnecessary node types for performance
    Comment: () => {},
    WhiteSpace: () => {}
  });
}

// Parallel processing for independent operations
function processRulesInParallel(ast, processor) {
  const rules = findAll(ast, (node) => node.type === 'Rule');
  
  return Promise.all(rules.map(processor));
}

AST Transformation Utilities

Helper functions for common AST transformations:

// Clone subtree
function cloneSubtree(node) {
  const cloned = { ...node };
  
  if (node.children) {
    cloned.children = new List();
    node.children.forEach((child) => {
      cloned.children.append(cloneSubtree(child));
    });
  }
  
  return cloned;
}

// Merge rules with same selector
function mergeIdenticalRules(ast) {
  const ruleMap = new Map();
  
  walk(ast, (node, item, list) => {
    if (node.type === 'Rule') {
      const selector = generate(node.prelude);
      
      if (ruleMap.has(selector)) {
        // Merge declarations
        const existingRule = ruleMap.get(selector);
        walk(node.block, {
          Declaration: (decl) => {
            existingRule.block.children.append(cloneSubtree(decl));
          }
        });
        
        // Remove duplicate rule
        list.remove(item);
      } else {
        ruleMap.set(selector, node);
      }
    }
  });
}