use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tree_sitter::Node;
use walkdir::WalkDir;
use crate::ast::parser::parse_file;
use crate::security::ast_utils;
use crate::types::Language;
use crate::TldrResult;
const MAX_CONTEXT_LENGTH: usize = 200;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReferencesReport {
pub symbol: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub definition: Option<Definition>,
pub references: Vec<Reference>,
pub total_references: usize,
pub search_scope: SearchScope,
pub stats: ReferenceStats,
}
impl ReferencesReport {
pub fn new(symbol: String) -> Self {
Self {
symbol,
..Default::default()
}
}
pub fn no_matches(symbol: String, scope: SearchScope, stats: ReferenceStats) -> Self {
Self {
symbol,
definition: None,
references: Vec::new(),
total_references: 0,
search_scope: scope,
stats,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Definition {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub kind: DefinitionKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
}
impl Definition {
pub fn new(file: PathBuf, line: usize, column: usize, kind: DefinitionKind) -> Self {
Self {
file,
line,
column,
kind,
signature: None,
}
}
pub fn with_signature(
file: PathBuf,
line: usize,
column: usize,
kind: DefinitionKind,
signature: String,
) -> Self {
Self {
file,
line,
column,
kind,
signature: Some(signature),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "lowercase")]
pub enum DefinitionKind {
Function,
Class,
Variable,
Constant,
Type,
Module,
Method,
Property,
#[default]
Other,
}
impl DefinitionKind {
pub fn as_str(&self) -> &'static str {
match self {
DefinitionKind::Function => "function",
DefinitionKind::Class => "class",
DefinitionKind::Variable => "variable",
DefinitionKind::Constant => "constant",
DefinitionKind::Type => "type",
DefinitionKind::Module => "module",
DefinitionKind::Method => "method",
DefinitionKind::Property => "property",
DefinitionKind::Other => "other",
}
}
}
impl std::fmt::Display for DefinitionKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reference {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub kind: ReferenceKind,
pub context: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub end_column: Option<usize>,
}
impl Reference {
pub fn new(
file: PathBuf,
line: usize,
column: usize,
kind: ReferenceKind,
context: String,
) -> Self {
Self {
file,
line,
column,
kind,
context: truncate_context(context),
confidence: None,
end_column: None,
}
}
pub fn with_details(
file: PathBuf,
line: usize,
column: usize,
end_column: usize,
kind: ReferenceKind,
context: String,
confidence: f64,
) -> Self {
Self {
file,
line,
column,
kind,
context: truncate_context(context),
confidence: Some(confidence),
end_column: Some(end_column),
}
}
pub fn verified(
file: PathBuf,
line: usize,
column: usize,
kind: ReferenceKind,
context: String,
) -> Self {
Self {
file,
line,
column,
kind,
context: truncate_context(context),
confidence: Some(1.0),
end_column: None,
}
}
}
fn truncate_context(context: String) -> String {
if context.len() > MAX_CONTEXT_LENGTH {
let truncated: String = context.chars().take(MAX_CONTEXT_LENGTH - 3).collect();
format!("{}...", truncated)
} else {
context
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "lowercase")]
pub enum ReferenceKind {
Call,
Read,
Write,
Import,
Type,
Definition,
#[default]
Other,
}
impl ReferenceKind {
pub fn as_str(&self) -> &'static str {
match self {
ReferenceKind::Call => "call",
ReferenceKind::Read => "read",
ReferenceKind::Write => "write",
ReferenceKind::Import => "import",
ReferenceKind::Type => "type",
ReferenceKind::Definition => "definition",
ReferenceKind::Other => "other",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"call" => Some(ReferenceKind::Call),
"read" => Some(ReferenceKind::Read),
"write" => Some(ReferenceKind::Write),
"import" => Some(ReferenceKind::Import),
"type" => Some(ReferenceKind::Type),
"definition" => Some(ReferenceKind::Definition),
"other" => Some(ReferenceKind::Other),
_ => None,
}
}
}
impl std::fmt::Display for ReferenceKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "lowercase")]
pub enum SearchScope {
Local,
File,
#[default]
Workspace,
}
impl SearchScope {
pub fn as_str(&self) -> &'static str {
match self {
SearchScope::Local => "local",
SearchScope::File => "file",
SearchScope::Workspace => "workspace",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"local" => Some(SearchScope::Local),
"file" => Some(SearchScope::File),
"workspace" => Some(SearchScope::Workspace),
_ => None,
}
}
}
impl std::fmt::Display for SearchScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReferenceStats {
pub files_searched: usize,
pub candidates_found: usize,
pub verified_references: usize,
pub search_time_ms: u64,
}
impl ReferenceStats {
pub fn new(files_searched: usize, candidates_found: usize, verified_references: usize) -> Self {
Self {
files_searched,
candidates_found,
verified_references,
search_time_ms: 0,
}
}
pub fn with_time(mut self, time_ms: u64) -> Self {
self.search_time_ms = time_ms;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct ReferencesOptions {
pub include_definition: bool,
pub kinds: Option<Vec<ReferenceKind>>,
pub scope: SearchScope,
pub language: Option<String>,
pub limit: Option<usize>,
pub definition_file: Option<PathBuf>,
pub context_lines: usize,
}
impl ReferencesOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_definition(mut self) -> Self {
self.include_definition = true;
self
}
pub fn with_kinds(mut self, kinds: Vec<ReferenceKind>) -> Self {
self.kinds = Some(kinds);
self
}
pub fn with_scope(mut self, scope: SearchScope) -> Self {
self.scope = scope;
self
}
pub fn with_language(mut self, language: String) -> Self {
self.language = Some(language);
self
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn with_definition_file(mut self, file: PathBuf) -> Self {
self.definition_file = Some(file);
self
}
pub fn with_context_lines(mut self, lines: usize) -> Self {
self.context_lines = lines;
self
}
}
#[derive(Debug, Clone)]
pub struct TextCandidate {
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub end_column: usize,
pub line_text: String,
}
impl TextCandidate {
pub fn new(
file: PathBuf,
line: usize,
column: usize,
end_column: usize,
line_text: String,
) -> Self {
Self {
file,
line,
column,
end_column,
line_text,
}
}
}
pub fn find_text_candidates(
symbol: &str,
root: &Path,
language: Option<&str>,
) -> TldrResult<Vec<TextCandidate>> {
let mut candidates = Vec::new();
let pattern = format!(r"\b{}\b", regex::escape(symbol));
let re = Regex::new(&pattern)?;
for entry in WalkDir::new(root)
.into_iter()
.filter_entry(|e| {
if e.file_type().is_dir() {
let name = e.file_name().to_string_lossy();
!matches!(
name.as_ref(),
".git"
| "__pycache__"
| "node_modules"
| ".tox"
| "venv"
| ".venv"
| "__pypackages__"
| ".mypy_cache"
| ".pytest_cache"
| ".ruff_cache"
| "target"
| "build"
| "dist"
| ".next"
| ".nuxt"
| "vendor"
| ".bundle"
| "Pods"
| ".gradle"
| ".idea"
| ".vscode"
| ".eggs"
)
} else {
true
}
})
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.filter(|e| is_source_file(e.path(), language))
{
let content = match std::fs::read_to_string(entry.path()) {
Ok(c) => c,
Err(_) => continue, };
for (line_num, line) in content.lines().enumerate() {
if is_comment_line(line, language) {
continue;
}
for mat in re.find_iter(line) {
candidates.push(TextCandidate {
file: entry.path().to_path_buf(),
line: line_num + 1, column: mat.start() + 1, end_column: mat.end() + 1, line_text: line.to_string(),
});
}
}
}
Ok(candidates)
}
fn is_source_file(path: &Path, language: Option<&str>) -> bool {
match Language::from_path(path) {
Some(detected) => {
match language {
None => true, Some(lang) => {
let normalized = lang.to_lowercase();
match normalized.as_str() {
"python" => matches!(detected, Language::Python),
"typescript" => matches!(detected, Language::TypeScript),
"javascript" => {
matches!(detected, Language::JavaScript | Language::TypeScript)
}
"go" => matches!(detected, Language::Go),
"rust" => matches!(detected, Language::Rust),
"java" => matches!(detected, Language::Java),
"c" => matches!(detected, Language::C),
"cpp" => matches!(detected, Language::Cpp),
"csharp" => matches!(detected, Language::CSharp),
"kotlin" => matches!(detected, Language::Kotlin),
"scala" => matches!(detected, Language::Scala),
"swift" => matches!(detected, Language::Swift),
"php" => matches!(detected, Language::Php),
"ruby" => matches!(detected, Language::Ruby),
"lua" => matches!(detected, Language::Lua),
"luau" => matches!(detected, Language::Luau),
"elixir" => matches!(detected, Language::Elixir),
"ocaml" => matches!(detected, Language::Ocaml),
_ => false,
}
}
}
}
None => false, }
}
fn is_comment_line(line: &str, language: Option<&str>) -> bool {
let trimmed = line.trim();
match language {
Some("python") | Some("ruby") | Some("elixir") => trimmed.starts_with('#'),
Some("php") => {
trimmed.starts_with("//")
|| trimmed.starts_with('#')
|| trimmed.starts_with("/*")
|| trimmed.starts_with('*')
}
Some("typescript") | Some("javascript") | Some("go") | Some("rust") | Some("java")
| Some("c") | Some("cpp") | Some("csharp") | Some("kotlin") | Some("scala")
| Some("swift") => {
trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.starts_with('*')
}
Some("lua") | Some("luau") => trimmed.starts_with("--"),
Some("ocaml") => trimmed.starts_with("(*"),
None => {
trimmed.starts_with("//")
|| trimmed.starts_with('#')
|| trimmed.starts_with("/*")
|| trimmed.starts_with('*')
|| trimmed.starts_with("--")
|| trimmed.starts_with("(*")
}
_ => false,
}
}
fn count_source_files(root: &Path, language: Option<&str>) -> usize {
WalkDir::new(root)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.filter(|e| is_source_file(e.path(), language))
.count()
}
#[derive(Debug, Clone)]
pub struct VerifiedReference {
pub kind: ReferenceKind,
pub confidence: f64,
pub is_valid: bool,
}
pub fn verify_candidates_with_ast(
candidates: &[TextCandidate],
symbol: &str,
_language_str: Option<&str>,
) -> Vec<(TextCandidate, VerifiedReference)> {
let mut verified = Vec::new();
let mut by_file: HashMap<PathBuf, Vec<&TextCandidate>> = HashMap::new();
for candidate in candidates {
by_file
.entry(candidate.file.clone())
.or_default()
.push(candidate);
}
for (file_path, file_candidates) in by_file {
let parsed = match parse_file(&file_path) {
Ok(p) => p,
Err(_) => {
for candidate in file_candidates {
verified.push((
candidate.clone(),
VerifiedReference {
kind: ReferenceKind::Other,
confidence: 0.5, is_valid: true, },
));
}
continue;
}
};
let (tree, source, lang) = parsed;
let source_bytes = source.as_bytes();
for candidate in file_candidates {
if let Some(verified_ref) =
verify_single_candidate(candidate, symbol, &tree, source_bytes, lang)
{
if verified_ref.is_valid {
verified.push((candidate.clone(), verified_ref));
}
}
}
}
verified
}
fn verify_single_candidate(
candidate: &TextCandidate,
symbol: &str,
tree: &tree_sitter::Tree,
source: &[u8],
language: Language,
) -> Option<VerifiedReference> {
let point = tree_sitter::Point::new(candidate.line - 1, candidate.column - 1);
let node = tree.root_node().descendant_for_point_range(point, point)?;
let node_text = node.utf8_text(source).ok()?;
if node_text != symbol {
return find_exact_match_node(&node, symbol, source, language);
}
if is_in_invalid_context(&node, language) {
return Some(VerifiedReference {
kind: ReferenceKind::Other,
confidence: 1.0,
is_valid: false, });
}
let kind = classify_reference_kind(&node, source, language);
Some(VerifiedReference {
kind,
confidence: 1.0, is_valid: true,
})
}
fn find_exact_match_node(
node: &Node,
symbol: &str,
source: &[u8],
language: Language,
) -> Option<VerifiedReference> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Ok(text) = child.utf8_text(source) {
if text == symbol {
if is_in_invalid_context(&child, language) {
return Some(VerifiedReference {
kind: ReferenceKind::Other,
confidence: 1.0,
is_valid: false,
});
}
let kind = classify_reference_kind(&child, source, language);
return Some(VerifiedReference {
kind,
confidence: 1.0,
is_valid: true,
});
}
}
}
Some(VerifiedReference {
kind: ReferenceKind::Other,
confidence: 0.5,
is_valid: true,
})
}
fn is_in_invalid_context(node: &Node, language: Language) -> bool {
let node_kind = node.kind();
let invalid_self_kinds = [
"string",
"string_literal",
"string_content",
"template_string",
"raw_string_literal",
"comment",
"line_comment",
"block_comment",
"heredoc_content",
];
if invalid_self_kinds.contains(&node_kind) {
return true;
}
let mut current = node.parent();
while let Some(ancestor) = current {
let kind = ancestor.kind();
match language {
Language::Python => {
if matches!(
kind,
"string" | "string_content" | "concatenated_string" | "comment"
) {
return true;
}
if kind == "string" {
if !is_inside_format_expression(node) {
return true;
}
}
}
Language::TypeScript | Language::JavaScript => {
if matches!(
kind,
"string" | "template_string" | "string_fragment" | "comment"
) {
if kind == "template_string" && has_template_substitution(&ancestor) {
if is_inside_template_substitution(node) {
return false;
}
}
return true;
}
}
Language::Go => {
if matches!(
kind,
"raw_string_literal" | "interpreted_string_literal" | "comment"
) {
return true;
}
}
Language::Rust => {
if matches!(
kind,
"string_literal" | "raw_string_literal" | "line_comment" | "block_comment"
) {
return true;
}
}
_ => {
if kind.contains("string") || kind.contains("comment") {
return true;
}
}
}
current = ancestor.parent();
}
false
}
fn is_inside_format_expression(node: &Node) -> bool {
let mut current = node.parent();
while let Some(ancestor) = current {
if ancestor.kind() == "interpolation" || ancestor.kind() == "format_expression" {
return true;
}
if ancestor.kind() == "string" || ancestor.kind() == "concatenated_string" {
return false; }
current = ancestor.parent();
}
false
}
fn has_template_substitution(node: &Node) -> bool {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "template_substitution" {
return true;
}
}
false
}
fn is_inside_template_substitution(node: &Node) -> bool {
let mut current = node.parent();
while let Some(ancestor) = current {
if ancestor.kind() == "template_substitution" {
return true;
}
if ancestor.kind() == "template_string" {
return false;
}
current = ancestor.parent();
}
false
}
pub fn classify_reference_kind(node: &Node, source: &[u8], language: Language) -> ReferenceKind {
let parent = match node.parent() {
Some(p) => p,
None => return ReferenceKind::Other,
};
match language {
Language::Python => classify_python_reference(node, &parent, source),
Language::TypeScript | Language::JavaScript => {
classify_typescript_reference(node, &parent, source)
}
Language::Go => classify_go_reference(node, &parent, source),
Language::Rust => classify_rust_reference(node, &parent, source),
Language::Java => classify_java_reference(node, &parent, source),
Language::C => classify_c_reference(node, &parent, source),
Language::Cpp => classify_cpp_reference(node, &parent, source),
Language::CSharp => classify_csharp_reference(node, &parent, source),
Language::Kotlin => classify_kotlin_reference(node, &parent, source),
Language::Scala => classify_scala_reference(node, &parent, source),
Language::Swift => classify_swift_reference(node, &parent, source),
Language::Php => classify_php_reference(node, &parent, source),
Language::Ruby => classify_ruby_reference(node, &parent, source),
Language::Lua => classify_lua_reference(node, &parent, source),
Language::Luau => classify_luau_reference(node, &parent, source),
Language::Elixir => classify_elixir_reference(node, &parent, source),
Language::Ocaml => classify_ocaml_reference(node, &parent, source),
}
}
fn classify_python_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call" => ReferenceKind::Call,
"argument_list" => {
ReferenceKind::Read
}
"assignment" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"augmented_assignment" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"import_statement" | "import_from_statement" | "dotted_name" | "aliased_import" => {
let mut current = Some(*parent);
while let Some(ancestor) = current {
if ancestor.kind() == "import_statement"
|| ancestor.kind() == "import_from_statement"
{
return ReferenceKind::Import;
}
current = ancestor.parent();
}
ReferenceKind::Read
}
"type" | "annotation" | "subscript" => {
if is_type_context(parent) {
return ReferenceKind::Type;
}
ReferenceKind::Read
}
"function_definition" | "class_definition" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"parameter" | "typed_parameter" | "default_parameter" | "typed_default_parameter" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(type_node) = parent.child_by_field_name("type") {
if node_contains(node, &type_node) {
return ReferenceKind::Type;
}
}
ReferenceKind::Read
}
"for_statement" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"for_in_clause" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"attribute" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call" {
if let Some(func) = grandparent.child_by_field_name("function") {
if func.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn classify_typescript_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"variable_declarator" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"import_specifier" | "import_clause" | "namespace_import" => ReferenceKind::Import,
"type_annotation" | "type_identifier" | "generic_type" | "type_arguments" => {
ReferenceKind::Type
}
"function_declaration" | "class_declaration" | "method_definition" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"member_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(func) = grandparent.child_by_field_name("function") {
if func.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn classify_go_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"assignment_statement" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"short_var_declaration" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"import_spec" => ReferenceKind::Import,
"type_identifier" | "qualified_type" | "pointer_type" | "slice_type" | "array_type" => {
ReferenceKind::Type
}
"function_declaration" | "method_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"selector_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(func) = grandparent.child_by_field_name("function") {
if func.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn classify_rust_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"let_declaration" => {
if let Some(pattern) = parent.child_by_field_name("pattern") {
if node_contains(node, &pattern) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"use_declaration" | "use_clause" | "scoped_identifier" => {
let mut current = Some(*parent);
while let Some(ancestor) = current {
if ancestor.kind() == "use_declaration" {
return ReferenceKind::Import;
}
current = ancestor.parent();
}
ReferenceKind::Read
}
"type_identifier" | "generic_type" | "scoped_type_identifier" | "reference_type" => {
ReferenceKind::Type
}
"function_item" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"field_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(func) = grandparent.child_by_field_name("function") {
if func.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn first_named_child_of_kind<'a>(node: &Node<'a>, kinds: &[&str]) -> Option<Node<'a>> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if kinds.contains(&child.kind()) {
return Some(child);
}
}
}
None
}
fn classify_java_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"method_invocation" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Call;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"method_declaration" | "constructor_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class_declaration" | "interface_declaration" | "enum_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"import_declaration" => ReferenceKind::Import,
"field_access" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "method_invocation" {
if let Some(name) = grandparent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"type_identifier" | "generic_type" | "array_type" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_c_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
if let Some(first) = parent.child(0) {
if node.id() == first.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_declarator" => {
if let Some(decl) = parent.child_by_field_name("declarator") {
if node.id() == decl.id() {
return classify_c_declarator_as_definition(parent);
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"parameter_declaration" => {
if let Some(decl) = parent.child_by_field_name("declarator") {
if node_contains(node, &decl) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"preproc_include" => ReferenceKind::Import,
"field_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"type_identifier" | "sized_type_specifier" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_c_declarator_as_definition(_declarator: &Node) -> ReferenceKind {
ReferenceKind::Definition
}
fn classify_cpp_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
if let Some(first) = parent.child(0) {
if node.id() == first.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_declarator" => {
if let Some(first_id) = first_named_child_of_kind(
parent,
&[
"identifier",
"field_identifier",
"qualified_identifier",
"destructor_name",
],
) {
if node.id() == first_id.id() || node_contains(node, &first_id) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"qualified_identifier" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
if grandparent.kind() == "function_declarator" {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"field_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"parameter_declaration" => {
if let Some(decl) = parent.child_by_field_name("declarator") {
if node_contains(node, &decl) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"preproc_include" => ReferenceKind::Import,
"type_identifier" | "template_type" | "sized_type_specifier" => ReferenceKind::Type,
"class_specifier" | "struct_specifier" | "namespace_definition" => {
if let Some(name) = parent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn classify_csharp_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"invocation_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
if let Some(first) = parent.child(0) {
if node.id() == first.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"method_declaration" | "constructor_declaration" | "local_function_statement" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class_declaration" | "interface_declaration" | "struct_declaration"
| "enum_declaration" | "record_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"member_access_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "invocation_expression" {
if let Some(func) = grandparent.child_by_field_name("function") {
if func.id() == parent.id() {
return ReferenceKind::Call;
}
}
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"using_directive" | "namespace_declaration" => ReferenceKind::Import,
"type_parameter" | "type_argument_list" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_kotlin_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(first) = parent.child(0) {
if node.id() == first.id() || node_contains(node, &first) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_declaration" | "class_declaration" | "object_declaration"
| "property_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"navigation_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"assignment" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"import_header" | "import_list" => ReferenceKind::Import,
"user_type" | "type_reference" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_scala_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(first) = parent.child(0) {
if node.id() == first.id() || node_contains(node, &first) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_definition" | "function_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) =
first_named_child_of_kind(parent, &["identifier", "type_identifier"])
{
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class_definition" | "object_definition" | "trait_definition" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) =
first_named_child_of_kind(parent, &["identifier", "type_identifier"])
{
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"field_expression" | "select_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"import_declaration" | "import_expression" | "import_selectors" => ReferenceKind::Import,
"type_identifier" | "generic_type" | "compound_type" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_swift_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call_expression" => {
if let Some(first) = parent.child(0) {
if node.id() == first.id() || node_contains(node, &first) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_declaration" | "protocol_function_declaration" | "init_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["simple_identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class_declaration" | "protocol_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"property_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"navigation_expression" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call_expression" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"assignment" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"import_declaration" => ReferenceKind::Import,
"user_type" | "type_identifier" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_php_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"function_call_expression" => {
if let Some(func) = parent.child_by_field_name("function") {
if node_contains(node, &func) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"member_call_expression" | "scoped_call_expression" => {
if let Some(name) = parent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_definition" | "method_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class_declaration" | "trait_declaration" | "interface_declaration" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"assignment_expression" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
"namespace_use_declaration" | "namespace_use_clause" | "namespace_name" => {
ReferenceKind::Import
}
"named_type" | "primitive_type" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn classify_ruby_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call" => {
if let Some(name) = parent.child_by_field_name("method") {
if node_contains(node, &name) {
return ReferenceKind::Call;
}
}
let mut last_id: Option<Node> = None;
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "argument_list" {
break;
}
if child.kind() == "identifier" {
last_id = Some(child);
}
}
}
if let Some(id) = last_id {
if node.id() == id.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"method" | "singleton_method" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"class" | "module" => {
if let Some(name) = parent.child_by_field_name("name") {
if node_contains(node, &name) {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"assignment" | "operator_assignment" => {
if let Some(left) = parent.child_by_field_name("left") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn classify_lua_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
classify_lua_family_reference(node, parent)
}
fn classify_luau_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
classify_lua_family_reference(node, parent)
}
fn classify_lua_family_reference(node: &Node, parent: &Node) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"function_call" => {
if let Some(first) = parent.child(0) {
if node.id() == first.id() || node_contains(node, &first) {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"function_declaration" | "local_function" | "function_definition_statement" => {
if let Some(name) = parent.child_by_field_name("name") {
if node.id() == name.id() {
return ReferenceKind::Definition;
}
}
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"dot_index_expression" | "method_index_expression" => {
if let Some(grandparent) = parent.parent() {
match grandparent.kind() {
"function_call" => {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
"function_declaration" => {
return ReferenceKind::Definition;
}
_ => {}
}
}
ReferenceKind::Read
}
"assignment_statement" => {
if let Some(left) = parent.child_by_field_name("variables") {
if node_contains(node, &left) {
return ReferenceKind::Write;
}
}
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "variable_list" {
if node_contains(node, &child) {
return ReferenceKind::Write;
}
break;
}
}
}
ReferenceKind::Read
}
"variable_declaration" => {
ReferenceKind::Definition
}
_ => ReferenceKind::Read,
}
}
fn classify_elixir_reference(node: &Node, parent: &Node, source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"call" => {
if let Some(first_id) = first_named_child_of_kind(parent, &["identifier"]) {
if node.id() == first_id.id() {
if is_elixir_def_call(parent, source) {
return ReferenceKind::Definition;
}
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"dot" => {
if let Some(grandparent) = parent.parent() {
if grandparent.kind() == "call" {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
let mut last_id: Option<Node> = None;
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i) {
if child.kind() == "identifier" {
last_id = Some(child);
}
}
}
if let Some(id) = last_id {
if node.id() == id.id() {
return ReferenceKind::Call;
}
}
}
}
}
}
ReferenceKind::Read
}
_ => ReferenceKind::Read,
}
}
fn is_elixir_def_call(call_node: &Node, source: &[u8]) -> bool {
let args = match call_node.parent() {
Some(p) if p.kind() == "arguments" => p,
_ => return false,
};
let outer_call = match args.parent() {
Some(p) if p.kind() == "call" => p,
_ => return false,
};
for i in 0..outer_call.child_count() {
if let Some(child) = outer_call.child(i) {
if child.kind() == "identifier" {
let start = child.start_byte();
let end = child.end_byte();
if end <= source.len() {
let text = &source[start..end];
return matches!(text, b"def" | b"defp" | b"defmacro" | b"defmacrop");
}
return false;
}
}
}
false
}
fn classify_ocaml_reference(node: &Node, parent: &Node, _source: &[u8]) -> ReferenceKind {
let parent_kind = parent.kind();
match parent_kind {
"let_binding" | "value_definition" => {
if let Some(name) = parent.child_by_field_name("pattern") {
if node.id() == name.id() || node_contains(node, &name) {
return ReferenceKind::Definition;
}
}
if let Some(first_name) = first_named_child_of_kind(parent, &["value_name"]) {
if node.id() == first_name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"value_path" => {
if let Some(grandparent) = parent.parent() {
if matches!(grandparent.kind(), "application_expression" | "application") {
if let Some(first) = grandparent.child(0) {
if first.id() == parent.id() {
return ReferenceKind::Call;
}
}
}
}
ReferenceKind::Read
}
"application_expression" | "application" => {
if let Some(first) = parent.child(0) {
if node.id() == first.id() {
return ReferenceKind::Call;
}
}
ReferenceKind::Read
}
"module_binding" | "module_definition" => {
if let Some(first_name) = first_named_child_of_kind(parent, &["module_name"]) {
if node.id() == first_name.id() {
return ReferenceKind::Definition;
}
}
ReferenceKind::Read
}
"open_module" | "include_module" => ReferenceKind::Import,
"type_constructor" | "type_constructor_path" => ReferenceKind::Type,
_ => ReferenceKind::Read,
}
}
fn node_contains(inner: &Node, outer: &Node) -> bool {
inner.start_byte() >= outer.start_byte() && inner.end_byte() <= outer.end_byte()
}
fn is_type_context(node: &Node) -> bool {
let mut current = Some(*node);
while let Some(n) = current {
let kind = n.kind();
if matches!(
kind,
"type"
| "annotation"
| "type_annotation"
| "return_type"
| "parameter"
| "typed_parameter"
| "generic_type"
| "type_arguments"
) {
return true;
}
if kind.ends_with("_statement") || kind.ends_with("_definition") {
return false;
}
current = n.parent();
}
false
}
pub fn find_definition(
symbol: &str,
root: &Path,
language: Option<&str>,
) -> TldrResult<Option<Definition>> {
for entry in WalkDir::new(root)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.filter(|e| is_source_file(e.path(), language))
{
if let Ok(Some(def)) = find_definition_in_file(symbol, entry.path(), language) {
return Ok(Some(def));
}
}
Ok(None)
}
fn find_definition_in_file(
symbol: &str,
file_path: &Path,
_language_str: Option<&str>,
) -> TldrResult<Option<Definition>> {
let parsed = parse_file(file_path)?;
let (tree, source, language) = parsed;
let source_bytes = source.as_bytes();
let root = tree.root_node();
find_definition_in_node(&root, symbol, source_bytes, language, file_path)
}
fn find_definition_in_node(
node: &Node,
symbol: &str,
source: &[u8],
language: Language,
file_path: &Path,
) -> TldrResult<Option<Definition>> {
if let Some(def) = check_definition_node(node, symbol, source, language, file_path)? {
return Ok(Some(def));
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(def) = find_definition_in_node(&child, symbol, source, language, file_path)? {
return Ok(Some(def));
}
}
Ok(None)
}
fn check_definition_node(
node: &Node,
symbol: &str,
source: &[u8],
language: Language,
file_path: &Path,
) -> TldrResult<Option<Definition>> {
match language {
Language::Python => check_python_definition(node, symbol, source, file_path),
Language::TypeScript | Language::JavaScript => {
check_ts_definition(node, symbol, source, file_path)
}
Language::Go => check_go_definition(node, symbol, source, file_path),
Language::Rust => check_rust_definition(node, symbol, source, file_path),
_ => Ok(None),
}
}
fn check_python_definition(
node: &Node,
symbol: &str,
source: &[u8],
file_path: &Path,
) -> TldrResult<Option<Definition>> {
let node_kind = node.kind();
match node_kind {
"function_definition" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Python);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Function,
signature,
}));
}
}
}
"class_definition" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Python);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Class,
signature,
}));
}
}
}
"assignment" | "expression_statement" => {
if let Some(parent) = node.parent() {
if parent.kind() == "module" || parent.kind() == "expression_statement" {
let target_node = if node_kind == "assignment" {
node.child_by_field_name("left")
} else {
node.child(0).and_then(|c| c.child_by_field_name("left"))
};
if let Some(left) = target_node {
if left.kind() == "identifier"
&& left.utf8_text(source).unwrap_or("") == symbol
{
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: left.start_position().row + 1,
column: left.start_position().column + 1,
kind: DefinitionKind::Variable,
signature: None,
}));
}
}
}
}
}
_ => {}
}
Ok(None)
}
fn check_ts_definition(
node: &Node,
symbol: &str,
source: &[u8],
file_path: &Path,
) -> TldrResult<Option<Definition>> {
let node_kind = node.kind();
match node_kind {
"function_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::TypeScript);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Function,
signature,
}));
}
}
}
"class_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::TypeScript);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Class,
signature,
}));
}
}
}
"variable_declarator" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let kind = if let Some(parent) = node.parent() {
if let Some(gp) = parent.parent() {
let decl_text = gp.utf8_text(source).unwrap_or("");
if decl_text.starts_with("const") {
DefinitionKind::Constant
} else {
DefinitionKind::Variable
}
} else {
DefinitionKind::Variable
}
} else {
DefinitionKind::Variable
};
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: name_node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind,
signature: None,
}));
}
}
}
"type_alias_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::TypeScript);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
"interface_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::TypeScript);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
_ => {}
}
Ok(None)
}
fn check_go_definition(
node: &Node,
symbol: &str,
source: &[u8],
file_path: &Path,
) -> TldrResult<Option<Definition>> {
let node_kind = node.kind();
match node_kind {
"function_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Go);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Function,
signature,
}));
}
}
}
"method_declaration" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Go);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Method,
signature,
}));
}
}
}
"type_declaration" | "type_spec" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Go);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
_ => {}
}
Ok(None)
}
fn check_rust_definition(
node: &Node,
symbol: &str,
source: &[u8],
file_path: &Path,
) -> TldrResult<Option<Definition>> {
let node_kind = node.kind();
match node_kind {
"function_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Function,
signature,
}));
}
}
}
"struct_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
"enum_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
"const_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Constant,
signature,
}));
}
}
}
"static_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Variable,
signature,
}));
}
}
}
"type_item" => {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == symbol {
let signature = extract_signature(node, source, Language::Rust);
return Ok(Some(Definition {
file: file_path.to_path_buf(),
line: node.start_position().row + 1,
column: name_node.start_position().column + 1,
kind: DefinitionKind::Type,
signature,
}));
}
}
}
_ => {}
}
Ok(None)
}
fn extract_signature(node: &Node, source: &[u8], _language: Language) -> Option<String> {
let node_text = node.utf8_text(source).ok()?;
let first_line = node_text.lines().next()?;
let signature = if first_line.len() > MAX_CONTEXT_LENGTH {
format!("{}...", &first_line[..MAX_CONTEXT_LENGTH - 3])
} else {
first_line.to_string()
};
Some(signature.trim().to_string())
}
pub fn find_references(
symbol: &str,
root: &Path,
options: &ReferencesOptions,
) -> TldrResult<ReferencesReport> {
let start = std::time::Instant::now();
let language = options.language.as_deref();
let effective_scope = if options.scope != SearchScope::Workspace {
options.scope
} else if let Some(lang) = language {
determine_search_scope(symbol, options.definition_file.as_deref(), lang)
} else {
SearchScope::Workspace
};
let candidates = find_text_candidates(symbol, root, language)?;
let candidates_found = candidates.len();
let scoped_candidates = apply_scope_filter(
candidates,
effective_scope,
options.definition_file.as_deref(),
);
let verified = verify_candidates_with_ast(&scoped_candidates, symbol, language);
let mut references: Vec<Reference> = verified
.into_iter()
.map(|(candidate, verified_ref)| Reference {
file: candidate.file,
line: candidate.line,
column: candidate.column,
kind: verified_ref.kind,
context: truncate_context(candidate.line_text),
confidence: Some(verified_ref.confidence),
end_column: Some(candidate.end_column),
})
.collect();
let definition = find_definition(symbol, root, language)?;
if let Some(ref kinds) = options.kinds {
references.retain(|r| kinds.contains(&r.kind));
}
if let Some(limit) = options.limit {
references.truncate(limit);
}
let files_searched = count_source_files(root, language);
let verified_references = references.len();
let stats = ReferenceStats {
files_searched,
candidates_found,
verified_references,
search_time_ms: start.elapsed().as_millis() as u64,
};
Ok(ReferencesReport {
symbol: symbol.to_string(),
definition,
references,
total_references: verified_references,
search_scope: effective_scope, stats,
})
}
pub fn determine_search_scope(
symbol: &str,
definition_file: Option<&Path>,
language: &str,
) -> SearchScope {
match language.to_lowercase().as_str() {
"python" => determine_python_scope(symbol, definition_file),
"typescript" | "javascript" => determine_ts_scope(symbol, definition_file),
"go" => determine_go_scope(symbol, definition_file),
"rust" => determine_rust_scope(symbol, definition_file),
_ => SearchScope::Workspace, }
}
fn determine_python_scope(symbol: &str, _definition_file: Option<&Path>) -> SearchScope {
if symbol.starts_with("__") && symbol.ends_with("__") {
return SearchScope::Workspace;
}
if symbol.starts_with("__") && !symbol.ends_with("__") {
return SearchScope::File;
}
if symbol.starts_with('_') && !symbol.starts_with("__") {
return SearchScope::File;
}
SearchScope::Workspace
}
fn determine_ts_scope(_symbol: &str, _definition_file: Option<&Path>) -> SearchScope {
SearchScope::Workspace
}
fn determine_go_scope(symbol: &str, _definition_file: Option<&Path>) -> SearchScope {
if let Some(first_char) = symbol.chars().next() {
if first_char.is_uppercase() {
return SearchScope::Workspace;
}
return SearchScope::File;
}
SearchScope::Workspace
}
fn determine_rust_scope(_symbol: &str, _definition_file: Option<&Path>) -> SearchScope {
SearchScope::Workspace
}
pub fn apply_scope_filter(
candidates: Vec<TextCandidate>,
scope: SearchScope,
definition_file: Option<&Path>,
) -> Vec<TextCandidate> {
match scope {
SearchScope::Workspace => candidates, SearchScope::File => {
if let Some(def_file) = definition_file {
candidates
.into_iter()
.filter(|c| c.file == def_file)
.collect()
} else {
candidates }
}
SearchScope::Local => {
if let Some(def_file) = definition_file {
candidates
.into_iter()
.filter(|c| c.file == def_file)
.collect()
} else {
candidates
}
}
}
}
pub fn filter_by_kinds(
references: Vec<Reference>,
allowed_kinds: &[ReferenceKind],
) -> Vec<Reference> {
references
.into_iter()
.filter(|r| allowed_kinds.contains(&r.kind))
.collect()
}
pub fn get_incoming_calls(
symbol: &str,
root: &Path,
options: &ReferencesOptions,
) -> TldrResult<Vec<Reference>> {
let report = find_references(symbol, root, options)?;
Ok(report
.references
.into_iter()
.filter(|r| r.kind == ReferenceKind::Call)
.collect())
}
pub fn get_outgoing_calls(file: &Path, function: &str) -> TldrResult<Vec<String>> {
use crate::ast::parser::parse_file;
let (tree, source, language) = parse_file(file)?;
let source_bytes = source.as_bytes();
let root = tree.root_node();
let calls = find_and_extract_calls(&root, function, source_bytes, language);
Ok(calls)
}
fn find_and_extract_calls(
node: &tree_sitter::Node,
function_name: &str,
source: &[u8],
language: Language,
) -> Vec<String> {
let node_kind = node.kind();
let is_function = ast_utils::function_node_kinds(language).contains(&node_kind);
if is_function {
if let Some(name_node) = node.child_by_field_name("name") {
if name_node.utf8_text(source).unwrap_or("") == function_name {
let mut calls = Vec::new();
extract_calls_recursive(node, source, language, &mut calls);
return calls;
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let calls = find_and_extract_calls(&child, function_name, source, language);
if !calls.is_empty() {
return calls;
}
}
Vec::new()
}
fn extract_calls_recursive(
node: &tree_sitter::Node,
source: &[u8],
language: Language,
calls: &mut Vec<String>,
) {
let node_kind = node.kind();
let is_call = ast_utils::call_node_kinds(language).contains(&node_kind);
if is_call {
if let Some(func_node) = node.child_by_field_name("function") {
let func_text = func_node.utf8_text(source).unwrap_or("");
let call_name = if func_text.contains('.') {
func_text.rsplit('.').next().unwrap_or(func_text)
} else {
func_text
};
if !call_name.is_empty() {
calls.push(call_name.to_string());
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
extract_calls_recursive(&child, source, language, calls);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_references_report_default() {
let report = ReferencesReport::default();
assert!(report.symbol.is_empty());
assert!(report.definition.is_none());
assert!(report.references.is_empty());
assert_eq!(report.total_references, 0);
assert_eq!(report.search_scope, SearchScope::Workspace);
}
#[test]
fn test_references_report_new() {
let report = ReferencesReport::new("test_symbol".to_string());
assert_eq!(report.symbol, "test_symbol");
assert!(report.definition.is_none());
assert!(report.references.is_empty());
}
#[test]
fn test_definition_kind_serialization() {
let kinds = vec![
DefinitionKind::Function,
DefinitionKind::Class,
DefinitionKind::Variable,
DefinitionKind::Constant,
DefinitionKind::Type,
DefinitionKind::Module,
DefinitionKind::Method,
DefinitionKind::Property,
DefinitionKind::Other,
];
for kind in kinds {
let json = serde_json::to_string(&kind).unwrap();
let parsed: DefinitionKind = serde_json::from_str(&json).unwrap();
assert_eq!(kind, parsed);
}
}
#[test]
fn test_reference_kind_serialization() {
let kinds = vec![
ReferenceKind::Call,
ReferenceKind::Read,
ReferenceKind::Write,
ReferenceKind::Import,
ReferenceKind::Type,
ReferenceKind::Definition,
ReferenceKind::Other,
];
for kind in kinds {
let json = serde_json::to_string(&kind).unwrap();
let parsed: ReferenceKind = serde_json::from_str(&json).unwrap();
assert_eq!(kind, parsed);
}
}
#[test]
fn test_search_scope_serialization() {
let scopes = vec![
SearchScope::Local,
SearchScope::File,
SearchScope::Workspace,
];
for scope in scopes {
let json = serde_json::to_string(&scope).unwrap();
let parsed: SearchScope = serde_json::from_str(&json).unwrap();
assert_eq!(scope, parsed);
}
}
#[test]
fn test_reference_kind_parse() {
assert_eq!(ReferenceKind::parse("call"), Some(ReferenceKind::Call));
assert_eq!(ReferenceKind::parse("CALL"), Some(ReferenceKind::Call));
assert_eq!(ReferenceKind::parse("read"), Some(ReferenceKind::Read));
assert_eq!(ReferenceKind::parse("invalid"), None);
}
#[test]
fn test_search_scope_parse() {
assert_eq!(SearchScope::parse("local"), Some(SearchScope::Local));
assert_eq!(SearchScope::parse("FILE"), Some(SearchScope::File));
assert_eq!(
SearchScope::parse("workspace"),
Some(SearchScope::Workspace)
);
assert_eq!(SearchScope::parse("invalid"), None);
}
#[test]
fn test_truncate_context_short() {
let short = "def login(): pass".to_string();
let result = truncate_context(short.clone());
assert_eq!(result, short);
}
#[test]
fn test_truncate_context_long() {
let long: String = "x".repeat(300);
let result = truncate_context(long);
assert!(result.len() <= MAX_CONTEXT_LENGTH);
assert!(result.ends_with("..."));
}
#[test]
fn test_definition_new() {
let def = Definition::new(
PathBuf::from("src/auth.py"),
42,
5,
DefinitionKind::Function,
);
assert_eq!(def.file, PathBuf::from("src/auth.py"));
assert_eq!(def.line, 42);
assert_eq!(def.column, 5);
assert_eq!(def.kind, DefinitionKind::Function);
assert!(def.signature.is_none());
}
#[test]
fn test_definition_with_signature() {
let def = Definition::with_signature(
PathBuf::from("src/auth.py"),
42,
5,
DefinitionKind::Function,
"def login(username: str, password: str) -> bool:".to_string(),
);
assert!(def.signature.is_some());
assert!(def.signature.as_ref().unwrap().contains("login"));
}
#[test]
fn test_reference_new() {
let ref_ = Reference::new(
PathBuf::from("src/routes.py"),
15,
12,
ReferenceKind::Call,
"result = auth.login(username, password)".to_string(),
);
assert_eq!(ref_.file, PathBuf::from("src/routes.py"));
assert_eq!(ref_.line, 15);
assert_eq!(ref_.column, 12);
assert_eq!(ref_.kind, ReferenceKind::Call);
assert!(ref_.confidence.is_none());
}
#[test]
fn test_reference_verified() {
let ref_ = Reference::verified(
PathBuf::from("src/routes.py"),
15,
12,
ReferenceKind::Call,
"login()".to_string(),
);
assert_eq!(ref_.confidence, Some(1.0));
}
#[test]
fn test_reference_stats_default() {
let stats = ReferenceStats::default();
assert_eq!(stats.files_searched, 0);
assert_eq!(stats.candidates_found, 0);
assert_eq!(stats.verified_references, 0);
assert_eq!(stats.search_time_ms, 0);
}
#[test]
fn test_reference_stats_with_time() {
let stats = ReferenceStats::new(10, 50, 25).with_time(127);
assert_eq!(stats.files_searched, 10);
assert_eq!(stats.candidates_found, 50);
assert_eq!(stats.verified_references, 25);
assert_eq!(stats.search_time_ms, 127);
}
#[test]
fn test_references_options_builder() {
let opts = ReferencesOptions::new()
.with_definition()
.with_kinds(vec![ReferenceKind::Call, ReferenceKind::Import])
.with_scope(SearchScope::File)
.with_limit(100)
.with_context_lines(2);
assert!(opts.include_definition);
assert_eq!(opts.kinds.as_ref().unwrap().len(), 2);
assert_eq!(opts.scope, SearchScope::File);
assert_eq!(opts.limit, Some(100));
assert_eq!(opts.context_lines, 2);
}
#[test]
fn test_report_serialization() {
let report = ReferencesReport {
symbol: "login".to_string(),
definition: Some(Definition::new(
PathBuf::from("src/auth.py"),
42,
5,
DefinitionKind::Function,
)),
references: vec![Reference::new(
PathBuf::from("src/routes.py"),
15,
12,
ReferenceKind::Call,
"login()".to_string(),
)],
total_references: 1,
search_scope: SearchScope::Workspace,
stats: ReferenceStats::new(10, 5, 1).with_time(50),
};
let json = serde_json::to_string_pretty(&report).unwrap();
let parsed: ReferencesReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.symbol, "login");
assert!(parsed.definition.is_some());
assert_eq!(parsed.references.len(), 1);
assert_eq!(parsed.total_references, 1);
assert_eq!(parsed.search_scope, SearchScope::Workspace);
}
#[test]
fn test_no_matches_report() {
let report = ReferencesReport::no_matches(
"nonexistent".to_string(),
SearchScope::Workspace,
ReferenceStats::new(50, 0, 0),
);
assert_eq!(report.symbol, "nonexistent");
assert!(report.definition.is_none());
assert!(report.references.is_empty());
assert_eq!(report.total_references, 0);
assert_eq!(report.stats.files_searched, 50);
}
fn collect_reference_kinds_for(
source: &str,
lang: Language,
needle: &str,
identifier_kinds: &[&str],
) -> Vec<(ReferenceKind, usize)> {
use crate::ast::parser;
let tree = parser::parse(source, lang).expect("parse should succeed");
let src_bytes = source.as_bytes();
let mut results = Vec::new();
fn walk<'a>(
node: tree_sitter::Node<'a>,
source: &'a [u8],
src_str: &'a str,
needle: &str,
identifier_kinds: &[&str],
language: Language,
out: &mut Vec<(ReferenceKind, usize)>,
) {
if identifier_kinds.contains(&node.kind()) {
let start = node.start_byte();
let end = node.end_byte();
if end <= src_str.len() {
let text = &src_str[start..end];
if text == needle {
let kind = classify_reference_kind(&node, source, language);
let line = node.start_position().row + 1;
out.push((kind, line));
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
walk(child, source, src_str, needle, identifier_kinds, language, out);
}
}
}
walk(
tree.root_node(),
src_bytes,
source,
needle,
identifier_kinds,
lang,
&mut results,
);
results
}
fn assert_def_and_calls(results: &[(ReferenceKind, usize)], language_name: &str) {
assert!(
!results.is_empty(),
"{}: no occurrences of `greet` matched by walker",
language_name
);
let def_count = results
.iter()
.filter(|(k, _)| *k == ReferenceKind::Definition)
.count();
let call_count = results
.iter()
.filter(|(k, _)| *k == ReferenceKind::Call)
.count();
let other_count = results
.iter()
.filter(|(k, _)| *k == ReferenceKind::Other)
.count();
assert!(
def_count >= 1,
"{}: expected >=1 Definition, got {} (results={:?})",
language_name,
def_count,
results
);
assert!(
call_count >= 2,
"{}: expected >=2 Calls, got {} (results={:?})",
language_name,
call_count,
results
);
assert_eq!(
other_count, 0,
"{}: expected 0 Other, got {} (results={:?})",
language_name, other_count, results
);
}
#[test]
fn test_java_classifier_emits_call_and_definition() {
let src = r#"
class App {
static String greet(String name) { return "Hello " + name; }
public static void main(String[] args) {
System.out.println(greet("World"));
System.out.println(greet("Alice"));
}
}
"#;
let results = collect_reference_kinds_for(src, Language::Java, "greet", &["identifier"]);
assert_def_and_calls(&results, "Java");
}
#[test]
fn test_c_classifier_emits_call_and_definition() {
let src = r#"
#include <stdio.h>
void greet(const char *name) { printf("Hello %s\n", name); }
int main(void) {
greet("World");
greet("Alice");
return 0;
}
"#;
let results = collect_reference_kinds_for(src, Language::C, "greet", &["identifier"]);
assert_def_and_calls(&results, "C");
}
#[test]
fn test_cpp_classifier_emits_call_and_definition() {
let src = r#"
#include <iostream>
void greet(const std::string &name) { std::cout << "Hello " << name; }
int main() {
greet("World");
greet("Alice");
return 0;
}
"#;
let results = collect_reference_kinds_for(src, Language::Cpp, "greet", &["identifier"]);
assert_def_and_calls(&results, "C++");
}
#[test]
fn test_csharp_classifier_emits_call_and_definition() {
let src = r#"
class App {
static string greet(string name) { return "Hello " + name; }
static void Main() {
System.Console.WriteLine(greet("World"));
System.Console.WriteLine(greet("Alice"));
}
}
"#;
let results =
collect_reference_kinds_for(src, Language::CSharp, "greet", &["identifier"]);
assert_def_and_calls(&results, "C#");
}
#[test]
fn test_kotlin_classifier_emits_call_and_definition() {
let src = r#"
fun greet(name: String): String { return "Hello " + name }
fun main() {
println(greet("World"))
println(greet("Alice"))
}
"#;
let results =
collect_reference_kinds_for(src, Language::Kotlin, "greet", &["identifier"]);
assert_def_and_calls(&results, "Kotlin");
}
#[test]
fn test_scala_classifier_emits_call_and_definition() {
let src = r#"
object App {
def greet(name: String): String = "Hello " + name
def main(args: Array[String]): Unit = {
println(greet("World"))
println(greet("Alice"))
}
}
"#;
let results = collect_reference_kinds_for(
src,
Language::Scala,
"greet",
&["identifier", "type_identifier"],
);
assert_def_and_calls(&results, "Scala");
}
#[test]
fn test_swift_classifier_emits_call_and_definition() {
let src = r#"
func greet(name: String) -> String { return "Hello " + name }
func main() {
print(greet(name: "World"))
print(greet(name: "Alice"))
}
"#;
let results =
collect_reference_kinds_for(src, Language::Swift, "greet", &["simple_identifier"]);
assert_def_and_calls(&results, "Swift");
}
#[test]
fn test_php_classifier_emits_call_and_definition() {
let src = r#"<?php
function greet($name) { return "Hello " . $name; }
echo greet("World");
echo greet("Alice");
"#;
let results = collect_reference_kinds_for(src, Language::Php, "greet", &["name"]);
assert_def_and_calls(&results, "PHP");
}
#[test]
fn test_ruby_classifier_emits_call_and_definition() {
let src = r#"
def greet(name)
"Hello " + name
end
puts greet("World")
puts greet("Alice")
"#;
let results = collect_reference_kinds_for(src, Language::Ruby, "greet", &["identifier"]);
assert_def_and_calls(&results, "Ruby");
}
#[test]
fn test_lua_classifier_emits_call_and_definition() {
let src = r#"
function greet(name)
return "Hello " .. name
end
print(greet("World"))
print(greet("Alice"))
"#;
let results = collect_reference_kinds_for(src, Language::Lua, "greet", &["identifier"]);
assert_def_and_calls(&results, "Lua");
}
#[test]
fn test_luau_classifier_emits_call_and_definition() {
let src = r#"
function greet(name: string): string
return "Hello " .. name
end
print(greet("World"))
print(greet("Alice"))
"#;
let results = collect_reference_kinds_for(src, Language::Luau, "greet", &["identifier"]);
assert_def_and_calls(&results, "Luau");
}
#[test]
fn test_elixir_classifier_emits_call_and_definition() {
let src = r#"
defmodule App do
def greet(name) do
"Hello " <> name
end
end
IO.puts(App.greet("World"))
IO.puts(App.greet("Alice"))
"#;
let results =
collect_reference_kinds_for(src, Language::Elixir, "greet", &["identifier"]);
assert_def_and_calls(&results, "Elixir");
}
#[test]
fn test_ocaml_classifier_emits_call_and_definition() {
let src = r#"
let greet name = "Hello " ^ name
let _ = print_string (greet "World")
let _ = print_string (greet "Alice")
"#;
let results =
collect_reference_kinds_for(src, Language::Ocaml, "greet", &["value_name"]);
assert_def_and_calls(&results, "OCaml");
}
}