Acorn Loose Parser
raw JSON →acorn-loose is an error-tolerant ECMAScript parser, currently at version 8.5.2, designed to produce an Abstract Syntax Tree (AST) conforming to the ESTree specification even when faced with syntactically invalid JavaScript code. It's part of the broader Acorn project, sharing the same tokenizer as the strict acorn parser, and updates are typically released in tandem with its parent project, following an active but unstated release cadence. Its primary differentiation from acorn is its robust recovery mechanism, which attempts to make sense of malformed input, sometimes by treating whitespace as significant or by inserting placeholder "dummy" nodes, identifiable by the name "✖", where it cannot resolve the syntax. This makes it invaluable for tasks like linting, code analysis, or language services where full parsing is required despite minor errors. However, it's generally recommended to first attempt parsing with the strict acorn parser and only fall back to acorn-loose if syntax errors are encountered, as its error-recovery can occasionally mis-parse valid but unusually indented code, leading to potentially unexpected AST structures.
Common errors
error TypeError: Cannot read properties of undefined (reading 'type') (or similar property access error) ↓
isDummy(node) or implement more robust checks for node existence and expected structure. Be prepared for malformed subtrees resulting from error recovery. error SyntaxError: Unexpected token (X:Y) ↓
acorn.parse(), and if it throws a SyntaxError, then re-attempt parsing the same code with acorn-loose.parse(). error ModuleNotFoundError: Cannot find module 'acorn-loose' or 'acorn' ↓
acorn-loose and acorn are installed (npm install acorn-loose acorn). Verify your tsconfig.json (for TypeScript) or package.json type field and adjust import statements accordingly (ESM import or CommonJS require). Warnings
gotcha acorn-loose may mis-parse valid but weirdly indented files due to its error-recovery mechanisms treating whitespace as significant in some contexts. This can lead to an incorrect AST for otherwise valid code. ↓
gotcha The parser inserts 'dummy' identifier nodes with the name "✖" as placeholders for syntactically incomprehensible parts of the input. Downstream tools must be designed to explicitly handle or skip these placeholder nodes. ↓
gotcha acorn-loose relies on the `acorn` package for its tokenizer. Mismatched major versions between `acorn` and `acorn-loose` could lead to unexpected parsing behavior or runtime errors. ↓
Install
npm install acorn-loose yarn add acorn-loose pnpm add acorn-loose Imports
- parse wrong
const acornLoose = require('acorn-loose'); const ast = acornLoose.parse(...)correctimport { parse } from 'acorn-loose' - LooseParser wrong
const { Parser } = require('acorn-loose')correctimport { LooseParser } from 'acorn-loose' - isDummy wrong
acornLoose.isDummy(node)correctimport { isDummy } from 'acorn-loose'
Quickstart
import { parse, isDummy } from 'acorn-loose';
import { parse as strictParse } from 'acorn'; // Recommended peer dependency for strict parsing
import { generate } from 'astring'; // A common AST utility for code generation
const invalidCode = `
function example(arg1, arg2,) { // Trailing comma in params is okay in ES2017+
if (true { // Missing parenthesis and closing brace
return "hello";
`;
let ast;
try {
// Always attempt strict parsing first for valid code
strictParse(invalidCode, { ecmaVersion: 2020, sourceType: 'module' });
} catch (e: any) {
console.log('Strict parser failed as expected:', e.message.split('\n')[0]);
// Fallback to acorn-loose for error-tolerant parsing of malformed code
ast = parse(invalidCode, { ecmaVersion: 2020, sourceType: 'module' });
console.log('AST generated by acorn-loose (first few nodes):', JSON.stringify(ast.body.slice(0, 2), null, 2));
let dummyCount = 0;
// Simple traversal to find dummy nodes (a dedicated AST walker like acorn.walk is recommended for full traversal)
function traverse(node: any) {
if (node && typeof node === 'object') {
if (isDummy(node)) {
dummyCount++;
}
for (const key in node) {
// Avoid circular references and properties that may not contain nodes (e.g., 'parent')
if (Object.prototype.hasOwnProperty.call(node, key) && key !== 'parent' && typeof node[key] === 'object') {
traverse(node[key]);
}
}
}
}
traverse(ast);
console.log(`Found ${dummyCount} dummy nodes in the AST.`);
console.log('Attempting to regenerate code (may be malformed due to error recovery):\n', generate(ast));
}