codebook/crates/codebook/src/queries/README.md at main · blopker/codebook · GitHub
Skip to content

Latest commit

 

History

History
131 lines (92 loc) · 5.79 KB

README.md

File metadata and controls

131 lines (92 loc) · 5.79 KB

Tree-sitter Queries

This directory contains Tree-sitter query files (.scm) that define which parts of each language's source code are extracted for spell checking.

Tag Convention

Every capture name is a tag that categorizes the matched text. Tags use a dot-separated hierarchy so users can filter what gets checked via include_tags/exclude_tags in codebook.toml. Matching is prefix-based: "comment" matches comment, comment.line, comment.block, etc.

Available Tags

Capture name When to use
@comment Generic comments (when line/block aren't distinguished)
@comment.line Line comments (//, #, --, etc.)
@comment.block Block comments (/* */, {- -}, etc.)
@string String literals and string content
@string.special Atoms, symbols, struct tags, and other non-standard strings
@string.heredoc Heredoc bodies
@identifier Fallback for ambiguous identifiers that don't fit below
@identifier.function Function and method name definitions
@identifier.type Type, class, struct, interface, enum name definitions
@identifier.parameter Function parameter names
@identifier.field Struct field and object property names
@identifier.variable Variable declaration names
@identifier.constant Constant and enum member names
@identifier.module Package, module, and namespace names

Not every language needs every tag. HTML, for example, only uses @comment and @string. You can get a feel for which tags are available for a specific language by looking at the scm file for that language in this directory.

Custom Predicates

In addition to tree-sitter's built-in predicates (#eq?, #match?, #any-of?, …), codebook evaluates one extra predicate:

Predicate Effect
(#not-has-ancestor? @capture "kind" ["kind" …]) Drop the capture if any ancestor of the captured node has one of the listed tree-sitter node kinds.

Use this to narrow a broad capture instead of enumerating every positive context. For example, Python's (string_content) matches every string in the file, but inside type annotations (forward references, generic arguments) it's another tool's job:

((string_content) @string
  (#not-has-ancestor? @string "type"))

Injection Tags (Multi-Language Support)

Injection tags tell codebook to re-parse a region of the file using a different language's grammar. This is how Markdown code blocks, HTML <script> tags, and similar multi-language files are handled.

Capture name When to use
@injection.content The text to re-parse (paired with @injection.language in the same query match)
@injection.language A node whose text names the target language (paired with @injection.content)
@injection.{lang} Static injection: always re-parse the captured node as {lang} (e.g., @injection.html)

Example from markdown.scm:

; Prose content:spell-checked as text
(paragraph (inline) @string)
(atx_heading (inline) @string)

; HTML blocks:re-parsed with the HTML grammar
(html_block) @injection.html

; Fenced code blocks:language read from the info string
(fenced_code_block
  (info_string (language) @injection.language)
  (code_fence_content) @injection.content)

The @injection.language value is resolved against the ids and extensions fields in LANGUAGE_SETTINGS. Common aliases like py, js, sh, rs, etc. work automatically.

Adding a New Language

1. Create the Query File

Create a new .scm file in this directory named after your language (e.g., java.scm).

Use namespaced capture names from the table above. Example:

(comment) @comment
(string_content) @string
(function_declaration name: (identifier) @identifier.function)
(parameter name: (identifier) @identifier.parameter)
(variable_declaration (identifier) @identifier.variable)
(class_declaration name: (identifier) @identifier.type)

2. Understand the Language's AST

Use these tools to explore the grammar's node types:

A good approach:

  1. Write sample code with identifiers, strings, and comments
  2. Paste it into the playground/visualizer
  3. Observe the node types used for each element
  4. Create capture patterns that target only definition nodes, not usages

3. Update the Language Settings

Add your language to queries.rs:

  1. Add a new variant to the LanguageType enum
  2. Add a new entry to the LANGUAGE_SETTINGS array with the language type, file extensions, language identifiers, and path to your query file

4. Add the Tree-sitter Grammar

Add the grammar as a dependency in Cargo.toml and update the language() function in queries.rs to return the correct parser.

5. Test

cargo test -p codebook queries::tests::test_all_queries_are_valid

Additional language tests go in crates/codebook/tests/. Example files with at least one spelling error go in examples/.

Tips

  • Focus on capturing definitions, not usages
  • Only capture nodes that contain user-defined text (not keywords)
  • Always use namespaced capture names (@identifier.function, not @func_declaration)
  • Use the most specific tag that fits (e.g., @identifier.type over @identifier)
  • Avoid overlapping captures:don't let two patterns capture the same node at the same byte range. For example, a blanket (atom) @string.special would overlap with (function_clause name: (atom) @identifier.function). Instead, capture atoms only in specific parent contexts like (tuple (atom) @string.special). A debug_assert will catch overlaps during testing.
  • Start simple and add complexity as needed
  • Look at existing query files for patterns