or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-acorn-loose

Error-tolerant ECMAScript parser that parses any text into an ESTree syntax tree with reasonable approximations for syntax errors

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/acorn-loose@8.5.x

To install, run

npx @tessl/cli install tessl/npm-acorn-loose@8.5.0

index.mddocs/

Acorn Loose

Acorn Loose provides an error-tolerant ECMAScript parser that can parse any text into an ESTree syntax tree, making reasonable approximations when encountering syntax errors. It serves as a fallback parser when the regular acorn parser encounters syntax errors, using whitespace as significant for recovery from missing brackets.

Package Information

  • Package Name: acorn-loose
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install acorn-loose

Core Imports

import { parse, LooseParser, isDummy, dummyValue } from "acorn-loose";

For CommonJS:

const { parse, LooseParser, isDummy, dummyValue } = require("acorn-loose");

Basic Usage

import { parse, isDummy, dummyValue } from "acorn-loose";

// Parse malformed JavaScript code that would fail with regular acorn
const ast = parse("1 / * 4 )[2]", {
  ecmaVersion: 2020,
  locations: true
});

// Walk the AST to find dummy placeholder nodes
function walkAST(node) {
  if (!node || typeof node !== 'object') return;
  
  if (isDummy(node)) {
    console.log(`Found dummy node "${dummyValue}" where parser couldn't recover:`, node);
    return;
  }
  
  // Recursively walk child nodes
  for (let key in node) {
    if (Array.isArray(node[key])) {
      node[key].forEach(walkAST);
    } else if (node[key] && typeof node[key] === 'object' && node[key].type) {
      walkAST(node[key]);
    }
  }
}

walkAST(ast);
console.log("Parsed AST:", JSON.stringify(ast, null, 2));

Architecture

Acorn Loose is built around several key components:

  • Error Recovery System: Comprehensive tokenization error handling that attempts to continue parsing after syntax errors
  • Dummy Node Insertion: Places placeholder nodes with name "✖" (dummyValue) when parsing fails completely
  • Indentation-Based Recovery: Uses whitespace indentation to infer missing brackets and delimiters - maintains a context stack of indentation levels to determine when blocks should close
  • Lookahead Tokenization: Maintains a buffer of future tokens to enable better error recovery decisions
  • Plugin Compatibility: Maintains compatibility with the acorn parser's plugin system while extending error tolerance
  • Statement & Expression Parsing: Separate parsing modules for statements and expressions with comprehensive error tolerance

Capabilities

Parse Function

Main parsing function that accepts any input and returns an ESTree-compliant AST, inserting dummy nodes where syntax errors prevent normal parsing.

/**
 * Parses input string using the loose parser with error tolerance
 * @param {string} input - The JavaScript code to parse
 * @param {Object} options - Parsing options (same as acorn parser options)
 * @returns {Program} ESTree Program node representing the parsed code
 */
function parse(input, options);

Usage Examples:

import { parse } from "acorn-loose";

// Parse valid code (works like regular acorn)
const validAst = parse("const x = 1;", { ecmaVersion: 2020 });

// Parse invalid code with error recovery
const invalidAst = parse("const x = 1 +;", { ecmaVersion: 2020 });
// Parser will insert dummy nodes to complete the expression

// Parse with locations for error reporting
const astWithLoc = parse("function() {", { 
  ecmaVersion: 2020, 
  locations: true 
});

LooseParser Class

The main loose parser class for advanced usage, plugin development, and error-tolerant parsing with full control. Most users should use the standalone parse() function instead.

/**
 * Error-tolerant parser class with plugin support
 */
class LooseParser {
  /**
   * Creates a new parser instance
   * @param {string} input - Input code to parse
   * @param {Object} options - Parser options
   */
  constructor(input, options);

  /**
   * Parses the input and returns the AST
   * @returns {Program} The parsed program AST
   */
  parse();

  /**
   * Creates a new AST node at current position
   * @returns {Node} Empty AST node with position information
   */
  startNode();

  /**
   * Stores current parser position for later use
   * @returns {number|Array} Position information (number or [start, loc] if locations enabled)
   */
  storeCurrentPos();

  /**
   * Creates a new AST node at specific position
   * @param {number|Array} pos - Position from storeCurrentPos()
   * @returns {Node} Empty AST node at specified position
   */
  startNodeAt(pos);

  /**
   * Completes an AST node with the specified type
   * @param {Node} node - The node to complete
   * @param {string} type - The AST node type
   * @returns {Node} The completed node
   */
  finishNode(node, type);

  /**
   * Creates a dummy placeholder node when parsing fails
   * @param {string} type - The type of dummy node to create
   * @returns {Node} Dummy node with placeholder values
   */
  dummyNode(type);

  /**
   * Creates a dummy identifier node with name "✖"
   * @returns {Identifier} Dummy identifier node
   */
  dummyIdent();

  /**
   * Creates a dummy string literal node
   * @returns {Literal} Dummy string literal node
   */
  dummyString();

  /**
   * Consumes a token if it matches the expected type
   * @param {TokenType} type - Expected token type
   * @returns {boolean} True if token was consumed
   */
  eat(type);

  /**
   * Expects and consumes a token, with error recovery if not found
   * @param {TokenType} type - Expected token type
   * @returns {boolean} True if token was found and consumed (may advance multiple tokens for recovery)
   */
  expect(type);

  /**
   * Checks if current token is a contextual keyword
   * @param {string} name - The contextual keyword to check
   * @returns {boolean} True if current token matches the contextual keyword
   */
  isContextual(name);

  /**
   * Consumes a contextual keyword if present
   * @param {string} name - The contextual keyword to consume
   * @returns {boolean} True if contextual keyword was consumed
   */
  eatContextual(name);

