Skip to main content

sqry_core/relations/queries/
mod.rs

1//! Tree-sitter query helpers and language-specific query sets.
2//!
3//! Queries live alongside the shared engine so adapters can reference them
4//! without duplicating S-expression strings in each language crate. The helper
5//! utilities here compile queries on demand and return the captured nodes so the
6//! engines can assemble relation edges.
7
8use std::collections::HashSet;
9
10use tree_sitter::StreamingIterator;
11use tree_sitter::{Node, Query, QueryCursor, Tree};
12
13/// Describes a single tree-sitter query and the capture that callers care
14/// about.
15#[derive(Debug, Clone, Copy)]
16pub struct QuerySpec {
17    /// Human-readable name used in error messages.
18    pub name: &'static str,
19    /// Capture identifier that should be collected from matches.
20    pub capture: &'static str,
21    /// S-expression query source.
22    pub source: &'static str,
23}
24
25impl QuerySpec {
26    /// Execute the query against `tree`, collecting all nodes matching the
27    /// configured capture. Duplicate nodes (based on start byte) are filtered
28    /// so downstream consumers don't have to deduplicate work.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error when the query source cannot be compiled for the
33    /// current tree-sitter language or when the requested capture name is not
34    /// present in the compiled query.
35    pub fn run<'tree>(
36        &self,
37        tree: &'tree Tree,
38        content: &[u8],
39    ) -> Result<Vec<Node<'tree>>, String> {
40        let language = tree.language();
41        let query = Query::new(&language, self.source)
42            .map_err(|err| format!("failed to compile query `{}`: {err}", self.name))?;
43        let capture_index = query
44            .capture_names()
45            .iter()
46            .position(|name| *name == self.capture)
47            .ok_or_else(|| format!("query `{}` missing capture `{}`", self.name, self.capture))?;
48
49        let mut cursor = QueryCursor::new();
50        let mut matches = cursor.matches(&query, tree.root_node(), content);
51        let mut seen_offsets = HashSet::new();
52        let mut nodes = Vec::new();
53
54        while let Some(m) = matches.next() {
55            for capture in m.captures {
56                if capture.index as usize == capture_index
57                    && seen_offsets.insert(capture.node.start_byte())
58                {
59                    nodes.push(capture.node);
60                }
61            }
62        }
63
64        Ok(nodes)
65    }
66}
67
68/// Query set used by the import extractor. Additional relation-specific query
69/// collections will join this as the engine gains native query support.
70#[derive(Debug)]
71pub struct ImportQuerySet {
72    /// Query specs that match import statements in the source file.
73    pub statements: &'static [QuerySpec],
74}
75
76/// Query set used by the export extractor.
77#[derive(Debug)]
78pub struct ExportQuerySet {
79    /// Query specs that match export statements in the source file.
80    pub statements: &'static [QuerySpec],
81}
82
83/// Query set used by the call extractor.
84#[derive(Debug)]
85pub struct CallQuerySet {
86    /// Query specs that match top-level callable constructs.
87    pub roots: &'static [QuerySpec],
88}
89
90pub mod typescript {
91    //! TypeScript-specific queries consumed by the shared relations engine.
92
93    use super::{CallQuerySet, ExportQuerySet, ImportQuerySet, QuerySpec};
94
95    /// Query set that extracts every `import_statement` in a TypeScript source
96    /// file.
97    pub const IMPORT_QUERIES: ImportQuerySet = ImportQuerySet {
98        statements: &[QuerySpec {
99            name: "typescript_import_statements",
100            capture: "import.statement",
101            source: include_str!("typescript/imports.scm"),
102        }],
103    };
104
105    /// Query set that extracts every `export_statement`.
106    pub const EXPORT_QUERIES: ExportQuerySet = ExportQuerySet {
107        statements: &[QuerySpec {
108            name: "typescript_export_statements",
109            capture: "export.statement",
110            source: include_str!("typescript/exports.scm"),
111        }],
112    };
113
114    /// Query set that captures top-level call roots (functions/classes).
115    pub const CALL_QUERIES: CallQuerySet = CallQuerySet {
116        roots: &[QuerySpec {
117            name: "typescript_call_roots",
118            capture: "call.root",
119            source: include_str!("typescript/calls.scm"),
120        }],
121    };
122}
123
124pub mod javascript {
125    //! JavaScript-specific queries consumed by the shared relations engine.
126
127    use super::{CallQuerySet, ExportQuerySet, ImportQuerySet, QuerySpec};
128
129    /// Query set for extracting JavaScript import statements.
130    pub const IMPORT_QUERIES: ImportQuerySet = ImportQuerySet {
131        statements: &[QuerySpec {
132            name: "javascript_import_statements",
133            capture: "import.statement",
134            source: include_str!("javascript/imports.scm"),
135        }],
136    };
137
138    /// Query set for extracting JavaScript export statements.
139    pub const EXPORT_QUERIES: ExportQuerySet = ExportQuerySet {
140        statements: &[QuerySpec {
141            name: "javascript_export_statements",
142            capture: "export.statement",
143            source: include_str!("javascript/exports.scm"),
144        }],
145    };
146
147    /// Query set for extracting JavaScript call roots.
148    pub const CALL_QUERIES: CallQuerySet = CallQuerySet {
149        roots: &[QuerySpec {
150            name: "javascript_call_roots",
151            capture: "call.root",
152            source: include_str!("javascript/calls.scm"),
153        }],
154    };
155}
156
157pub mod csharp {
158    //! C# query sets used by the shared relations adapters.
159
160    use super::{CallQuerySet, ImportQuerySet, QuerySpec};
161
162    /// Query set for extracting C# using directives.
163    pub const IMPORT_QUERIES: ImportQuerySet = ImportQuerySet {
164        statements: &[QuerySpec {
165            name: "csharp_using_directives",
166            capture: "import.directive",
167            source: include_str!("csharp/imports.scm"),
168        }],
169    };
170
171    /// Query set for extracting C# call roots.
172    pub const CALL_QUERIES: CallQuerySet = CallQuerySet {
173        roots: &[QuerySpec {
174            name: "csharp_call_roots",
175            capture: "call.root",
176            source: include_str!("csharp/calls.scm"),
177        }],
178    };
179}
180
181pub mod go {
182    //! Go query sets used by the shared relations adapters.
183
184    use super::{CallQuerySet, ExportQuerySet, ImportQuerySet, QuerySpec};
185
186    /// Query set for extracting Go import declarations.
187    pub const IMPORT_QUERIES: ImportQuerySet = ImportQuerySet {
188        statements: &[QuerySpec {
189            name: "go_import_declarations",
190            capture: "import.declaration",
191            source: include_str!("go/imports.scm"),
192        }],
193    };
194
195    /// Query set for extracting Go call roots.
196    pub const CALL_QUERIES: CallQuerySet = CallQuerySet {
197        roots: &[QuerySpec {
198            name: "go_call_roots",
199            capture: "call.root",
200            source: include_str!("go/calls.scm"),
201        }],
202    };
203
204    /// Query set for extracting Go exported declarations.
205    pub const EXPORT_QUERIES: ExportQuerySet = ExportQuerySet {
206        statements: &[QuerySpec {
207            name: "go_export_declarations",
208            capture: "export.declaration",
209            source: include_str!("go/exports.scm"),
210        }],
211    };
212}