How BiomeJS Handles TypeScript: Parsing, Analysis, and Semantic Integration

BiomeJS handles TypeScript by detecting the source type, activating the TypeScript feature flag, and routing exclusive syntax through gated parsing functions while maintaining context via the TypeContext struct.

BiomeJS treats TypeScript as an optional language extension of its core JavaScript parser. When parsing files in the biomejs/biome repository, the parser checks the source type and enables TypeScript-specific grammar rules, allowing the same JsParser infrastructure to handle both JavaScript and TypeScript seamlessly.

Language Detection via Feature Flags

The parser determines whether a file contains TypeScript by checking the JsSyntaxFeature::TypeScript flag. In crates/biome_js_parser/src/lib.rs, the is_supported method evaluates whether the source type's language is TypeScript:

// biome_js_parser/src/lib.rs
pub enum JsSyntaxFeature {
    TypeScript,
    // …
}

impl SyntaxFeature for JsSyntaxFeature {
    fn is_supported(&self, p: &JsParser) -> bool {
        match self {
            Self::TypeScript => p.source_type().language().is_typescript(),
            // …
        }
    }
}

When is_supported returns true, the parser unlocks TypeScript-exclusive syntax paths. This check occurs at lines 90–106 in lib.rs, ensuring that TypeScript constructs are only parsed when the input file is explicitly identified as TypeScript or TSX.

Gating TypeScript-Exclusive Syntax

Rather than branching the entire parser, BiomeJS uses exclusive syntax gates to handle TypeScript-specific productions. The parse_exclusive_syntax method wraps TypeScript-only functions and validates the feature flag before execution. In crates/biome_js_parser/src/syntax/stmt.rs, the statement parser invokes this gate for constructs like enums:

// biome_js_parser/src/syntax/stmt.rs
TypeScript.parse_exclusive_syntax(p, parse_ts_enum_declaration, |p, decl| { … });

This pattern appears at line 206. If the TypeScript feature is not enabled, the parser skips the TypeScript-specific logic and falls back to standard JavaScript rules, emitting diagnostics for unsupported constructs.

Modular TypeScript Grammar

The TypeScript grammar implementation resides in crates/biome_js_parser/src/syntax/typescript/. This directory contains specialized modules that extend the core JavaScript parser:

  • types.rs – Handles type annotations, unions, intersections, and type literals
  • statement.rs – Parses TypeScript-specific statements including enum, interface, and declare modifiers
  • module.rs – Processes module-level syntax such as import =, export =, and namespace declarations
  • ts_parse_error.rs – Generates custom error messages for TypeScript-only constructs

The header of types.rs (lines 1–27) defines the imports and module structure for type parsing.

TypeContext for Advanced Constructs

Parsing complex TypeScript features requires contextual awareness. The TypeContext struct in crates/biome_js_parser/src/syntax/typescript/types.rs tracks parsing flags such as conditional type allowance and in/out modifier support:

// biome_js_parser/src/syntax/typescript/types.rs
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct TypeContext(BitFlags<ContextFlag>);

Defined between lines 48–88, this struct provides methods like and_allow_in_out_modifier to toggle capabilities while descending the AST. These context flags ensure that modifiers like in and out are only permitted in valid locations, such as within type parameter declarations.

Error Handling for TypeScript Constructs

When TypeScript syntax appears in JavaScript files, or when invalid constructs are encountered, the parser generates specific diagnostics via ts_parse_error.rs. For example, attempting to use a declare modifier in a standard JavaScript file triggers an error at the parsing stage. In crates/biome_js_parser/src/syntax/stmt.rs (line 357), the error is constructed as follows:

// biome_js_parser/src/syntax/stmt.rs
TypeScript.parse_exclusive_syntax(p, parse_ts_declare_statement, |p, _| {
    p.err_builder("The `declare` modifier can only be used in TypeScript files.", range)
});

This ensures clear, actionable error messages that distinguish between JavaScript and TypeScript parsing modes.

Semantic Analysis and Type Resolution

After parsing, the TypeScript AST nodes flow into the semantic analyzer (biome_js_semantic). This component resolves type references, handles declaration merging, and provides type information to LSP features. The biome_js_type_info crate contains utilities for type data management, including declaration merging logic in type_data.rs (line 123) and local inference capabilities in local_inference.rs.

The semantic layer transforms the parsed syntax tree into a fully resolved representation, enabling BiomeJS to perform type-aware linting and provide accurate IDE support for TypeScript projects.

End-to-End TypeScript Parsing Example

To parse a TypeScript file programmatically, instantiate the parser with a TypeScript source type:

use biome_js_parser::{JsParser, JsParserOptions};
use biome_js_parser::source_type::SourceType;

fn parse_ts(source: &str) -> biome_js_syntax::JsSyntaxNode {
    // Declare that the source is TypeScript
    let source_type = SourceType::tsx(); // or .ts() for plain TS
    
    // Build parser options with the source type
    let options = JsParserOptions::default().with_source_type(source_type);
    
    // Create the parser
    let mut parser = JsParser::new(source, options);
    
    // Parse the program (top-level)
    let root = parser.parse_program();
    
    // Retrieve the syntax tree
    root.syntax()
}

Calling SourceType::tsx() marks the input as TypeScript JSX, enabling the TypeScript feature flag. The parser automatically routes all TypeScript constructs through the exclusive-syntax gates, constructing AST nodes with SyntaxKind variants like TS_TYPE_ANNOTATION.

Summary

  • Feature Flag Detection: BiomeJS detects TypeScript files via JsSyntaxFeature::TypeScript.is_supported() in crates/biome_js_parser/src/lib.rs, which checks if the source type language is TypeScript.
  • Exclusive Syntax Gates: TypeScript-specific constructs are parsed through parse_exclusive_syntax() methods that validate the feature flag before executing TypeScript logic.
  • Modular Grammar: TypeScript parsing is organized into dedicated modules under syntax/typescript/, including types.rs for type annotations and ts_parse_error.rs for diagnostics.
  • Context Tracking: The TypeContext struct manages parsing states for advanced features like conditional types and variance modifiers.
  • Semantic Integration: The biome_js_semantic crate and biome_js_type_info handle type resolution and declaration merging after parsing.

Frequently Asked Questions

How does BiomeJS detect if a file is TypeScript?

BiomeJS detects TypeScript files by examining the source type using p.source_type().language().is_typescript() within the is_supported method of JsSyntaxFeature. This check occurs in crates/biome_js_parser/src/lib.rs and returns true for .ts and .tsx extensions, enabling the TypeScript feature flag for that parse session.

What happens when TypeScript syntax is used in a JavaScript file?

When TypeScript-specific syntax like declare or interface appears in a JavaScript file, the parser encounters an exclusive syntax gate that checks the TypeScript feature flag. Since the flag is disabled for JavaScript files, the parser emits a diagnostic error—such as "The declare modifier can only be used in TypeScript files"—and falls back to standard JavaScript parsing rules.

How does BiomeJS handle complex TypeScript features like conditional types?

Complex features are managed via the TypeContext struct in crates/biome_js_parser/src/syntax/typescript/types.rs. This struct maintains bit flags for contextual permissions, such as allowing conditional types or in/out modifiers. As the parser descends into the AST, it creates modified contexts using methods like and_allow_in_out_modifier() to ensure syntax validity at specific tree depths.

Where does BiomeJS store type information after parsing?

Type information is processed by the biome_js_semantic crate after the initial parse. Declaration merging and type data structures are defined in crates/biome_js_type_info/src/type_data.rs, while local type inference logic resides in local_inference.rs. These modules provide the resolved type information used by BiomeJS's linting rules and LSP features.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →