# How BiomeJS Handles JavaScript: Inside the Lossless, Error-Tolerant Parser

> Discover how BiomeJS handles JavaScript with its lossless, error-tolerant parser. Learn how it builds a syntax tree retaining all details for zero-cost AST conversions and resilient linting.

- Repository: [Biome/biome](https://github.com/biomejs/biome)
- Tags: internals
- Published: 2026-06-19

---

**BiomeJS handles JavaScript through a lossless, error-tolerant parser that emits events to build a syntax tree retaining all whitespace and comments, enabling zero-cost AST conversions and resilient linting even on broken code.**

BiomeJS processes JavaScript using a sophisticated parser architecture that prioritizes error recovery and complete source fidelity. Unlike traditional parsers that construct an AST directly from tokens, the BiomeJS parser—implemented in the `biome_js_parser` crate—generates a stream of parsing events consumed by a tree-sink to construct an untyped syntax tree. This design, adapted from the Rust Analyzer parser, supports fast incremental reparsing while preserving every detail of the source text.

## Core Architecture of the BiomeJS Parser

The parser is organized into distinct components that separate tokenization, event generation, and tree construction.

### Lexer and TokenSource

The **Lexer** ([`crates/biome_js_parser/src/lexer/mod.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/lexer/mod.rs)) transforms raw source strings into a stream of `JsSyntaxKind` tokens. It handles complex re-lexing contexts required for modern JavaScript, including JSX, template literals, and regular expressions. The lexer works with `JsTokenSource` to provide the parser with lookahead and token consumption capabilities.

### Event-Driven Parsing with JsParser

At the heart of the system is the **`JsParser`** struct defined in [`src/parser.rs`](https://github.com/biomejs/biome/blob/main/src/parser.rs) (line 31). This component maintains the parser state (`JsParserState`), source type (`JsFileSource`), and configuration options (`JsParserOptions`). Rather than building a tree directly, the parser emits **events**—such as `Start`, `Token`, `Error`, and `Finish`—that describe the structure of the code. The grammar logic, generated from `.ungram` files, drives the `syntax::program::parse` entry point to produce these events.

### Lossless Tree Construction

The **`JsLosslessTreeSink`** ([`src/lib.rs`](https://github.com/biomejs/biome/blob/main/src/lib.rs), line 18) consumes the event stream to construct a **green tree** (`JsSyntaxNode`). This tree is lossless, meaning it retains all whitespace, comments, and other trivia, allowing the formatter to reconstruct the exact original source or apply precise transformations.

### Syntax Tree vs. Typed AST

BiomeJS maintains a strict separation between the untyped syntax tree and the typed AST:

- **Untyped layer**: `JsSyntaxNode` objects store the raw tree structure with `JsSyntaxKind` discriminants.
- **Typed layer**: Thin wrapper structs like `JsScript`, `JsModule`, and `JsIfStatement` cast the untyped nodes via `cast` methods without allocating new memory.

This zero-cost conversion pattern is defined in [`crates/biome_js_syntax/src/lib.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_syntax/src/lib.rs) and allows tools to work with convenient typed interfaces while preserving the underlying lossless representation.

### Error Recovery Mechanism

BiomeJS achieves **error tolerance** by ensuring every token always becomes part of the final tree. When the parser encounters unexpected input, it wraps the error in an `ERROR` node and continues parsing ([`src/parser.rs`](https://github.com/biomejs/biome/blob/main/src/parser.rs), lines 29-34). This guarantees that linting and formatting tools receive a complete tree even for syntactically invalid JavaScript.

## The Parsing Flow in BiomeJS

When you call a parsing function, the system executes a precise sequence of steps:

1. **Entry point**: Functions like `parse_script` or `parse_module` in [`src/parse.rs`](https://github.com/biomejs/biome/blob/main/src/parse.rs) (lines 87-95) accept source text and `JsParserOptions`.
2. **Parser construction**: `JsParser::new` initializes a `JsTokenSource` and empty `ParserContext`.
3. **Grammar parsing**: The `program::parse` function walks the JavaScript grammar and emits events to the parser's event buffer.
4. **Finalization**: `parser.finish()` returns the event list, trivia information, and any diagnostics.
5. **Tree building**: `JsLosslessTreeSink::with_cache` processes the events to construct the green tree (`JsSyntaxNode`).
6. **Result**: A `Parse<T>` struct wraps the root node and diagnostics, exposing `tree()` for typed AST access and `diagnostics()` for error inspection.

## Working with the BiomeJS Parser API

The crate exposes three primary high-level APIs for different use cases:

```rust
use biome_js_parser::{JsParserOptions, parse_script, parse_module};
use biome_js_syntax::{JsSyntaxKind, JsIfStatement};
use biome_rowan::TextSize;

// 1. Parse a classic script (non-module)
let script = parse_script(
    "if (a > 5) { console.log(a); }",
    JsParserOptions::default(),
);

// Walk the untyped tree and cast to typed AST
let if_stmt = script
    .syntax()
    .children()
    .find(|c| c.kind() == JsSyntaxKind::JS_IF_STATEMENT)
    .unwrap();

let typed_if: JsIfStatement = JsIfStatement::cast(if_stmt).unwrap();
assert_eq!(typed_if.syntax().kind(), JsSyntaxKind::JS_IF_STATEMENT);

// 2. Parse an ECMAScript module
let module = parse_module(
    r#"
        import { foo } from "./bar.js";
        export default foo;
    "#,
    JsParserOptions::default(),
);
assert!(!module.has_errors());

// 3. Offset-aware parsing for embedded JavaScript (e.g., inside HTML <script> tags)
let offset = TextSize::from(100);
let offset_parse = biome_js_parser::parse_js_with_offset(
    "console.log('hello');",
    offset,
    biome_languages::JsFileSource::js_module(),
    JsParserOptions::default(),
);
assert_eq!(offset_parse.base_offset(), offset);

```

## Configuration and Language Features

The parser supports JavaScript variants through **`JsParserOptions`** ([`src/options.rs`](https://github.com/biomejs/biome/blob/main/src/options.rs)). This configuration struct enables or disables:

- **Strict mode** parsing
- **TypeScript** support
- **JSX** syntax
- **Module** vs. **Script** source types

The `JsFileSource` type determines the base language variant, while the lexer handles context-sensitive re-lexing for JSX expressions and TypeScript type annotations.

## Key Source Files

| File | Description |
|------|-------------|
| [`crates/biome_js_parser/src/lib.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/lib.rs) | Public façade, re-exports, and high-level parsing entry points |
| [`crates/biome_js_parser/src/parser.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/parser.rs) | Core `JsParser` struct, state handling, and event generation |
| [`crates/biome_js_parser/src/parse.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/parse.rs) | Helper functions (`parse_script`, `parse_module`, `parse_js_with_offset`) and `Parse<T>` wrapper |
| [`crates/biome_js_parser/src/lexer/mod.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/lexer/mod.rs) | Tokenization logic with re-lexing for JSX and TypeScript contexts |
| [`crates/biome_js_parser/src/syntax/program.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/syntax/program.rs) | Grammar entry point that drives full program parsing |
| [`crates/biome_js_parser/src/options.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_parser/src/options.rs) | Configuration options for language features |
| [`crates/biome_js_syntax/src/lib.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_syntax/src/lib.rs) | Untyped `JsSyntaxNode`/`JsSyntaxKind` definitions and typed AST wrappers |
| [`crates/biome_js_formatter/src/lib.rs`](https://github.com/biomejs/biome/blob/main/crates/biome_js_formatter/src/lib.rs) | Demonstrates consumption of the lossless syntax tree for formatting |

## Summary

- **BiomeJS handles JavaScript** using an event-driven, lossless parser that preserves all source trivia including whitespace and comments.
- The **tree-sink architecture** separates parsing logic from tree construction, enabling error recovery by wrapping invalid tokens in `ERROR` nodes.
- **Zero-cost abstractions** allow casting between untyped `JsSyntaxNode` trees and typed AST nodes like `JsScript` without memory allocation.
- **High-level APIs** (`parse_script`, `parse_module`, `parse_js_with_offset`) provide simple entry points for scripts, modules, and embedded JavaScript with offset positioning.
- The parser supports **TypeScript and JSX** through configurable options and context-sensitive re-lexing in the lexer.

## Frequently Asked Questions

### How does BiomeJS handle JavaScript syntax errors?

BiomeJS implements error tolerance by ensuring every token appears in the final syntax tree, wrapping syntax errors in `ERROR` nodes rather than stopping the parse. This allows the parser to continue analyzing the rest of the file, providing linting and formatting tools with a complete tree even for broken code.

### What is the difference between the syntax tree and AST in BiomeJS?

The syntax tree consists of untyped `JsSyntaxNode` objects that retain all source details including whitespace and comments, while the typed AST provides thin wrapper structs (like `JsScript` or `JsIfStatement`) that cast the untyped nodes without copying data, enabling zero-cost conversions between representations.

### Can BiomeJS parse TypeScript and JSX?

Yes, the parser supports TypeScript and JSX through the `JsParserOptions` configuration and the `JsFileSource` type. The lexer handles re-lexing contexts for JSX tags and TypeScript type annotations, allowing the same parser infrastructure to handle standard JavaScript, TypeScript, and JSX seamlessly.

### How does BiomeJS achieve lossless code formatting?

By constructing a green tree through `JsLosslessTreeSink` that preserves all trivia (whitespace and comments) alongside the structural tokens, the formatter can reconstruct the exact original source while applying style changes, ensuring that formatting operations never lose information from the input file.