Error-tolerant ECMAScript parser that parses any text into an ESTree syntax tree with reasonable approximations for syntax errors
npx @tessl/cli install tessl/npm-acorn-loose@8.5.0Acorn 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.
npm install acorn-looseimport { parse, LooseParser, isDummy, dummyValue } from "acorn-loose";For CommonJS:
const { parse, LooseParser, isDummy, dummyValue } = require("acorn-loose");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));Acorn Loose is built around several key components:
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
});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 });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);The parser employs several error recovery mechanisms:
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;Accepts all standard acorn parser options:
// 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;
}