CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-dustjs-linkedin

Asynchronous templates for the browser and server (LinkedIn fork)

Overview
Eval results
Files

parsing.mddocs/

Template Parsing

Template parsing functionality that converts Dust template source code into Abstract Syntax Trees (AST) using a PEG.js generated parser.

Capabilities

Parse Function

Parses Dust template source into an Abstract Syntax Tree for compilation or analysis.

/**
 * Parses template source into Abstract Syntax Tree
 * @param source - Dust template source code
 * @returns Abstract Syntax Tree object representation
 * @throws SyntaxError with detailed location information for invalid syntax
 */
function parse(source: string): ASTNode;

interface ASTNode {
  /** Node type identifier */
  [0]: string;
  /** Node attributes and properties */
  [1]?: any;
  /** Child nodes array */
  [2]?: ASTNode[];
}

Usage Examples:

const dust = require('dustjs-linkedin');

// Basic template parsing
const source = 'Hello {name}!';
const ast = dust.parse(source);
console.log(ast);
// Output: ['body', {}, [['buffer', 'Hello '], ['reference', ['path', ['key', 'name']], [], {}], ['buffer', '!']]]

// Complex template with sections
const complexSource = `
{#users}
  <li>{name} - {role}</li>
{:else}
  <li>No users</li>
{/users}
`;

try {
  const complexAst = dust.parse(complexSource);
  console.log('AST nodes:', complexAst[2].length);
  console.log('First node type:', complexAst[2][0][0]);
} catch (err) {
  console.error('Parse error:', err.message);
}

AST Node Structure

Understanding the structure of parsed AST nodes for template analysis and manipulation.

// Common AST node types:

// Body node (root container)
type BodyNode = ['body', {}, ASTNode[]];

// Buffer node (static text)
type BufferNode = ['buffer', string];

// Reference node (variable substitution)
type ReferenceNode = ['reference', PathNode, FilterNode[], ParamsNode];

// Section node (conditional/loop)
type SectionNode = ['section', PathNode, ContextNode, ParamsNode, BodyNode];

// Exists/NotExists nodes (conditional checks)
type ExistsNode = ['exists', PathNode, BodyNode];
type NotExistsNode = ['notexists', PathNode, BodyNode];

// Block node (template inheritance)
type BlockNode = ['block', KeyNode, BodyNode];

// Partial node (template inclusion)
type PartialNode = ['partial', PathNode | InlineNode, ContextNode, ParamsNode];

// Helper node (custom function call)
type HelperNode = ['helper', KeyNode, ContextNode, ParamsNode, BodyNode];

// Path node (variable path)
type PathNode = ['path', KeyNode[], FiltersNode[]];

// Key node (identifier)
type KeyNode = ['key', string];

// Inline node (literal value)
type InlineNode = ['inline', string];

Usage Examples:

// Analyze AST structure
function analyzeTemplate(source) {
  const ast = dust.parse(source);

  function walkNodes(node, depth = 0) {
    const indent = '  '.repeat(depth);
    console.log(`${indent}Node: ${node[0]}`);

    if (node[0] === 'reference') {
      const path = node[1];
      console.log(`${indent}  Variable: ${path[1][0][1]}`); // Extract key name
    }

    if (node[0] === 'section') {
      const path = node[1];
      console.log(`${indent}  Section: ${path[1][0][1]}`); // Extract section name
    }

    // Walk child nodes
    if (node[2] && Array.isArray(node[2])) {
      node[2].forEach(child => walkNodes(child, depth + 1));
    }
  }

  walkNodes(ast);
}

// Analyze a template
analyzeTemplate('{#users}{name}{/users}');
// Output:
// Node: body
//   Node: section
//     Section: users
//     Node: body
//       Node: reference
//         Variable: name

Parser Error Handling

Comprehensive error handling with detailed location information for debugging template syntax issues.

interface ParseSyntaxError extends SyntaxError {
  /** Error message describing the syntax issue */
  message: string;
  /** Expected tokens or syntax elements */
  expected: any[];
  /** Actually found token or character */
  found: string;
  /** Location information for the error */
  location: {
    start: { offset: number; line: number; column: number };
    end: { offset: number; line: number; column: number };
  };
  /** Error type name */
  name: 'SyntaxError';
}

Usage Examples:

// Handle parsing errors with location information
function parseWithErrorHandling(source, templateName) {
  try {
    return dust.parse(source);
  } catch (err) {
    if (err instanceof SyntaxError && err.location) {
      console.error(`Parse error in template "${templateName}":
        Message: ${err.message}
        Line: ${err.location.start.line}
        Column: ${err.location.start.column}
        Expected: ${JSON.stringify(err.expected)}
        Found: ${err.found}
        Position: ${err.location.start.offset}`);

      // Show source context
      const lines = source.split('\n');
      const errorLine = lines[err.location.start.line - 1];
      const pointer = ' '.repeat(err.location.start.column - 1) + '^';

      console.error(`Source: ${errorLine}`);
      console.error(`        ${pointer}`);
    } else {
      console.error('Unexpected parse error:', err);
    }
    throw err;
  }
}

