Skip to content

tree-sitter grammar: missing interface/type alias/as cast + scanner bug killing ternary parsing #1302

Description

@Opaius

Summary

The tree-sitter grammar for ripple (used by the Zed tsrx extension for syntax highlighting) was missing several TypeScript features and had a bug in the external scanner that broke ternary expression parsing. These issues caused syntax highlighting to fail on many .tsrx files using standard TypeScript patterns.

Bugs fixed

1. Missing interface_declaration (highest impact)

The interface keyword was completely absent from the declaration rule. Any file with export interface TransitionOptions { ... } would produce a parse ERROR at line 1, cascading into a catastrophic failure that killed ALL syntax highlighting for the rest of the file.

Fix: Added interface_declaration, interface_body, method_signature, and index_signature grammar rules. interface_body uses optional(choice($._semicolon, ',')) to separate members.

2. Missing type_alias_declaration

type Foo = ... declarations were not recognized.

3. Missing as_expression

TypeScript as type casts like (el as any).__ripple_exit failed.

4. object_type only allowed comma separators

Object type literals like { x: number; y: string } use semicolons, but object_type only accepted commas.

5. Scanner bug: AUTOMATIC_SEMICOLON handler prevented TERNARY_QMARK (ROOT CAUSE of ternary failure)

In scanner.c, the tree_sitter_ripple_external_scanner_scan function had the checks ordered with AUTOMATIC_SEMICOLON first:

if (valid_symbols[AUTOMATIC_SEMICOLON]) {
    bool ret = scan_automatic_semicolon(lexer);
    if (!ret && !valid_symbols[TERNARY_QMARK] && lexer->lookahead == '?') {
        return false;
    }
    return ret;  // returns before TERNARY_QMARK is ever checked
}

When the scanner saw ? after an expression, the AUTOMATIC_SEMICOLON handler would run first and return before TERNARY_QMARK ever had a chance. This meant the scanner never produced TERNARY_QMARK, making every a ? b : c ternary fail.

6. Missing generic_type in primary_expression

new Map<string | number, DOMRect>() failed because generic_type (Identifier) was only in the type rule (for type annotations), not in primary_expression. Added it with necessary conflict declarations.

Files modified (in grammars/tree-sitter/)

  • grammar.js — grammar rules (+interface_declaration, +type_alias_declaration, +as_expression, +generic_type in primary_expression, object_type separator fix, added sepBy helper, added precedences array)
  • src/scanner.c — scanner control flow fix (TERNARY_QMARK now properly checked after AUTOMATIC_SEMICOLON falls through)

Verification

All tests pass with 0 parse errors:

  • transitions.tsrx (440 lines, interfaces, ternaries, generics, as casts): 0 errors (was ~10+ before fixes)
  • +Page.tsrx (JSX-heavy): 0 errors
  • new Map<string | number, DOMRect>(): 0 errors
  • const x = a ? b : c;: 0 errors
  • interface Foo { bar: string; baz(): void; [key: string]: any }: 0 errors
  • a ?? b, a?.b, a < b: all still work

Remaining pre-existing issues

  • a?.[b] (optional element access) — pre-existing, rare
  • Type parameters in call_expression (e.g. foo<Type>()) — not supported yet

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions