Predicates
You can also specify arbitrary metadata and conditions associated with a pattern
by adding predicate S-expressions anywhere within your pattern. Predicate S-expressions
start with a predicate name beginning with a #
character, and ending with a ?
character. After that, they can
contain an arbitrary number of @
-prefixed capture names or strings.
Tree-sitter's CLI supports the following predicates by default:
The eq?
predicate
This family of predicates allows you to match against a single capture or string value.
The first argument to this predicate must be a capture, but the second can be either a capture to compare the two captures' text, or a string to compare first capture's text against.
The base predicate is #eq?
, but its complement, #not-eq?
, can be used to not
match a value. Additionally, you can prefix either of these with any-
to match
if any of the nodes match the predicate. This is only useful when dealing with
quantified captures, as by default a quantified capture will only match if all the captured nodes match the predicate.
Thus, there are four predicates in total:
#eq?
#not-eq?
#any-eq?
#any-not-eq?
Consider the following example targeting C:
((identifier) @variable.builtin
(#eq? @variable.builtin "self"))
This pattern would match any identifier that is self
.
Now consider the following example:
(
(pair
key: (property_identifier) @key-name
value: (identifier) @value-name)
(#eq? @key-name @value-name)
)
This pattern would match key-value pairs where the value
is an identifier
with the same text as the key (meaning they are the same):
As mentioned earlier, the any-
prefix is meant for use with quantified captures. Here's
an example finding an empty comment within a group of comments:
((comment)+ @comment.empty
(#any-eq? @comment.empty "//"))
The match?
predicate
These predicates are similar to the eq?
predicates, but they use regular expressions
to match against the capture's text instead of string comparisons.
The first argument must be a capture, and the second must be a string containing a regular expression.
Like the eq?
predicate family, we can tack on not-
to the beginning of the predicate
to negate the match, and any-
to match if any of the nodes in a quantified capture match the predicate.
This pattern matches identifiers written in SCREAMING_SNAKE_CASE
.
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_]+"))
This query identifies documentation comments in C that begin with three forward slashes (///
).
((comment)+ @comment.documentation
(#match? @comment.documentation "^///\\s+.*"))
This query finds C code embedded in Go comments that appear just before a "C" import statement.
These are known as Cgo
comments and are used to inject C code into Go programs.
((comment)+ @injection.content
.
(import_declaration
(import_spec path: (interpreted_string_literal) @_import_c))
(#eq? @_import_c "\"C\"")
(#match? @injection.content "^//"))
The any-of?
predicate
The any-of?
predicate allows you to match a capture against multiple strings,
and will match if the capture's text is equal to any of the strings.
The query below will match any of the builtin variables in JavaScript.
((identifier) @variable.builtin
(#any-of? @variable.builtin
"arguments"
"module"
"console"
"window"
"document"))
The is?
predicate
The is?
predicate allows you to assert that a capture has a given property. This isn't widely used, but the CLI uses it
to determine whether a given node is a local variable or not, for example:
((identifier) @variable.builtin
(#match? @variable.builtin "^(arguments|module|console|window|document)$")
(#is-not? local))
This pattern would match any builtin variable that is not a local variable, because the #is-not? local
predicate is used.
Directives
Similar to predicates, directives are a way to associate arbitrary metadata with a pattern. The only difference between predicates
and directives is that directives end in a !
character instead of ?
character.
Tree-sitter's CLI supports the following directives by default:
The set!
directive
This directive allows you to associate key-value pairs with a pattern. The key and value can be any arbitrary text that you see fit.
((comment) @injection.content
(#lua-match? @injection.content "/[*\/][!*\/]<?[^a-zA-Z]")
(#set! injection.language "doxygen"))
This pattern would match any comment that contains a Doxygen-style comment, and then sets the injection.language
key to
"doxygen"
. Programmatically, when iterating the captures of this pattern, you can access this property to then parse the
comment with the Doxygen parser.
The #select-adjacent!
directive
The #select-adjacent!
directive allows you to filter the text associated with a capture so that only nodes adjacent to
another capture are preserved. It takes two arguments, both of which are capture names.
The #strip!
directive
The #strip!
directive allows you to remove text from a capture. It takes two arguments: the first is the capture to strip
text from, and the second is a regular expression to match against the text. Any text matched by the regular expression will
be removed from the text associated with the capture.
For an example on the #select-adjacent!
and #strip!
directives,
view the code navigation documentation.
Recap
To recap about the predicates and directives Tree-Sitter's bindings support:
-
#eq?
checks for a direct match against a capture or string -
#match?
checks for a match against a regular expression -
#any-of?
checks for a match against a list of strings -
#is?
checks for a property on a capture -
Adding
not-
to the beginning of these predicates will negate the match -
By default, a quantified capture will only match if all the nodes match the predicate
-
Adding
any-
before theeq
ormatch
predicates will instead match if any of the nodes match the predicate -
#set!
associates key-value pairs with a pattern -
#select-adjacent!
filters the text associated with a capture so that only nodes adjacent to another capture are preserved -
#strip!
removes text from a capture
Note — Predicates and directives are not handled directly by the Tree-sitter C library. They are just exposed in a structured form so that higher-level code can perform the filtering. However, higher-level bindings to Tree-sitter like the Rust Crate or the WebAssembly binding do implement a few common predicates like those explained above. In the future, more "standard" predicates and directives may be added.