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
Summary
The tree-sitter grammar for
ripple(used by the Zedtsrxextension 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.tsrxfiles using standard TypeScript patterns.Bugs fixed
1. Missing
interface_declaration(highest impact)The
interfacekeyword was completely absent from thedeclarationrule. Any file withexport 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, andindex_signaturegrammar rules.interface_bodyusesoptional(choice($._semicolon, ','))to separate members.2. Missing
type_alias_declarationtype Foo = ...declarations were not recognized.3. Missing
as_expressionTypeScript
astype casts like(el as any).__ripple_exitfailed.4.
object_typeonly allowed comma separatorsObject type literals like
{ x: number; y: string }use semicolons, butobject_typeonly accepted commas.5. Scanner bug: AUTOMATIC_SEMICOLON handler prevented TERNARY_QMARK (ROOT CAUSE of ternary failure)
In
scanner.c, thetree_sitter_ripple_external_scanner_scanfunction had the checks ordered withAUTOMATIC_SEMICOLONfirst:When the scanner saw
?after an expression, theAUTOMATIC_SEMICOLONhandler would run first and return beforeTERNARY_QMARKever had a chance. This meant the scanner never producedTERNARY_QMARK, making everya ? b : cternary fail.6. Missing
generic_typeinprimary_expressionnew Map<string | number, DOMRect>()failed becausegeneric_type(Identifier) was only in thetyperule (for type annotations), not inprimary_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_typein primary_expression,object_typeseparator fix, addedsepByhelper, addedprecedencesarray)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 errorsnew Map<string | number, DOMRect>(): 0 errorsconst x = a ? b : c;: 0 errorsinterface Foo { bar: string; baz(): void; [key: string]: any }: 0 errorsa ?? b,a?.b,a < b: all still workRemaining pre-existing issues
a?.[b](optional element access) — pre-existing, rarecall_expression(e.g.foo<Type>()) — not supported yet