How to Troubleshoot SWC Parsing Errors for TypeScript and Modern JavaScript Syntax

Troubleshoot SWC parsing errors by examining the error's Span, draining lexer diagnostics with take_errors(), and tracing the execution path through Parser::parse_program to determine whether the failure originates in the lexer, TypeScript-specific grammar rules, or contextual validation.

When working with the swc-project/swc compiler, parsing errors in TypeScript or modern ECMAScript syntax typically surface during the lexing or AST construction phases. Understanding how to troubleshoot SWC parsing errors requires familiarity with the parser's architecture, from the Lexer tokenization to the grammar validation logic in swc_ecma_parser. This guide walks through the diagnostic workflow, error emission paths, and resolution strategies using the actual implementation details from the source code.

Understanding the SWC Parsing Architecture

SWC processes source code by feeding it into a lexer (swc_ecma_lexer) that produces tokens, which the parser (swc_ecma_parser) consumes to build an Abstract Syntax Tree (AST). When a parsing error occurs, the Error type in crate::error::Error captures the diagnostic along with a Span indicating the exact source location.

Entry Points and Syntax Detection

The parsing flow begins at Parser::parse_program in crates/swc_ecma_parser/src/parser/mod.rs (lines 89‑125). This method determines whether the source represents a module or a script, then dispatches to either parse_module_item_block_body or parse_stmt_block_body.

TypeScript-specific branches are guarded by #[cfg(feature = "typescript")] and runtime checks like self.syntax().typescript(). For TypeScript modules, the entry point is Parser::parse_typescript_module at lines 61‑71. All token consumption flows through the Buffer wrapper (self.input), using helper methods such as bump(), expect(), and eat_ident_ref() to advance or validate tokens.

Error Collection and Emission

Errors propagate through two primary paths:

  • Early-error checks for reserved words, illegal assignments, or strict-mode violations call emit_err or emit_strict_mode_err (defined at lines 61‑68 of mod.rs).
  • Token-level errors occur when the lexer returns Token::Error, which the parser handles via expect_error_token_and_bump (lines 70‑80).

All collected errors reside in the lexer's ErrorCollector. After parsing, calling parser.take_errors() drains any lexer-side diagnostics, while the main parse result returns Result<Program, Error>.

Common Causes of Parsing Failures

SWC parsing errors generally fall into five categories based on where the validation occurs:

  • Unexpected tokens — Triggered when Parser::expect encounters a token that violates grammar expectations (e.g., missing semicolons or stray braces). The unexpected! macro generates these errors throughout the parser.
  • Reserved word misuse — Occurs when identifiers like eval, arguments, or await (in strict mode) appear as binding targets. The check lives in Parser::check_assign_target and Expr::is_valid_simple_assignment_target.
  • TypeScript-only syntax — JSX type casts, as assertions, and type-only imports require self.syntax().typescript() to return true. These guards appear in parse_type, parse_prop_name, and the dedicated typescript module at crates/swc_ecma_parser/src/parser/typescript.rs.
  • Strict-mode violations — Duplicate let/const declarations, use of with, or delete on unqualified identifiers trigger Parser::emit_strict_mode_err.
  • Lexical errors — Unterminated strings or illegal Unicode escapes originate in crates/swc_ecma_lexer/src/parser/mod.rs and propagate as Token::Error.

Step-by-Step Troubleshooting Workflow

Follow this systematic approach to diagnose and resolve parsing failures:

  1. Capture both lexer and parser errors

    Always drain the error collector after a failed parse to see lexer-level diagnostics that might precede the parser error:

    use swc_ecma_parser::{Parser, StringInput, Syntax, EsVersion};
    use swc_common::{SourceMap, FileName};
    
    let cm = SourceMap::default();
    let fm = cm.new_source_file(FileName::Custom("input.ts".into()), src.into());
    
    let lexer = swc_ecma_parser::lexer::Lexer::new(
        Syntax::typescript(),
        EsVersion::Es2022,
        StringInput::from(&*fm),
        None,
    );
    let mut parser = Parser::new_from(lexer);
    
    match parser.parse_program() {
        Ok(program) => println!("Parsed successfully"),
        Err(err) => {
            eprintln!("Parser error: {}", err);
            // Critical: flush lexer errors
            for lex_err in parser.take_errors() {
                eprintln!("Lexer error: {}", lex_err);
            }
        }
    }
  2. Inspect the error's Span

    The Error type contains a Span pointing to the exact byte range. Use the SourceMap to extract the problematic snippet:

    if let Ok(snippet) = cm.span_to_snippet(err.span()) {
        eprintln!("Error at {:?}: {}", err.span(), snippet);
    }
  3. Verify the parsing context

    Many errors depend on context flags (module vs. script, strict mode, InType). Dump the current context before the failing rule:

    eprintln!("Context: {:?}", parser.ctx());
  4. Enable debug tracing

    Compile SWC with --features debug to activate trace_cur! macros. This outputs every token processed and the parser rule being applied, helping you pinpoint where the grammar diverges from your expectations.

  5. Isolate the construct

    Reduce the source to the smallest fragment that still triggers the error. This determines whether the bug lies in:

  6. Map error codes to implementation

    SWC uses TypeScript-compatible error codes. Search the codebase for the specific code:

    • TS1003 (duplicate export name) — emitted in Parser::record_exported_name
    • TS2406 (invalid assignment target) — emitted in Parser::check_assign_target
  7. Validate against the test suite

    Run the parser's fixture tests to verify behavior against known-good patterns:

    cd crates/swc_ecma_parser
    cargo test --features typescript

Resolving Common Parsing Gotchas

Symptom Likely Cause Resolution
"Unexpected token }" at EOF Unclosed block or class body Verify all opening { have matching }; use cargo fmt to auto-correct indentation.
"Expected ; but found }" Automatic semicolon insertion (ASI) failed due to a line-break before } Insert an explicit semicolon or restructure the statement to avoid the newline.
"TS2406: Assignment to const variable" Attempting to reassign a const or readonly binding Change the declaration to let or remove the reassignment.
"TS1003: Duplicate export name" Two exports share the same identifier Rename one export or consolidate the declarations.
"Invalid Unicode escape" \u{...} sequence contains an out-of-range code point Ensure the escape represents a valid Unicode scalar value (0‑10FFFF).

Essential Source Files for Debugging

When tracing a parsing error, reference these key locations in the swc-project/swc repository:

Summary

  • Drain all diagnostics using parser.take_errors() to capture lexer errors alongside parser errors.
  • Locate the source via Span inspection and SourceMap::span_to_snippet to identify the exact problematic code.
  • Check context flags (parser.ctx()) when errors involve strict mode or TypeScript-specific syntax.
  • Enable debug features (--features debug) for token-level tracing through the parser.
  • Reference error codes like TS2406 and TS1003 to map diagnostics to specific validation logic in mod.rs and typescript.rs.
  • Isolate the failure to determine if it stems from tokenization, TypeScript grammar, or ECMAScript validation rules.

Frequently Asked Questions

How do I distinguish between lexer errors and parser errors in SWC?

Lexer errors occur during tokenization (e.g., unterminated strings) and are stored separately from parser errors. After parsing, call parser.take_errors() to retrieve any lexer diagnostics. Parser errors (e.g., unexpected tokens) are returned directly in the Result from methods like parse_program().

What does error code TS2406 indicate in SWC?

Error TS2406 signifies an invalid left-hand side in an assignment expression. According to the source in crates/swc_ecma_parser/src/parser/mod.rs, this is emitted by Parser::check_assign_target when code attempts to assign to a const binding or an expression that is not a valid simple assignment target (e.g., a literal or function call).

How can I enable verbose tracing to debug parser state?

Compile SWC with the debug feature flag: cargo build --features debug. This activates trace_cur! macros throughout swc_ecma_parser that print every token consumed and the current parsing rule to stdout, allowing you to trace exactly where the parser rejects your syntax.

Where does SWC implement TypeScript-specific parsing logic?

TypeScript extensions are implemented in crates/swc_ecma_parser/src/parser/typescript.rs. This module handles type annotations, interfaces, enums, and namespace declarations. The parser checks self.syntax().typescript() before entering these code paths, ensuring the features are only active when TypeScript support is enabled.

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 →