// Usage with error handling
try {
  parseWithErrorHandling('{#unclosed', 'my-template');
} catch (err) {
  // Error details logged above, handle appropriately
}

AST Manipulation

Advanced AST manipulation for template transformation and analysis.

/**
 * Filter and transform AST nodes during compilation
 * @param context - Compilation context
 * @param node - AST node to filter
 * @returns Transformed AST node
 */
function filterNode(context: any, node: ASTNode): ASTNode;

Usage Examples:

// Custom AST transformation
function transformTemplate(source) {
  // Parse to AST
  const ast = dust.parse(source);

  // Apply transformations using compiler
  const context = {}; // Compilation context
  const transformedAst = dust.compiler.filterNode(context, ast);

  // Compile back to executable code
  const code = dust.compiler.compileNode(context, transformedAst);

  return code;
}

// Custom node visitor
function visitNodes(node, visitor) {
  // Visit current node
  visitor(node);

  // Visit child nodes recursively
  if (node[2] && Array.isArray(node[2])) {
    node[2].forEach(child => visitNodes(child, visitor));
  }
}

// Example: Find all variable references
function findReferences(source) {
  const ast = dust.parse(source);
  const references = [];

  visitNodes(ast, (node) => {
    if (node[0] === 'reference') {
      const path = node[1];
      if (path && path[1] && path[1][0] && path[1][0][1]) {
        references.push(path[1][0][1]); // Extract variable name
      }
    }
  });

  return references;
}

// Find variables in template
const variables = findReferences('Hello {name}! You have {count} messages.');
console.log(variables); // ['name', 'count']

Template Syntax Analysis

Understanding how different Dust template syntax elements are represented in the AST:

// Variable references: {name}
const refAst = dust.parse('{name}');
// ['body', {}, [['reference', ['path', ['key', 'name']], [], {}]]]

// Sections: {#items}...{/items}
const sectionAst = dust.parse('{#items}Item: {name}{/items}');
// ['body', {}, [['section', ['path', ['key', 'items']], [...], {}]]]

// Conditionals: {?exists}...{/exists}
const existsAst = dust.parse('{?exists}Content{/exists}');
// ['body', {}, [['exists', ['path', ['key', 'exists']], [...]]]]

// Filters: {name|capitalize}
const filteredAst = dust.parse('{name|capitalize}');
// Reference node with filters array populated

// Parameters: {#section param="value"}...{/section}
const paramAst = dust.parse('{#section param="value"}Content{/section}');
// Section node with params object containing parameter definitions

// Blocks: {+title}Default Title{/title}
const blockAst = dust.parse('{+title}Default{/title}');
// ['body', {}, [['block', ['key', 'title'], [...]]]]

// Partials: {>"template" data=context /}
const partialAst = dust.parse('{>"template"/}');
// ['body', {}, [['partial', ['inline', 'template'], [...], {}]]]

// Helpers: {@helper param="value"}...{/helper}
const helperAst = dust.parse('{@helper param="value"}Content{/helper}');
// ['body', {}, [['helper', ['key', 'helper'], [...], {...}, [...]]]]

Advanced Parser Features

Context-Aware Parsing

The parser understands Dust template context and nesting rules:

// Nested sections are properly parsed
const nested = dust.parse(`
  {#users}
    {name}
    {#posts}
      {title}
    {/posts}
  {/users}
`);

// Alternative section syntax
const alternative = dust.parse(`
  {#users}
    Active user: {name}
  {:else}
    No users found
  {/users}
`);

// Complex path references
const paths = dust.parse('{user.profile.name} and {items[0].title}');

Performance Considerations

The parser is optimized for template compilation performance:

  • Templates are typically parsed once during compilation
  • AST structures are lightweight for fast traversal
  • Error reporting includes precise location information
  • Parser supports all Dust syntax features comprehensively
// For performance-critical applications, cache parsed ASTs
const astCache = new Map();

function getCachedAst(source, templateName) {
  if (astCache.has(templateName)) {
    return astCache.get(templateName);
  }

  const ast = dust.parse(source);
  astCache.set(templateName, ast);
  return ast;
}

Install with Tessl CLI

npx tessl i tessl/npm-dustjs-linkedin

docs

cli.md

compilation.md

context.md

filters.md

helpers.md

index.md

parsing.md

rendering.md

tile.json