This content originally appeared on DEV Community and was authored by Riel Joseph
If you’ve ever written raw SQL queries inside TypeScript template literals, you probably love Treesitter’s syntax highlighting for embedded languages like:
const result = await sql`SELECT * FROM users`;
But what happens when your code starts getting fancy — say, using generic calls, non-null assertions, or different helper functions like tx.sql or sql<User>?
By default, Treesitter doesn’t inject SQL highlighting in these more complex patterns.
In this post, we’ll fix that by writing a custom Treesitter injection query to handle generic TypeScript expressions with sql and tx.
The Problem
Neovim’s Treesitter injection for SQL works great in the simplest case:
const users = await sql`SELECT * FROM users`;
…but once you introduce TypeScript generics or non-null assertions, highlighting breaks:
// Generic instantiation
const result = await sql<User>`SELECT * FROM users`;
// Non-null expression
const result = (await sql<User>!)`SELECT * FROM users`;
The reason?
Treesitter doesn’t recognize that sql<User> (an instantiation_expression) still represents a function call for the purpose of language injection.
The Fix: Custom Treesitter Injection Query
To solve this, we extend Neovim’s built-in SQL injection rules using a custom .scm query file.
Create a file at:
~/.config/nvim/queries/typescript/injections.scm
Paste this code:
;; Extend built-in injections
(call_expression
  function: (non_null_expression
    (instantiation_expression
      (await_expression
        (identifier) @_name)))
  arguments: (template_string) @injection.content
  (#eq? @_name "sql")
  (#set! injection.language "sql")
  (#set! injection.include-children))
;; Handle simpler variants like sql`...` and await sql`...`
(call_expression
  function: [
    (identifier) @_name
    (await_expression (identifier) @_name)
    (instantiation_expression function: (identifier) @_name)
  ]
  arguments: (template_string) @injection.content
  (#eq? @_name "sql")
  (#set! injection.language "sql")
  (#set! injection.include-children))
;; Support tx`...` and tx<User>`...`
(call_expression
  function: (non_null_expression
    (instantiation_expression
      (await_expression
        (identifier) @_name)))
  arguments: (template_string) @injection.content
  (#eq? @_name "tx")
  (#set! injection.language "sql")
  (#set! injection.include-children))
;; Handle simpler variants for tx as well
(call_expression
  function: [
    (identifier) @_name
    (await_expression (identifier) @_name)
    (instantiation_expression function: (identifier) @_name)
  ]
  arguments: (template_string) @injection.content
  (#eq? @_name "tx")
  (#set! injection.language "sql")
  (#set! injection.include-children))
What This Query Does
Let’s break it down.
Treesitter parses TypeScript code into syntax nodes like:
- 
call_expression— a function call (sql\...``)
- 
instantiation_expression— a generic function call (sql<User>)
- 
await_expression— forawait sql\...``
- 
template_string— your backticked SQL string
We’re telling Treesitter:
“Whenever you see a
call_expressionwhose function name issqlortx, treat the argument (a template string) as SQL content.”
The #set! injection.language "sql" command tells Neovim to apply SQL highlighting within that string.
We also include multiple variations to cover:
- 
sql\...``
- 
await sql\...``
- 
sql<User>\...``
- 
await sql<User>\...``
- 
tx\...``
- 
await tx<User>\...``
Testing It
Once you’ve saved the query, restart Neovim and open a TypeScript file containing SQL template strings.
Try all these patterns:
const a = sql`SELECT * FROM users`;
const b = await sql`SELECT * FROM posts`;
const c = await sql<User>`SELECT * FROM users`;
const d = await tx<Post>`SELECT * FROM posts`;
const e = tx`SELECT * FROM comments`;
If everything worked, your SQL inside backticks should now be properly highlighted in all cases.
Bonus Tip: Reload Queries Without Restarting Neovim
You can quickly reload queries after editing them using:
:TSBufReload
or restart Treesitter highlighting manually with:
:edit
Conclusion
By extending Treesitter’s injection rules, we’ve made Neovim smart enough to recognize complex TypeScript SQL calls — even with generics, awaits, or alternate function names.
This small tweak can massively improve readability and developer experience when working with query-heavy TypeScript codebases.
TL;DR
- Create queries/typescript/injections.scm
- Paste the SQL injection query above
- Enjoy perfect SQL highlighting for all your sqlandtxtemplate strings
Would you like me to include a short section on how to debug Treesitter queries (e.g., using :InspectTree or :TSPlaygroundToggle) before you publish it? It’s a good addition for readers who want to tweak it further.
This content originally appeared on DEV Community and was authored by Riel Joseph
 
	
			Riel Joseph | Sciencx (2025-10-29T23:22:52+00:00) Neovim Treesitter highlighting with sql generic types (Typescript). Retrieved from https://www.scien.cx/2025/10/29/neovim-treesitter-highlighting-with-sql-generic-types-typescript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.
