use super::{CodeGraphV2, TypeFlowGraphV2};
use crate::symbol::{SymbolId, SymbolPath, SymbolRegistry};
use crate::Pattern;
use crate::SymbolKind;
pub struct QueryBuilder<'a> {
graph: &'a CodeGraphV2,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
filters: Vec<Filter>,
}
enum Filter {
Kind(SymbolKind),
KindOneOf(Vec<SymbolKind>),
Public,
CrateVisible,
Private,
InModule(String),
InCrate(String),
NamePattern(Pattern),
HasCallers,
HasNoCallers,
Implements(SymbolId),
UsedBy(SymbolId),
}
impl<'a> QueryBuilder<'a> {
pub fn new(
graph: &'a CodeGraphV2,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
) -> Self {
Self {
graph,
typeflow,
registry,
filters: Vec::new(),
}
}
pub fn functions(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Function));
self
}
pub fn structs(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Struct));
self
}
pub fn enums(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Enum));
self
}
pub fn traits(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Trait));
self
}
pub fn modules(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Mod));
self
}
pub fn impls(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Impl));
self
}
pub fn methods(mut self) -> Self {
self.filters.push(Filter::Kind(SymbolKind::Method));
self
}
pub fn kind(mut self, kind: SymbolKind) -> Self {
self.filters.push(Filter::Kind(kind));
self
}
pub fn kinds(mut self, kinds: Vec<SymbolKind>) -> Self {
self.filters.push(Filter::KindOneOf(kinds));
self
}
pub fn public(mut self) -> Self {
self.filters.push(Filter::Public);
self
}
pub fn crate_visible(mut self) -> Self {
self.filters.push(Filter::CrateVisible);
self
}
pub fn private(mut self) -> Self {
self.filters.push(Filter::Private);
self
}
pub fn in_module(mut self, module: impl Into<String>) -> Self {
self.filters.push(Filter::InModule(module.into()));
self
}
pub fn in_crate(mut self, crate_name: impl Into<String>) -> Self {
self.filters.push(Filter::InCrate(crate_name.into()));
self
}
pub fn name_matches(mut self, pattern: Pattern) -> Self {
self.filters.push(Filter::NamePattern(pattern));
self
}
pub fn name(self, name: impl Into<String>) -> Self {
self.name_matches(Pattern::exact(name.into()))
}
pub fn name_glob(self, pattern: impl Into<String>) -> Self {
self.name_matches(Pattern::glob(pattern.into()))
}
pub fn has_callers(mut self) -> Self {
self.filters.push(Filter::HasCallers);
self
}
pub fn has_no_callers(mut self) -> Self {
self.filters.push(Filter::HasNoCallers);
self
}
pub fn implements_trait(mut self, trait_id: SymbolId) -> Self {
self.filters.push(Filter::Implements(trait_id));
self
}
pub fn used_by(mut self, user_id: SymbolId) -> Self {
self.filters.push(Filter::UsedBy(user_id));
self
}
pub fn collect(self) -> Vec<SymbolId> {
self.registry
.iter()
.map(|(id, _)| id)
.filter(|&id| self.matches(id))
.collect()
}
pub fn first(self) -> Option<SymbolId> {
self.registry
.iter()
.map(|(id, _)| id)
.find(|&id| self.matches(id))
}
pub fn count(self) -> usize {
self.registry
.iter()
.map(|(id, _)| id)
.filter(|&id| self.matches(id))
.count()
}
pub fn exists(self) -> bool {
self.registry
.iter()
.map(|(id, _)| id)
.any(|id| self.matches(id))
}
fn matches(&self, id: SymbolId) -> bool {
let path = match self.registry.resolve(id) {
Some(p) => p,
None => return false,
};
for filter in &self.filters {
if !self.matches_filter(id, path, filter) {
return false;
}
}
true
}
fn matches_filter(&self, id: SymbolId, path: &SymbolPath, filter: &Filter) -> bool {
match filter {
Filter::Kind(kind) => self
.registry
.kind(id)
.is_some_and(|k| k == *kind || k.matches(kind)),
Filter::KindOneOf(kinds) => self
.registry
.kind(id)
.is_some_and(|k| kinds.iter().any(|kind| k.matches(kind))),
Filter::Public => self.registry.visibility(id).is_some_and(|v| v.is_public()),
Filter::CrateVisible => self
.registry
.visibility(id)
.is_some_and(|v| v.is_crate_visible()),
Filter::Private => self.registry.visibility(id).is_none_or(|v| v.is_private()),
Filter::InModule(module) => path.to_string().contains(module),
Filter::InCrate(crate_name) => path.crate_name() == crate_name,
Filter::NamePattern(pattern) => pattern.matches(path.name()),
Filter::HasCallers => self.graph.callers_of(id).next().is_some(),
Filter::HasNoCallers => self.graph.callers_of(id).next().is_none(),
Filter::Implements(trait_id) => self.graph.implementors_of(*trait_id).any(|x| x == id),
Filter::UsedBy(user_id) => self.typeflow.types_used_by(*user_id).any(|x| x == id),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol::Visibility;
trait QueryExt {
fn query<'a>(
&'a self,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
) -> QueryBuilder<'a>;
}
impl QueryExt for CodeGraphV2 {
fn query<'a>(
&'a self,
typeflow: &'a TypeFlowGraphV2,
registry: &'a SymbolRegistry,
) -> QueryBuilder<'a> {
QueryBuilder::new(self, typeflow, registry)
}
}
fn setup() -> (SymbolRegistry, CodeGraphV2, TypeFlowGraphV2) {
let mut registry = SymbolRegistry::new();
let func1 = registry
.register_with_metadata(
SymbolPath::parse("mylib::handlers::handle").unwrap(),
SymbolKind::Function,
None,
Some(Visibility::Public),
)
.unwrap();
let func2 = registry
.register_with_metadata(
SymbolPath::parse("mylib::handlers::process").unwrap(),
SymbolKind::Function,
None,
Some(Visibility::Private),
)
.unwrap();
let struct1 = registry
.register_with_metadata(
SymbolPath::parse("mylib::models::User").unwrap(),
SymbolKind::Struct,
None,
Some(Visibility::Public),
)
.unwrap();
let mut graph = CodeGraphV2::new();
graph.add_node(func1);
graph.add_node(func2);
graph.add_node(struct1);
graph.add_to_kind_index(func1, SymbolKind::Function);
graph.add_to_kind_index(func2, SymbolKind::Function);
graph.add_to_kind_index(struct1, SymbolKind::Struct);
graph.add_edge(func1, func2, super::super::CodeEdgeV2::Calls);
let typeflow = TypeFlowGraphV2::new();
(registry, graph, typeflow)
}
#[test]
fn test_query_functions() {
let (registry, graph, typeflow) = setup();
let results = graph.query(&typeflow, ®istry).functions().collect();
assert_eq!(results.len(), 2);
}
#[test]
fn test_query_structs() {
let (registry, graph, typeflow) = setup();
let results = graph.query(&typeflow, ®istry).structs().collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_query_public() {
let (registry, graph, typeflow) = setup();
let results = graph.query(&typeflow, ®istry).public().collect();
assert_eq!(results.len(), 2); }
#[test]
fn test_query_in_module() {
let (registry, graph, typeflow) = setup();
let results = graph
.query(&typeflow, ®istry)
.in_module("handlers")
.collect();
assert_eq!(results.len(), 2);
}
#[test]
fn test_query_combined() {
let (registry, graph, typeflow) = setup();
let results = graph
.query(&typeflow, ®istry)
.functions()
.public()
.in_module("handlers")
.collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_query_name_glob() {
let (registry, graph, typeflow) = setup();
let results = graph.query(&typeflow, ®istry).name_glob("*er").collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_query_has_callers() {
let (registry, graph, typeflow) = setup();
let results = graph
.query(&typeflow, ®istry)
.functions()
.has_callers()
.collect();
assert_eq!(results.len(), 1);
}
#[test]
fn test_query_count() {
let (registry, graph, typeflow) = setup();
let count = graph.query(&typeflow, ®istry).functions().count();
assert_eq!(count, 2);
}
#[test]
fn test_query_exists() {
let (registry, graph, typeflow) = setup();
assert!(graph.query(&typeflow, ®istry).structs().exists());
assert!(!graph.query(&typeflow, ®istry).traits().exists());
}
}