use alloc::vec::Vec;
use crate::path_search::Path;
use crate::syntax::SyntaxGraph;
use crate::{CodeGraph, NodeId};
mod assignment;
mod catch_clause;
mod class_body;
mod declaration_block;
mod file_node;
mod for_each;
mod for_statement;
mod if_statement;
mod method_declaration;
mod method_invocation;
mod try_statement;
mod using_statement;
mod variable_declaration;
pub struct SearchArgs<'a> {
pub graph: &'a SyntaxGraph,
pub n_id: NodeId,
pub symbol: &'a str,
pub def_only: bool,
pub all_related: bool,
}
pub type SearchResult = (bool, NodeId);
type Searcher = fn(&SearchArgs<'_>) -> Vec<SearchResult>;
fn searcher_for(label: &str) -> Option<Searcher> {
match label {
"Assignment" => Some(assignment::search),
"CatchClause" => Some(catch_clause::search),
"ClassBody" => Some(class_body::search),
"DeclarationBlock" => Some(declaration_block::search),
"File" => Some(file_node::search),
"ForEachStatement" => Some(for_each::search),
"ForStatement" => Some(for_statement::search),
"If" => Some(if_statement::search),
"MethodDeclaration" => Some(method_declaration::search),
"MethodInvocation" => Some(method_invocation::search),
"TryStatement" => Some(try_statement::search),
"UsingStatement" => Some(using_statement::search),
"VariableDeclaration" => Some(variable_declaration::search),
_ => None,
}
}
#[must_use]
pub fn search(
graph: &SyntaxGraph,
path: &Path,
symbol: &str,
def_only: bool,
all_related: Option<bool>,
) -> Vec<SearchResult> {
let mut out: Vec<SearchResult> = Vec::new();
for &n_id in path {
if let Some(searcher) = graph.label_type(n_id).and_then(searcher_for) {
out.extend(searcher(&SearchArgs {
graph,
n_id,
symbol,
def_only,
all_related: all_related.unwrap_or(false),
}));
}
}
out
}
#[must_use]
pub fn definition_search(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Option<NodeId> {
search(graph, path, symbol, true, None)
.into_iter()
.next()
.map(|(_, ref_id)| ref_id)
}
fn collect_until_def(results: Vec<SearchResult>) -> Vec<NodeId> {
let mut out: Vec<NodeId> = Vec::new();
for (is_definition, ref_id) in results {
out.push(ref_id);
if is_definition {
break;
}
}
out
}
#[must_use]
pub fn search_until_def(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Vec<NodeId> {
collect_until_def(search(graph, path, symbol, false, None))
}
#[must_use]
pub fn search_all_related_until_def(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Vec<NodeId> {
collect_until_def(search(graph, path, symbol, false, Some(true)))
}
#[cfg(test)]
mod tests {
use super::{definition_search, search, search_all_related_until_def, search_until_def};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::NodeId;
use alloc::string::String;
use alloc::vec;
fn compound_then_declaration(graph: &mut SyntaxGraph) {
graph.add_node(NodeId(3), symbol_lookup("x"));
graph.add_node(
NodeId(1),
SyntaxNode::Assignment {
variable_id: NodeId(3),
value_id: None,
operator: Some(String::from("+=")),
},
);
graph.add_node(NodeId(2), variable_declaration("x"));
}
fn variable_declaration(name: &str) -> SyntaxNode {
SyntaxNode::VariableDeclaration {
variable: String::from(name),
variable_type: None,
value_id: None,
variable_id: None,
access_modifier: None,
}
}
fn symbol_lookup(symbol: &str) -> SyntaxNode {
SyntaxNode::SymbolLookup {
symbol: String::from(symbol),
symbol_scope: None,
value: None,
}
}
#[test]
fn finds_a_variable_declaration_definition() {
let mut graph = SyntaxGraph::new();
graph.add_node(NodeId(1), variable_declaration("x"));
let path = vec![NodeId(1)];
assert_eq!(definition_search(&graph, &path, "x"), Some(NodeId(1)));
assert_eq!(definition_search(&graph, &path, "y"), None);
}
#[test]
fn compound_assignment_is_a_reference_not_a_definition() {
let mut graph = SyntaxGraph::new();
graph.add_node(NodeId(2), symbol_lookup("x"));
graph.add_node(
NodeId(1),
SyntaxNode::Assignment {
variable_id: NodeId(2),
value_id: None,
operator: Some(String::from("+=")),
},
);
let path = vec![NodeId(1)];
assert_eq!(definition_search(&graph, &path, "x"), None);
assert_eq!(
search(&graph, &path, "x", false, None),
[(false, NodeId(1))]
);
}
#[test]
fn plain_assignment_is_a_definition() {
let mut graph = SyntaxGraph::new();
graph.add_node(NodeId(2), symbol_lookup("x"));
graph.add_node(
NodeId(1),
SyntaxNode::Assignment {
variable_id: NodeId(2),
value_id: None,
operator: None,
},
);
let path = vec![NodeId(1)];
assert_eq!(definition_search(&graph, &path, "x"), Some(NodeId(1)));
}
#[test]
fn search_until_def_yields_references_then_stops_at_the_definition() {
let mut graph = SyntaxGraph::new();
compound_then_declaration(&mut graph);
let path = vec![NodeId(1), NodeId(2)];
assert_eq!(search_until_def(&graph, &path, "x"), [NodeId(1), NodeId(2)]);
}
#[test]
fn search_all_related_until_def_also_stops_at_the_definition() {
let mut graph = SyntaxGraph::new();
compound_then_declaration(&mut graph);
let path = vec![NodeId(1), NodeId(2)];
assert_eq!(
search_all_related_until_def(&graph, &path, "x"),
[NodeId(1), NodeId(2)]
);
}
#[test]
fn related_search_sees_a_method_call_argument_only_when_enabled() {
let mut graph = SyntaxGraph::new();
graph.add_node(
NodeId(1),
SyntaxNode::MethodInvocation {
expression: String::from("foo"),
object: None,
symbol_scope: None,
expression_id: None,
arguments_id: Some(NodeId(2)),
object_id: None,
block_id: None,
receiver_type_fqn: None,
},
);
graph.add_node(NodeId(2), SyntaxNode::ArgumentList);
graph.add_node(NodeId(3), symbol_lookup("x"));
graph.add_ast_edge(NodeId(2), NodeId(3));
let path = vec![NodeId(1)];
assert_eq!(
search_all_related_until_def(&graph, &path, "x"),
[NodeId(1)]
);
assert!(search_until_def(&graph, &path, "x").is_empty());
}
#[test]
fn file_node_matches_an_import_alias() {
let mut graph = SyntaxGraph::new();
graph.add_node(NodeId(1), SyntaxNode::File);
graph.add_node(
NodeId(2),
SyntaxNode::Import {
expression: Some(String::from("os.path")),
alias: Some(String::from("p")),
method_name: None,
import_type: None,
},
);
graph.add_cfg_edge(NodeId(1), NodeId(2));
let path = vec![NodeId(1)];
assert_eq!(definition_search(&graph, &path, "p"), Some(NodeId(2)));
}
}