Skip to main content

Tree-sitter query syntax

We use the tree-sitter query language to define our parse tree patterns. In addition to the official tree-sitter query documentation, we support a couple of additional features.

Relationships​

In addition to the node corresponding to the scope itself (which we call its content range), you can tag different aspects / relationships of the scope. Assuming the internal identifier of our scope is foo, we can tag the following aspects of the scope:

  • @foo.domain indicates the domain of the scope. For example, you could use @collectionKey.domain to indicate that the domain of a key is the containing item, which would allow you to say "take key" from within the value of a key-value pair to select the key.
  • @foo.leading and @foo.trailing indicate the leading and trailing delimiters of the scope. For example, you could use @collectionKey.trailing to include all the way up to the start of the value as the trailing delimiter, so that "chuck key" will leave just the value.
  • @foo.removal to indicate the removal range of the scope. Note that it is preferred to use @foo.leading or @foo.trailing instead of @foo.removal in situations where you just need to include a leading or trailing delimiter in the removal range.
  • @foo.interior to indicate the interior of the scope, used for "inside". For example, you could use @namedFunction.interior to indicate the interior of a function, which would usually be the function body itself, without any leading or trailing delimiters.
  • @foo.iteration to indicate the iteration scope of the scope. For example, you could use @namedFunction.iteration to indicate that the iteration scope for functions is a class. Note that unlike the other aspects, the iteration scope is not a part of the scope itself, but rather a separate scope that is used to determine the iteration scope of the given scope. Thus, it should nearly always appear in a separate pattern from the scope itself, unlike the other aspects, which must appear in the same pattern as the scope itself.

Inline operators​

In addition to the above aspects, you can also use the following inline operators to modify the scope:

  • @foo.start and @foo.end to construct the scope using a range between two nodes (inclusive).
  • @foo.startOf and @foo.endOf to refer to the start and end positions of a node. For example, you could use @foo.start.endOf to indicate that the scope should start at the end of the node.

Capture groups​

Use @G_ followed by at least two underscore-separated scope names to give the same capture to multiple scopes. For example, @G_value_name_namedFunction expands to @value, @name, and @namedFunction. The G_ prefix is discarded. Every scope name in the group must be valid.

A dot suffix applies to every scope in the group. For example, @G_value_name_namedFunction.start expands to @value.start, @name.start, and @namedFunction.start. This also works with relationships and inline operators. A Java block can share iteration boundaries without repeating the capture for each scope:

(_
"{" @G_statement_name_value_type.iteration.start.endOf
"}" @G_statement_name_value_type.iteration.end.startOf
)

Predicates refer to the group capture and apply their changes to every expanded capture.

Query predicate operators​

We also support a number of query predicate operators for modifying the scope. See queryPredicateOperators.ts for a list of available operators.