  /**
   * Checks if a semicolon can be inserted at current position
   * @returns {boolean} True if semicolon insertion is allowed
   */
  canInsertSemicolon();

  /**
   * Handles semicolon consumption (equivalent to eat(tt.semi))
   * @returns {boolean} True if semicolon was consumed
   */
  semicolon();

  /**
   * Pushes current indentation level onto context stack
   */
  pushCx();

  /**
   * Pops indentation level from context stack and restores it
   */
  popCx();

  /**
   * Advances to the next token with line tracking and context management
   */
  next();

  /**
   * Looks ahead at future tokens without consuming them
   * @param {number} n - Number of tokens to look ahead (1-based index)
   * @returns {Token} The token at position n ahead of current position
   */
  lookAhead(n);

  /**
   * Resets tokenizer to a specific position for error recovery
   * @param {number} pos - Character position in input to reset to
   */
  resetTo(pos);

  /**
   * Extends the parser instance with additional functionality
   * @param {string} name - Method name to extend/replace
   * @param {Function} f - Function that receives current method and returns new method
   */
  extend(name, f);

  /**
   * Extends the parser class with plugins
   * @param {...Function} plugins - Plugin functions to apply
   * @returns {Function} New parser class constructor
   */
  static extend(...plugins);

  /**
   * Static parse method that creates parser instance and parses
   * @param {string} input - Input code to parse
   * @param {Object} options - Parser options
   * @returns {Program} Parsed program AST
   */
  static parse(input, options);

  /**
   * Base parser class used for tokenization (can be changed for custom parsers)
   */
  static BaseParser;
}

Usage Examples:

import { LooseParser } from "acorn-loose";

// Use parser class directly
const parser = new LooseParser("const x = 1;", { ecmaVersion: 2020 });
const ast = parser.parse();

// Use static parse method
const ast2 = LooseParser.parse("const y = 2;", { ecmaVersion: 2020 });

// Extend with plugins
const ExtendedParser = LooseParser.extend(somePlugin);
const ast3 = ExtendedParser.parse("const z = 3;", { ecmaVersion: 2020 });

Dummy Node Detection

Utility function to check if a node is a dummy placeholder inserted by the parser during error recovery.

/**
 * Checks if a node is a dummy placeholder node
 * @param {Node} node - AST node to check
 * @returns {boolean} True if the node is a dummy placeholder
 */
function isDummy(node);

/**
 * The dummy value used as placeholder text in dummy nodes
 * @type {string}
 */
const dummyValue = "✖";

Usage Examples:

import { parse, isDummy, dummyValue } from "acorn-loose";

const ast = parse("const x = 1 +;", { ecmaVersion: 2020 });

// Walk the AST and find dummy nodes
function findDummyNodes(node) {
  if (isDummy(node)) {
    console.log(`Found dummy node with value "${dummyValue}" at:`, node.loc);
    return;
  }
  
  // Recursively check child nodes
  for (let key in node) {
    if (node[key] && typeof node[key] === 'object') {
      if (Array.isArray(node[key])) {
        node[key].forEach(findDummyNodes);
      } else if (node[key].type) {
        findDummyNodes(node[key]);
      }
    }
  }
}

findDummyNodes(ast);

Error Recovery Strategy

The parser employs several error recovery mechanisms:

  • Token Error Recovery: When tokenization fails, attempts to skip problematic characters and continue
  • Dummy Node Insertion: Creates placeholder nodes with name "✖" when parsing cannot continue
  • Whitespace-based Recovery: Uses indentation to infer missing brackets and delimiters
  • Context Preservation: Maintains parsing state across errors to provide meaningful approximations
  • Comprehensive Error Handling: Handles unterminated strings, invalid regexes, malformed numbers, and syntax errors

Plugin System

Acorn Loose supports the same plugin system as the regular acorn parser:

import { LooseParser } from "acorn-loose";

// Create extended parser with plugins
const ExtendedParser = LooseParser.extend(plugin1, plugin2);

// Parse with extended functionality
const ast = ExtendedParser.parse(code, options);

// Change the base parser (affects all loose parser instances)
LooseParser.BaseParser = CustomAcornParser;

Options

Accepts all standard acorn parser options:

  • ecmaVersion: ECMAScript version to parse (3, 5, 6, 2015-2023, "latest")
  • sourceType: "script" or "module"
  • allowReturnOutsideFunction: Allow return statements outside functions
  • allowHashBang: Allow hashbang comments
  • locations: Include location information in AST nodes
  • ranges: Include range information in AST nodes
  • preserveParens: Preserve parentheses in AST
  • allowAwaitOutsideFunction: Allow await outside async functions

Types

// Token types (from acorn)
interface Token {
  type: TokenType;
  value: any;
  start: number;
  end: number;
  loc?: SourceLocation;
}

interface SourceLocation {
  start: Position;
  end: Position;
}

interface Position {
  line: number;
  column: number;
}

// Parser options (extends acorn options)
interface Options {
  ecmaVersion?: 3 | 5 | 6 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | "latest";
  sourceType?: "script" | "module";
  allowReturnOutsideFunction?: boolean;
  allowHashBang?: boolean;
  allowAwaitOutsideFunction?: boolean;
  locations?: boolean;
  ranges?: boolean;
  preserveParens?: boolean;
  onComment?: Function;
  onToken?: Function;
}

// AST node types follow ESTree specification
interface Node {
  type: string;
  start: number;
  end: number;
  loc?: SourceLocation;
  range?: [number, number];
}

interface Program extends Node {
  type: "Program";
  body: Statement[];
  sourceType: "script" | "module";
}

interface Identifier extends Node {
  type: "Identifier";
  name: string;
}

interface Literal extends Node {
  type: "Literal";
  value: any;
  raw: string;
}