Fixing typescript-ts-mode in Emacs 30.2
Contents
What broke
After a recent Arch update, Emacs 30.2 + typescript-ts-mode died the first
time I opened a .ts or .tsx file:
Error: treesit-query-error ("Invalid predicate" "match")
The file still displayed, but without syntax highlighting. python-ts-mode
failed the same way. js-ts-mode and c-ts-mode mostly worked in the main
buffer but broke around JSDoc ranges and C’s emacs-specific range queries.
Cause
This is Emacs bug#79687↗:
Emacs 30.2 serializes tree-sitter query predicates as #match, but
libtree-sitter 0.26 (what Arch ships) only accepts #match?. The fix on Emacs
master (commit b0143530) has not been backported to 30.2.
String rewrites do not help. Emacs 30.2’s predicate dispatcher hardcodes bare
match/equal/pred and rejects the ? variants at evaluation time. Satisfy
libtree-sitter and Emacs breaks; satisfy Emacs and libtree-sitter breaks.
Workaround
Strip predicates from queries and move the logic into capture-name-is-a-function fontifiers.
A font-lock rule like:
((identifier) @font-lock-keyword-face
(:match "\\`\\(break\\|continue\\)\\'" @font-lock-keyword-face))
becomes:
((identifier) @my-ts-rw--fn-font-lock-keyword-face-abc12345)
The generated function applies font-lock-keyword-face only when the node’s
text matches the original regex. No predicates in the query, so libtree-sitter
is happy; the fontifier preserves the original semantics.
:filter-args advice on three functions handles the rewrite:
treesit-font-lock-rulescovers the main font-lock path for most modestreesit-range-ruleshandles JSDoc embedding injs-ts-modeand otherstreesit-query-compilecatches modes likec-ts-modethat compile queries with:matchdirectly
Install
Single file in my emacs-shared↗ repo: init/treesit-predicate-rewrite.el↗.
Load it early, before any tree-sitter mode sets up font-lock:
(load "/path/to/treesit-predicate-rewrite" nil nil nil t)
It self-activates via define-advice. Queries without predicates are untouched,
so it is safe to leave on after upstream fixes the bug.
Verified on typescript-ts-mode, tsx-ts-mode, python-ts-mode, js-ts-mode,
c-ts-mode, rust-ts-mode, java-ts-mode, go-ts-mode, and lua-ts-mode.