Fixing typescript-ts-mode in Emacs 30.2

Published:
Keywords: emacs

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-rules covers the main font-lock path for most modes
  • treesit-range-rules handles JSDoc embedding in js-ts-mode and others
  • treesit-query-compile catches modes like c-ts-mode that compile queries with :match directly

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.