The Acorn Loose Parser provides error-tolerant parsing of JavaScript code, attempting to recover from syntax errors and produce usable ASTs even from malformed or incomplete source code. This is particularly useful for code editors, linters, and other tools that need to work with potentially broken JavaScript.
import { parse, LooseParser, isDummy } from "acorn-loose";For CommonJS:
const { parse, LooseParser, isDummy } = require("acorn-loose");Parses JavaScript code with error recovery, handling syntax errors gracefully.
/**
* Parses JavaScript source code with error tolerance and recovery
* @param input - JavaScript source code string (potentially malformed)
* @param options - Parser configuration options (same as main acorn parser)
* @returns Program node representing the parsed code with error recovery
*/
function parse(input: string, options: Options): Program;Usage Example:
import { parse } from "acorn-loose";
// This code has syntax errors but can still be parsed
const brokenCode = `
function incomplete() {
let x =
return x + 1
// missing closing brace and semicolons
let y = "unterminated string
console.log(y)
`;
const ast = parse(brokenCode, { ecmaVersion: 2020 });
console.log(ast.type); // "Program"
console.log(ast.body.length); // Still produces a parseable ASTThe LooseParser class extends the main Acorn Parser with error recovery capabilities.
/**
* LooseParser class with error-tolerant parsing capabilities
* Extends the main Parser class with recovery mechanisms
*/
const LooseParser: typeof Parser;The LooseParser class provides the same interface as the main Parser but with different internal behavior for handling errors:
Usage Example:
import { LooseParser } from "acorn-loose";
const brokenCode = "function test() { let x = ; return x }";
const ast = LooseParser.parse(brokenCode, { ecmaVersion: 2020 });
// The parser recovers from the incomplete assignment
console.log(ast.body[0].body.body[0].declarations[0].init); // May be a dummy nodeIdentifies dummy nodes inserted during error recovery.
/**
* Checks if a node is a dummy node inserted by the loose parser
* @param node - AST node to check
* @returns true if the node is a dummy placeholder, false otherwise
*/
function isDummy(node: Node): boolean;Dummy nodes are created with the name "✖" and are inserted when the parser cannot determine what should be present at a particular location.
Usage Example:
import { parse, isDummy } from "acorn-loose";
import { simple } from "acorn-walk";
const brokenCode = "let x = ; console.log(x);";
const ast = parse(brokenCode, { ecmaVersion: 2020 });
// Find all dummy nodes
simple(ast, {
Identifier(node) {
if (isDummy(node)) {
console.log("Found dummy node at position", node.start);
}
}
});The loose parser employs several strategies to recover from syntax errors:
When an expression is expected but not found, a dummy identifier with the name "✖" is inserted.
// Input: "let x = ;"
// Recovers to: "let x = ✖;"Automatically inserts missing semicolons where needed.
// Input: "let x = 5 let y = 10"
// Recovers by treating as: "let x = 5; let y = 10;"Handles functions with missing closing braces or incomplete parameter lists.
// Input: "function test(a, b { return a + b"
// Recovers by adding missing closing parenthesis and braceAttempts to balance unmatched brackets and parentheses.
// Input: "array[index"
// Recovers by adding missing closing bracket: "array[index]"Recovers from incomplete statements by inserting necessary tokens or skipping problematic sections.
The loose parser accepts the same options as the main acorn parser, with some default differences:
interface LooseOptions extends Options {
// Default tabSize is set to 4 for the loose parser
// All other options inherited from main Options interface
}Default Differences:
tabSize: Defaults to 4 (vs undefined in main parser)import { parse, isDummy } from "acorn-loose";
import { simple } from "acorn-walk";
function analyzeCode(sourceCode) {
const ast = parse(sourceCode, {
ecmaVersion: "latest",
sourceType: "module",
locations: true
});
const errors = [];
const functions = [];
simple(ast, {
Identifier(node) {
if (isDummy(node)) {
errors.push({
type: "syntax-error",
position: node.start,
message: "Unexpected syntax"
});
}
},
FunctionDeclaration(node) {
functions.push({
name: node.id?.name || "anonymous",
start: node.start,
end: node.end
});
}
});
return { ast, errors, functions };
}import { parse, isDummy } from "acorn-loose";
import { ancestor } from "acorn-walk";
function lintCode(code) {
const ast = parse(code, { ecmaVersion: 2022 });
const issues = [];
ancestor(ast, {
VariableDeclaration(node, state, ancestors) {
// Check for unused variables, even in broken code
if (node.kind === "var") {
issues.push({
type: "prefer-const-let",
node,
message: "Prefer 'const' or 'let' over 'var'"
});
}
},
Identifier(node, state, ancestors) {
if (isDummy(node)) {
issues.push({
type: "syntax-error",
node,
message: "Syntax error: incomplete expression"
});
}
}
});
return issues;
}import { parse as strictParse } from "acorn";
import { parse as looseParse } from "acorn-loose";
function parseWithFallback(code, options) {
try {
// Try strict parsing first
return {
ast: strictParse(code, options),
hasErrors: false
};
} catch (error) {
// Fall back to loose parsing
return {
ast: looseParse(code, options),
hasErrors: true,
error: error.message
};
}
}
const result = parseWithFallback("let x = ;", { ecmaVersion: 2020 });
if (result.hasErrors) {
console.log("Parsed with errors:", result.error);
}import { parse } from "acorn-loose";
function processTemplate(templateCode) {
// Template might have placeholders that cause syntax errors
const code = templateCode.replace(/\{\{(\w+)\}\}/g, "PLACEHOLDER_$1");
const ast = parse(code, {
ecmaVersion: 2020,
allowReturnOutsideFunction: true
});
// Process the AST even if original template was malformed
return ast;
}
const template = "function process() { return {{value}} + 1; }";
const ast = processTemplate(template);| Feature | Main Parser | Loose Parser |
|---|---|---|
| Syntax errors | Throws exceptions | Recovers with dummy nodes |
| Return outside functions | Error (unless option set) | Allowed |
| Label consistency | Enforced | Not enforced |
| Missing semicolons | May error | Auto-inserted |
| Incomplete constructs | Error | Recovery attempted |
| Performance | Faster | Slightly slower due to recovery |
| Use case | Production parsing | Development tools |
While the loose parser is very tolerant, some limitations exist:
isDummy() when accuracy is important