use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use clap::Args;
use tree_sitter::Node;
use super::error::{RemainingError, RemainingResult};
use super::types::{DefinitionResult, Location, SymbolInfo, SymbolKind};
use crate::output::OutputWriter;
use tldr_core::ast::parser::PARSER_POOL;
use tldr_core::callgraph::cross_file_types::{ClassDef, FuncDef};
use tldr_core::callgraph::languages::LanguageRegistry;
use tldr_core::Language;
const MAX_IMPORT_DEPTH: usize = 10;
const PYTHON_BUILTINS: &[&str] = &[
"abs",
"aiter",
"all",
"any",
"anext",
"ascii",
"bin",
"bool",
"breakpoint",
"bytearray",
"bytes",
"callable",
"chr",
"classmethod",
"compile",
"complex",
"delattr",
"dict",
"dir",
"divmod",
"enumerate",
"eval",
"exec",
"filter",
"float",
"format",
"frozenset",
"getattr",
"globals",
"hasattr",
"hash",
"help",
"hex",
"id",
"input",
"int",
"isinstance",
"issubclass",
"iter",
"len",
"list",
"locals",
"map",
"max",
"memoryview",
"min",
"next",
"object",
"oct",
"open",
"ord",
"pow",
"print",
"property",
"range",
"repr",
"reversed",
"round",
"set",
"setattr",
"slice",
"sorted",
"staticmethod",
"str",
"sum",
"super",
"tuple",
"type",
"vars",
"zip",
"__import__",
];
pub struct DefinitionCycleDetector {
visited: HashSet<(PathBuf, String)>,
}
impl DefinitionCycleDetector {
pub fn new() -> Self {
Self {
visited: HashSet::new(),
}
}
pub fn visit(&mut self, file: &Path, symbol: &str) -> bool {
let key = (file.to_path_buf(), symbol.to_string());
!self.visited.insert(key)
}
}
impl Default for DefinitionCycleDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Args)]
pub struct DefinitionArgs {
pub file: Option<PathBuf>,
pub line: Option<u32>,
pub column: Option<u32>,
#[arg(long)]
pub symbol: Option<String>,
#[arg(long = "file", name = "target_file")]
pub target_file: Option<PathBuf>,
#[arg(long)]
pub project: Option<PathBuf>,
#[arg(long, default_value_t = true, action = clap::ArgAction::Set)]
pub workspace: bool,
#[arg(long, short = 'O')]
pub output: Option<PathBuf>,
}
impl DefinitionArgs {
pub fn run(
&self,
format: crate::output::OutputFormat,
quiet: bool,
lang: Option<Language>,
) -> Result<()> {
let writer = OutputWriter::new(format, quiet);
let lang_hint = match lang {
Some(l) => format!("{:?}", l).to_lowercase(),
None => "auto".to_string(),
};
let result = if let Some(ref symbol_name) = self.symbol {
let file = self.target_file.as_ref().ok_or_else(|| {
RemainingError::invalid_argument("--file is required with --symbol")
})?;
writer.progress(&format!(
"Finding definition of '{}' in {}...",
symbol_name,
file.display()
));
let auto_project: Option<PathBuf> = if self.project.is_none() && self.workspace {
find_workspace_root(file)
} else {
None
};
let effective_project = self.project.as_deref().or(auto_project.as_deref());
find_definition_by_name(symbol_name, file, effective_project, &lang_hint)?
} else {
let file = self
.file
.as_ref()
.ok_or_else(|| RemainingError::invalid_argument("file argument is required"))?;
let line = self
.line
.ok_or_else(|| RemainingError::invalid_argument("line argument is required"))?;
let column = self
.column
.ok_or_else(|| RemainingError::invalid_argument("column argument is required"))?;
writer.progress(&format!(
"Finding definition at {}:{}:{}...",
file.display(),
line,
column
));
let auto_project: Option<PathBuf> = if self.project.is_none() && self.workspace {
find_workspace_root(file)
} else {
None
};
let effective_project = self.project.as_deref().or(auto_project.as_deref());
match find_definition_by_position(
file,
line,
column,
effective_project,
&lang_hint,
) {
Ok(result) => result,
Err(e) => {
match e {
RemainingError::FileNotFound { .. }
| RemainingError::SymbolNotFound { .. } => return Err(e.into()),
_ => {
let msg = e.to_string();
let detail = if msg.contains("unresolved at") {
msg
} else {
format!(
"definition not found for {}:{}:{}: {}",
file.display(),
line,
column,
msg
)
};
return Err(anyhow::anyhow!(detail));
}
}
}
}
};
let use_text = format == crate::output::OutputFormat::Text;
if let Some(ref output_path) = self.output {
if use_text {
let text = format_definition_text(&result);
fs::write(output_path, text)?;
} else {
let json = serde_json::to_string_pretty(&result)?;
fs::write(output_path, json)?;
}
} else if use_text {
let text = format_definition_text(&result);
writer.write_text(&text)?;
} else {
writer.write(&result)?;
}
Ok(())
}
}
pub fn find_definition_by_name(
symbol: &str,
file: &Path,
project: Option<&Path>,
lang_hint: &str,
) -> RemainingResult<DefinitionResult> {
if !file.exists() {
return Err(RemainingError::file_not_found(file));
}
let language = detect_language(file, lang_hint)?;
if is_builtin(symbol, &language) {
return Ok(DefinitionResult {
symbol: SymbolInfo {
name: symbol.to_string(),
kind: SymbolKind::Function,
location: None,
type_annotation: None,
docstring: None,
is_builtin: true,
module: Some("builtins".to_string()),
},
definition: None,
type_definition: None,
});
}
let source = fs::read_to_string(file).map_err(RemainingError::Io)?;
if let Some(result) = find_symbol_in_file(symbol, file, &source, language)? {
return Ok(result);
}
if let Some(project_root) = project {
let mut detector = DefinitionCycleDetector::new();
if let Some(result) =
resolve_cross_file(symbol, file, project_root, language, &mut detector, 0)?
{
return Ok(result);
}
}
Err(RemainingError::symbol_not_found(symbol, file))
}
pub fn find_definition_by_position(
file: &Path,
line: u32,
column: u32,
project: Option<&Path>,
lang_hint: &str,
) -> RemainingResult<DefinitionResult> {
if !file.exists() {
return Err(RemainingError::file_not_found(file));
}
let language = detect_language(file, lang_hint)?;
let source = fs::read_to_string(file).map_err(RemainingError::Io)?;
let symbol_name = find_symbol_at_position(&source, line, column, language, file)?;
if let Some(result) =
resolve_local_scope(&source, line, column, &symbol_name, language, file)?
{
return Ok(result);
}
match find_definition_by_name(&symbol_name, file, project, lang_hint) {
Ok(result) => Ok(result),
Err(RemainingError::SymbolNotFound { .. }) => {
let trailing = trailing_segment(&symbol_name);
if trailing != symbol_name && !trailing.is_empty() {
if let Ok(result) =
find_definition_by_name(&trailing, file, project, lang_hint)
{
return Ok(result);
}
}
if let Some(result) = resolve_import_scope(&source, &symbol_name, language, file)? {
return Ok(result);
}
Err(RemainingError::invalid_argument(format!(
"unresolved at {}:{}:{} — symbol '{}' not found in scope",
file.display(),
line,
column,
symbol_name
)))
}
Err(e) => Err(e),
}
}
fn trailing_segment(s: &str) -> String {
let mut tail = s;
if let Some(idx) = s.rfind("::") {
tail = &s[idx + 2..];
}
if let Some(idx) = tail.rfind('.') {
tail = &tail[idx + 1..];
}
tail.to_string()
}
fn resolve_local_scope(
source: &str,
line: u32,
column: u32,
symbol: &str,
language: Language,
file: &Path,
) -> RemainingResult<Option<DefinitionResult>> {
if !matches!(
language,
Language::Python
| Language::JavaScript
| Language::TypeScript
| Language::Rust
| Language::Go
| Language::Java
| Language::C
| Language::Cpp
| Language::Ruby
| Language::Kotlin
| Language::Swift
| Language::Scala
| Language::Php
| Language::Lua
| Language::Luau
| Language::Elixir
| Language::Ocaml
| Language::CSharp
) {
return Ok(None);
}
let tree = PARSER_POOL
.parse_with_path(source, language, Some(file))
.map_err(|e| RemainingError::parse_error(file.to_path_buf(), e.to_string()))?;
let root = tree.root_node();
let target_line = line.saturating_sub(1) as usize;
let target_col = column as usize;
let point = tree_sitter::Point::new(target_line, target_col);
let Some(start_node) = root.descendant_for_point_range(point, point) else {
return Ok(None);
};
let mut current = Some(start_node);
while let Some(node) = current {
if is_scope_node(node.kind(), language) {
if let Some(loc) = scan_scope_for_binding(node, source, symbol, language, file) {
return Ok(Some(DefinitionResult {
symbol: SymbolInfo {
name: symbol.to_string(),
kind: loc.0,
location: Some(loc.1.clone()),
type_annotation: None,
docstring: None,
is_builtin: false,
module: None,
},
definition: Some(loc.1),
type_definition: None,
}));
}
}
current = node.parent();
}
Ok(None)
}
fn is_scope_node(kind: &str, language: Language) -> bool {
match language {
Language::Python => matches!(
kind,
"function_definition" | "lambda" | "module"
),
Language::JavaScript | Language::TypeScript => matches!(
kind,
"function_declaration"
| "function"
| "function_expression"
| "arrow_function"
| "method_definition"
| "method_signature"
| "statement_block"
| "program"
),
Language::Rust => matches!(
kind,
"function_item"
| "closure_expression"
| "block"
| "source_file"
),
Language::Go => matches!(
kind,
"function_declaration" | "method_declaration" | "block" | "source_file"
),
Language::Java => matches!(
kind,
"method_declaration"
| "constructor_declaration"
| "lambda_expression"
| "block"
| "program"
),
Language::C => matches!(
kind,
"function_definition" | "compound_statement" | "translation_unit"
),
Language::Cpp => matches!(
kind,
"function_definition"
| "lambda_expression"
| "compound_statement"
| "translation_unit"
),
Language::Ruby => matches!(
kind,
"method"
| "singleton_method"
| "do_block"
| "block"
| "lambda"
| "program"
),
Language::Kotlin => matches!(
kind,
"function_declaration"
| "anonymous_function"
| "lambda_literal"
| "function_body"
| "statements"
| "source_file"
),
Language::Swift => matches!(
kind,
"function_declaration"
| "init_declaration"
| "deinit_declaration"
| "lambda_literal"
| "function_body"
| "statements"
| "source_file"
),
Language::Scala => matches!(
kind,
"function_definition"
| "function_declaration"
| "lambda_expression"
| "block"
| "compilation_unit"
),
Language::Php => matches!(
kind,
"function_definition"
| "method_declaration"
| "anonymous_function_creation_expression"
| "arrow_function"
| "compound_statement"
| "program"
),
Language::Lua | Language::Luau => matches!(
kind,
"function_declaration"
| "function_definition"
| "function_definition_statement"
| "function_statement"
| "local_function"
| "local_function_statement"
| "function"
| "function_body"
| "do_statement"
| "block"
| "chunk"
),
Language::Elixir => matches!(
kind,
"call" | "do_block" | "anonymous_function" | "stab_clause" | "source"
),
Language::Ocaml => matches!(
kind,
"let_binding"
| "value_definition"
| "fun_expression"
| "function_expression"
| "compilation_unit"
),
Language::CSharp => matches!(
kind,
"method_declaration"
| "constructor_declaration"
| "local_function_statement"
| "lambda_expression"
| "anonymous_method_expression"
| "block"
| "compilation_unit"
),
}
}
fn scan_scope_for_binding(
node: Node,
source: &str,
symbol: &str,
language: Language,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let bytes = source.as_bytes();
match language {
Language::Python => scan_python_scope(node, bytes, symbol, file),
Language::JavaScript | Language::TypeScript => scan_jslike_scope(node, bytes, symbol, file),
Language::Rust => scan_rust_scope(node, bytes, symbol, file),
Language::Go => scan_go_scope(node, bytes, symbol, file),
Language::Java => scan_java_scope(node, bytes, symbol, file),
Language::C | Language::Cpp => scan_clike_scope(node, bytes, symbol, file),
Language::Ruby => scan_ruby_scope(node, bytes, symbol, file),
Language::Kotlin => scan_kotlin_scope(node, bytes, symbol, file),
Language::Swift => scan_swift_scope(node, bytes, symbol, file),
Language::Scala => scan_scala_scope(node, bytes, symbol, file),
Language::Php => scan_php_scope(node, bytes, symbol, file),
Language::Lua | Language::Luau => scan_lua_scope(node, bytes, symbol, file),
Language::Elixir => scan_elixir_scope(node, bytes, symbol, file),
Language::Ocaml => scan_ocaml_scope(node, bytes, symbol, file),
Language::CSharp => scan_csharp_scope(node, bytes, symbol, file),
}
}
fn scan_python_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(node.kind(), "function_definition" | "lambda") {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = python_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let body = node
.child_by_field_name("body")
.or_else(|| Some(node));
if let Some(body) = body {
let mut cursor = body.walk();
for child in body.children(&mut cursor) {
if let Some(loc) = python_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
}
None
}
fn python_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
match child.kind() {
"identifier" => {
if child.utf8_text(src).ok()? == symbol {
return Some(make_param_location(child, file));
}
}
"default_parameter"
| "typed_parameter"
| "typed_default_parameter"
| "list_splat_pattern"
| "dictionary_splat_pattern" => {
let name_node = match child.child_by_field_name("name") {
Some(n) => Some(n),
None => {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| n.kind() == "identifier");
found
}
};
if let Some(name) = name_node {
if name.utf8_text(src).ok()? == symbol {
return Some(make_param_location(name, file));
}
}
}
_ => {}
}
}
None
}
fn python_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_definition" | "class_definition" | "lambda" => None,
"assignment" => {
if let Some(left) = node.child_by_field_name("left") {
if let Some(loc) = python_match_target(left, src, symbol, file) {
return Some(loc);
}
}
None
}
"for_statement" => {
if let Some(left) = node.child_by_field_name("left") {
if let Some(loc) = python_match_target(left, src, symbol, file) {
return Some(loc);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = python_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = python_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn python_match_target(
target: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match target.kind() {
"identifier" => {
if target.utf8_text(src).ok()? == symbol {
Some((
SymbolKind::Variable,
Location::with_column(
file.display().to_string(),
target.start_position().row as u32 + 1,
target.start_position().column as u32,
),
))
} else {
None
}
}
"pattern_list" | "tuple_pattern" | "list_pattern" => {
let mut cursor = target.walk();
for child in target.children(&mut cursor) {
if let Some(loc) = python_match_target(child, src, symbol, file) {
return Some(loc);
}
}
None
}
_ => None,
}
}
fn make_param_location(name: Node, file: &Path) -> (SymbolKind, Location) {
(
SymbolKind::Parameter,
Location::with_column(
file.display().to_string(),
name.start_position().row as u32 + 1,
name.start_position().column as u32,
),
)
}
fn scan_jslike_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = jslike_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = jslike_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn jslike_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
match child.kind() {
"identifier" | "shorthand_property_identifier_pattern" => {
if child.utf8_text(src).ok()? == symbol {
return Some(make_param_location(child, file));
}
}
"required_parameter" | "optional_parameter" | "rest_pattern"
| "assignment_pattern" => {
let pat_node = match child.child_by_field_name("pattern") {
Some(n) => Some(n),
None => {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| n.kind() == "identifier");
found
}
};
if let Some(pat) = pat_node {
if pat.kind() == "identifier" && pat.utf8_text(src).ok()? == symbol {
return Some(make_param_location(pat, file));
}
}
}
_ => {}
}
}
None
}
fn jslike_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_declaration" | "function" | "function_expression" | "arrow_function"
| "method_definition" | "method_signature" | "class_declaration" => None,
"lexical_declaration" | "variable_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declarator" {
if let Some(name) = child.child_by_field_name("name") {
if name.kind() == "identifier"
&& name.utf8_text(src).ok()? == symbol
{
return Some((
SymbolKind::Variable,
Location::with_column(
file.display().to_string(),
name.start_position().row as u32 + 1,
name.start_position().column as u32,
),
));
}
}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = jslike_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_rust_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(node.kind(), "function_item" | "closure_expression") {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = rust_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = rust_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn rust_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "parameter" {
if let Some(pat) = child.child_by_field_name("pattern") {
if pat.kind() == "identifier" && pat.utf8_text(src).ok()? == symbol {
return Some(make_param_location(pat, file));
}
}
}
}
None
}
fn rust_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_item" | "closure_expression" | "impl_item" => None,
"let_declaration" => {
if let Some(pat) = node.child_by_field_name("pattern") {
if pat.kind() == "identifier" && pat.utf8_text(src).ok()? == symbol {
return Some((
SymbolKind::Variable,
Location::with_column(
file.display().to_string(),
pat.start_position().row as u32 + 1,
pat.start_position().column as u32,
),
));
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = rust_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_go_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(node.kind(), "function_declaration" | "method_declaration") {
if let Some(params) = node.child_by_field_name("parameters") {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "parameter_declaration" {
let mut c = child.walk();
for n in child.children(&mut c) {
if n.kind() == "identifier" && n.utf8_text(src).ok()? == symbol {
return Some(make_param_location(n, file));
}
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = go_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn go_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_declaration" | "method_declaration" | "func_literal" => None,
"short_var_declaration" | "var_declaration" => {
if let Some(left) = node.child_by_field_name("left") {
let mut c = left.walk();
for n in left.children(&mut c) {
if n.kind() == "identifier" && n.utf8_text(src).ok()? == symbol {
return Some((
SymbolKind::Variable,
Location::with_column(
file.display().to_string(),
n.start_position().row as u32 + 1,
n.start_position().column as u32,
),
));
}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = go_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn make_var_location(name: Node, file: &Path) -> (SymbolKind, Location) {
(
SymbolKind::Variable,
Location::with_column(
file.display().to_string(),
name.start_position().row as u32 + 1,
name.start_position().column as u32,
),
)
}
fn name_node_matches(n: Node, src: &[u8], symbol: &str) -> bool {
if let Ok(t) = n.utf8_text(src) {
t == symbol
} else {
false
}
}
fn scan_java_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"method_declaration" | "constructor_declaration" | "lambda_expression"
) {
if let Some(params) = node
.child_by_field_name("parameters")
.or_else(|| node.child_by_field_name("formal_parameters"))
{
if let Some(loc) = java_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = java_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn java_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if matches!(child.kind(), "formal_parameter" | "spread_parameter") {
if let Some(name) = child.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
}
} else if child.kind() == "identifier" && name_node_matches(child, src, symbol) {
return Some(make_param_location(child, file));
} else if child.kind() == "inferred_parameters" {
let mut c = child.walk();
for n in child.children(&mut c) {
if n.kind() == "identifier" && name_node_matches(n, src, symbol) {
return Some(make_param_location(n, file));
}
}
}
}
None
}
fn java_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"method_declaration"
| "constructor_declaration"
| "class_declaration"
| "interface_declaration"
| "lambda_expression" => None,
"local_variable_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declarator" {
if let Some(name) = child.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_var_location(name, file));
}
}
}
}
None
}
"enhanced_for_statement" => {
if let Some(name) = node.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_var_location(name, file));
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = java_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = java_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_clike_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if node.kind() == "function_definition" {
if let Some(decl) = node.child_by_field_name("declarator") {
if let Some(loc) = clike_scan_declarator_params(decl, src, symbol, file) {
return Some(loc);
}
}
}
if node.kind() == "lambda_expression" {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "abstract_function_declarator" || child.kind() == "parameter_list" {
if let Some(loc) = clike_scan_param_list(child, src, symbol, file) {
return Some(loc);
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = clike_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn clike_scan_declarator_params(
declarator: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = declarator.walk();
for child in declarator.children(&mut cursor) {
if child.kind() == "parameter_list" {
if let Some(loc) = clike_scan_param_list(child, src, symbol, file) {
return Some(loc);
}
} else if matches!(
child.kind(),
"function_declarator" | "parenthesized_declarator"
) {
if let Some(loc) = clike_scan_declarator_params(child, src, symbol, file) {
return Some(loc);
}
}
}
None
}
fn clike_scan_param_list(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "parameter_declaration" {
if let Some(name) = clike_find_param_identifier(child, src, symbol) {
return Some(make_param_location(name, file));
}
}
}
None
}
fn clike_find_param_identifier<'a>(
node: Node<'a>,
src: &[u8],
symbol: &str,
) -> Option<Node<'a>> {
if matches!(node.kind(), "identifier" | "field_identifier") && name_node_matches(node, src, symbol)
{
return Some(node);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(n) = clike_find_param_identifier(child, src, symbol) {
return Some(n);
}
}
None
}
fn clike_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_definition" | "lambda_expression" => None,
"declaration" | "init_declarator" => {
if let Some(name) = clike_extract_decl_name(node, src, symbol) {
return Some(make_var_location(name, file));
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = clike_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = clike_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn clike_extract_decl_name<'a>(node: Node<'a>, src: &[u8], symbol: &str) -> Option<Node<'a>> {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"init_declarator" => {
if let Some(decl) = child.child_by_field_name("declarator") {
if let Some(n) = clike_extract_decl_name(decl, src, symbol) {
return Some(n);
}
}
}
"identifier" | "field_identifier" => {
if name_node_matches(child, src, symbol) {
return Some(child);
}
}
"pointer_declarator" | "array_declarator" | "parenthesized_declarator"
| "reference_declarator" => {
if let Some(n) = clike_extract_decl_name(child, src, symbol) {
return Some(n);
}
if let Some(inner) = child.child_by_field_name("declarator") {
if let Some(n) = clike_extract_decl_name(inner, src, symbol) {
return Some(n);
}
}
}
_ => {}
}
}
None
}
fn scan_ruby_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(node.kind(), "method" | "singleton_method" | "lambda" | "do_block" | "block") {
if let Some(params) = node
.child_by_field_name("parameters")
.or_else(|| node.child_by_field_name("method_parameters"))
.or_else(|| node.child_by_field_name("block_parameters"))
{
if let Some(loc) = ruby_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = ruby_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn ruby_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
match child.kind() {
"identifier" => {
if name_node_matches(child, src, symbol) {
return Some(make_param_location(child, file));
}
}
"optional_parameter"
| "keyword_parameter"
| "splat_parameter"
| "hash_splat_parameter"
| "block_parameter" => {
if let Some(name) = child.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
} else {
let mut c = child.walk();
for n in child.children(&mut c) {
if n.kind() == "identifier" && name_node_matches(n, src, symbol) {
return Some(make_param_location(n, file));
}
}
}
}
_ => {}
}
}
None
}
fn ruby_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"method" | "singleton_method" | "class" | "module" | "lambda" => None,
"assignment" => {
if let Some(left) = node.child_by_field_name("left") {
if left.kind() == "identifier" && name_node_matches(left, src, symbol) {
return Some(make_var_location(left, file));
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = ruby_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_kotlin_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"function_declaration" | "anonymous_function" | "lambda_literal"
) {
let params = node
.child_by_field_name("parameters")
.or_else(|| {
let mut c = node.walk();
let found = node.children(&mut c).find(|n| {
matches!(
n.kind(),
"function_value_parameters" | "lambda_parameters"
)
});
found
});
if let Some(params) = params {
if let Some(loc) = kotlin_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = kotlin_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn kotlin_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if matches!(
child.kind(),
"parameter" | "function_value_parameter" | "value_parameter"
) {
let name = child
.child_by_field_name("name")
.or_else(|| {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| matches!(n.kind(), "identifier" | "simple_identifier"));
found
});
if let Some(name) = name {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
}
} else if matches!(child.kind(), "identifier" | "simple_identifier")
&& name_node_matches(child, src, symbol)
{
return Some(make_param_location(child, file));
}
}
None
}
fn kotlin_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_declaration"
| "anonymous_function"
| "lambda_literal"
| "class_declaration"
| "object_declaration" => None,
"property_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declaration" {
let mut c = child.walk();
for n in child.children(&mut c) {
if matches!(n.kind(), "identifier" | "simple_identifier")
&& name_node_matches(n, src, symbol)
{
return Some(make_var_location(n, file));
}
}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = kotlin_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_swift_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"function_declaration" | "init_declaration" | "lambda_literal"
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if matches!(
child.kind(),
"parameter" | "value_parameter" | "lambda_function_type_parameters"
) {
let name = child
.child_by_field_name("name")
.or_else(|| {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| matches!(n.kind(), "identifier" | "simple_identifier"));
found
});
if let Some(name) = name {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = swift_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn swift_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_declaration" | "init_declaration" | "class_declaration"
| "struct_declaration" | "enum_declaration" | "lambda_literal" => None,
"property_declaration" => {
let name = node.child_by_field_name("name").or_else(|| {
let mut c = node.walk();
let found = node.children(&mut c)
.find(|n| matches!(n.kind(), "identifier" | "simple_identifier" | "pattern"));
found
});
if let Some(name) = name {
if name.kind() == "pattern" {
let mut c = name.walk();
for n in name.children(&mut c) {
if matches!(n.kind(), "identifier" | "simple_identifier")
&& name_node_matches(n, src, symbol)
{
return Some(make_var_location(n, file));
}
}
} else if name_node_matches(name, src, symbol) {
return Some(make_var_location(name, file));
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = swift_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_scala_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"function_definition" | "function_declaration" | "lambda_expression"
) {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = scala_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = scala_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn scala_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if matches!(child.kind(), "parameter" | "class_parameter" | "binding") {
let name = child.child_by_field_name("name").or_else(|| {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| n.kind() == "identifier");
found
});
if let Some(name) = name {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
}
} else if matches!(child.kind(), "identifier") && name_node_matches(child, src, symbol) {
return Some(make_param_location(child, file));
} else if matches!(child.kind(), "parameters" | "bindings") {
if let Some(loc) = scala_scan_params(child, src, symbol, file) {
return Some(loc);
}
}
}
None
}
fn scala_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_definition" | "function_declaration" | "class_definition"
| "object_definition" | "trait_definition" | "lambda_expression" => None,
"val_definition" | "var_definition" | "val_declaration" | "var_declaration" => {
let name = node.child_by_field_name("pattern").or_else(|| {
let mut c = node.walk();
let found = node.children(&mut c).find(|n| n.kind() == "identifier");
found
});
if let Some(name) = name {
if name.kind() == "identifier" && name_node_matches(name, src, symbol) {
return Some(make_var_location(name, file));
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = scala_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_php_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"function_definition"
| "method_declaration"
| "anonymous_function_creation_expression"
| "arrow_function"
) {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = php_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = php_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn php_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let target_with_dollar = if symbol.starts_with('$') {
symbol.to_string()
} else {
format!("${}", symbol)
};
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if matches!(
child.kind(),
"simple_parameter"
| "variadic_parameter"
| "property_promotion_parameter"
) {
if let Some(name) = child
.child_by_field_name("name")
.or_else(|| {
let mut c = child.walk();
let found = child
.children(&mut c)
.find(|n| n.kind() == "variable_name");
found
})
{
if let Ok(t) = name.utf8_text(src) {
if t == target_with_dollar || t.trim_start_matches('$') == symbol.trim_start_matches('$') {
return Some(make_param_location(name, file));
}
}
}
}
}
None
}
fn php_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_definition"
| "method_declaration"
| "anonymous_function_creation_expression"
| "arrow_function"
| "class_declaration" => None,
"assignment_expression" => {
if let Some(left) = node.child_by_field_name("left") {
if left.kind() == "variable_name" {
if let Ok(t) = left.utf8_text(src) {
let bare = t.trim_start_matches('$');
if bare == symbol.trim_start_matches('$') {
return Some(make_var_location(left, file));
}
}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = php_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_lua_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"function_declaration"
| "function_definition"
| "function_definition_statement"
| "function_statement"
| "local_function"
| "local_function_statement"
| "function"
) {
let params = node.child_by_field_name("parameters").or_else(|| {
let mut c = node.walk();
let found = node.children(&mut c).find(|n| n.kind() == "parameters");
found
});
if let Some(params) = params {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
match child.kind() {
"identifier" | "name" => {
if name_node_matches(child, src, symbol) {
return Some(make_param_location(child, file));
}
}
"parameter" => {
let mut c = child.walk();
for n in child.children(&mut c) {
if matches!(n.kind(), "identifier" | "name")
&& name_node_matches(n, src, symbol)
{
return Some(make_param_location(n, file));
}
}
}
_ => {}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = lua_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn lua_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_declaration"
| "function_definition"
| "function_definition_statement"
| "function_statement"
| "local_function"
| "local_function_statement"
| "function" => None,
"local_variable_declaration"
| "local_declaration"
| "local_variable_declaration_statement"
| "variable_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"identifier" | "name" => {
if name_node_matches(child, src, symbol) {
return Some(make_var_location(child, file));
}
}
"variable_list" | "name_list" | "attnamelist" => {
let mut c = child.walk();
for n in child.children(&mut c) {
if matches!(n.kind(), "identifier" | "name")
&& name_node_matches(n, src, symbol)
{
return Some(make_var_location(n, file));
}
}
}
_ => {}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = lua_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_elixir_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if node.kind() == "call" {
if let Some(name) = elixir_call_head_name(node, src) {
if matches!(
name.as_str(),
"def" | "defp" | "defmacro" | "defmacrop"
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "arguments" {
if let Some(loc) = elixir_scan_def_args(child, src, symbol, file) {
return Some(loc);
}
}
}
}
}
}
if node.kind() == "stab_clause" {
if let Some(left) = node.child_by_field_name("left") {
if let Some(loc) = elixir_scan_stab_left(left, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = elixir_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn elixir_call_head_name(node: Node, src: &[u8]) -> Option<String> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if child.kind() == "identifier" {
return child.utf8_text(src).ok().map(|s| s.to_string());
}
}
}
None
}
fn elixir_scan_def_args(
args: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = args.walk();
for child in args.children(&mut cursor) {
let head_call = match child.kind() {
"call" => Some(child),
"binary_operator" => {
let mut found: Option<Node> = None;
let mut bc = child.walk();
for bch in child.children(&mut bc) {
if bch.kind() == "call" {
found = Some(bch);
break;
}
}
found
}
_ => None,
};
if let Some(head) = head_call {
let mut cc = head.walk();
for inner in head.children(&mut cc) {
if inner.kind() == "arguments" {
let mut c = inner.walk();
for arg in inner.children(&mut c) {
if let Some(loc) = elixir_match_param(arg, src, symbol, file) {
return Some(loc);
}
}
}
}
return None;
} else if child.kind() == "identifier" && name_node_matches(child, src, symbol) {
return None;
}
}
None
}
fn elixir_scan_stab_left(
left: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = left.walk();
for child in left.children(&mut cursor) {
if let Some(loc) = elixir_match_param(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn elixir_match_param(
arg: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match arg.kind() {
"identifier" => {
if name_node_matches(arg, src, symbol) {
Some(make_param_location(arg, file))
} else {
None
}
}
"binary_operator" => {
if let Some(left) = arg.child_by_field_name("left") {
if left.kind() == "identifier" && name_node_matches(left, src, symbol) {
return Some(make_param_location(left, file));
}
}
None
}
_ => None,
}
}
fn elixir_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if node.kind() == "call" {
if let Some(name) = elixir_call_head_name(node, src) {
if matches!(
name.as_str(),
"def" | "defp" | "defmacro" | "defmacrop" | "defmodule"
) {
return None;
}
}
}
if node.kind() == "binary_operator" {
if let Some(op) = node.child_by_field_name("operator") {
if let Ok(o) = op.utf8_text(src) {
if o == "=" {
if let Some(left) = node.child_by_field_name("left") {
if left.kind() == "identifier" && name_node_matches(left, src, symbol) {
return Some(make_var_location(left, file));
}
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = elixir_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn scan_ocaml_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if node.kind() == "value_definition" {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "let_binding" {
if let Some(loc) = ocaml_scan_let_binding_params(child, src, symbol, file) {
return Some(loc);
}
}
}
}
if node.kind() == "let_binding" {
if let Some(loc) = ocaml_scan_let_binding_params(node, src, symbol, file) {
return Some(loc);
}
}
if matches!(node.kind(), "fun_expression" | "function_expression") {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if matches!(child.kind(), "parameter" | "value_pattern") {
if let Some(name) = ocaml_find_first_ident(child, src, symbol) {
return Some(make_param_location(name, file));
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = ocaml_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn ocaml_scan_let_binding_params(
binding: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = binding.walk();
for child in binding.children(&mut cursor) {
if child.kind() == "parameter" {
if let Some(name) = ocaml_find_first_ident(child, src, symbol) {
return Some(make_param_location(name, file));
}
}
}
None
}
fn ocaml_find_first_ident<'a>(node: Node<'a>, src: &[u8], symbol: &str) -> Option<Node<'a>> {
if matches!(
node.kind(),
"value_name" | "value_pattern" | "lowercase_identifier" | "identifier"
) && name_node_matches(node, src, symbol)
{
return Some(node);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(n) = ocaml_find_first_ident(child, src, symbol) {
return Some(n);
}
}
None
}
fn ocaml_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"fun_expression" | "function_expression" => None,
"let_binding" | "value_definition" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if matches!(child.kind(), "value_name" | "value_pattern") {
if let Some(name) = ocaml_find_first_ident(child, src, symbol) {
return Some(make_var_location(name, file));
}
break;
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = ocaml_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn scan_csharp_scope(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
if matches!(
node.kind(),
"method_declaration"
| "constructor_declaration"
| "local_function_statement"
| "lambda_expression"
| "anonymous_method_expression"
) {
if let Some(params) = node.child_by_field_name("parameters") {
if let Some(loc) = csharp_scan_params(params, src, symbol, file) {
return Some(loc);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = csharp_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
fn csharp_scan_params(
params: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if matches!(child.kind(), "parameter") {
if let Some(name) = child.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_param_location(name, file));
}
}
} else if child.kind() == "identifier" && name_node_matches(child, src, symbol) {
return Some(make_param_location(child, file));
} else if child.kind() == "implicit_parameter_list" {
let mut c = child.walk();
for n in child.children(&mut c) {
if n.kind() == "identifier" && name_node_matches(n, src, symbol) {
return Some(make_param_location(n, file));
}
}
}
}
None
}
fn csharp_walk_for_binding(
node: Node,
src: &[u8],
symbol: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"method_declaration"
| "constructor_declaration"
| "local_function_statement"
| "class_declaration"
| "struct_declaration"
| "interface_declaration"
| "lambda_expression"
| "anonymous_method_expression" => None,
"variable_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declarator" {
if let Some(name) = child.child_by_field_name("name") {
if name_node_matches(name, src, symbol) {
return Some(make_var_location(name, file));
}
} else {
let mut c = child.walk();
for n in child.children(&mut c) {
if n.kind() == "identifier" && name_node_matches(n, src, symbol) {
return Some(make_var_location(n, file));
}
}
}
}
}
None
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(loc) = csharp_walk_for_binding(child, src, symbol, file) {
return Some(loc);
}
}
None
}
}
}
fn resolve_import_scope(
source: &str,
symbol: &str,
language: Language,
file: &Path,
) -> RemainingResult<Option<DefinitionResult>> {
let line_idx = match language {
Language::Python => python_import_line(source, symbol),
Language::JavaScript | Language::TypeScript => jslike_import_line(source, symbol),
Language::Rust => rust_use_line(source, symbol),
Language::Java => java_import_line(source, symbol),
Language::Kotlin | Language::Scala => jvm_import_line(source, symbol),
Language::Swift => swift_import_line(source, symbol),
Language::Php => php_use_line(source, symbol),
Language::CSharp => csharp_using_line(source, symbol),
Language::Lua | Language::Luau => lua_require_line(source, symbol),
Language::Elixir => elixir_alias_line(source, symbol),
Language::Ocaml => ocaml_open_line(source, symbol),
Language::C | Language::Cpp | Language::Ruby | Language::Go => None,
};
let Some((line_no, col)) = line_idx else {
return Ok(None);
};
let location = Location::with_column(file.display().to_string(), line_no, col);
Ok(Some(DefinitionResult {
symbol: SymbolInfo {
name: symbol.to_string(),
kind: SymbolKind::Module,
location: Some(location.clone()),
type_annotation: None,
docstring: None,
is_builtin: false,
module: None,
},
definition: Some(location),
type_definition: None,
}))
}
fn python_import_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
if let Some(rest) = line.strip_prefix("import ") {
for piece in rest.split(',') {
let piece = piece.trim();
if piece.is_empty() {
continue;
}
let bound = if let Some((_, alias)) = piece.split_once(" as ") {
alias.trim()
} else {
piece.split('.').next().unwrap_or(piece).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
} else if line.starts_with("from ") {
if let Some(import_idx) = line.find(" import ") {
let names_str = &line[import_idx + 8..];
for piece in names_str.split(',') {
let piece = piece.trim().trim_start_matches('(').trim_end_matches(')');
if piece.is_empty() || piece == "*" {
continue;
}
let bound = if let Some((_, alias)) = piece.split_once(" as ") {
alias.trim()
} else {
piece.trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
}
}
}
None
}
fn jslike_import_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let body = match line.strip_prefix("import ") {
Some(b) => b,
None => continue,
};
let body = body
.split(" from ")
.next()
.unwrap_or(body)
.trim()
.trim_end_matches(';')
.trim();
if body.starts_with('"') || body.starts_with('\'') {
continue;
}
if let Some(rest) = body.strip_prefix("* as ") {
let bound = rest.trim().trim_end_matches(',').trim();
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
continue;
}
let mut remainder = body;
let mut pieces: Vec<&str> = Vec::new();
if !remainder.starts_with('{') {
if let Some(idx_brace) = remainder.find('{') {
let (default_part, rest) = remainder.split_at(idx_brace);
let default_name = default_part.trim().trim_end_matches(',').trim();
if !default_name.is_empty() {
pieces.push(default_name);
}
remainder = rest;
} else {
let default_name = remainder.trim();
if !default_name.is_empty() {
pieces.push(default_name);
}
remainder = "";
}
}
if remainder.starts_with('{') {
let inside = remainder
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
for p in inside.split(',') {
let p = p.trim();
if !p.is_empty() {
pieces.push(p);
}
}
}
for piece in pieces {
let bound = if let Some((_, alias)) = piece.split_once(" as ") {
alias.trim()
} else {
piece.trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
}
None
}
fn rust_use_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("use ") else {
continue;
};
let rest = rest.trim_end_matches(';').trim();
if rest.contains('{') {
continue;
}
let bound = if let Some((_, alias)) = rest.split_once(" as ") {
alias.trim()
} else {
rest.rsplit("::").next().unwrap_or(rest).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn last_dotted_segment(path: &str) -> &str {
path.rsplit('.').next().unwrap_or(path).trim()
}
fn java_import_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("import ") else {
continue;
};
let rest = rest.trim_end_matches(';').trim();
let rest = rest.strip_prefix("static ").map(|r| r.trim()).unwrap_or(rest);
if rest.ends_with('*') {
continue;
}
if last_dotted_segment(rest) == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn jvm_import_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("import ") else {
continue;
};
let rest = rest.trim_end_matches(';').trim();
if let Some(brace_idx) = rest.find('{') {
let inside = rest[brace_idx..]
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
for sel in inside.split(',') {
let sel = sel.trim();
if sel.is_empty() {
continue;
}
let bound = if let Some((_, alias)) = sel.split_once("=>") {
let a = alias.trim();
if a == "_" {
continue;
}
a
} else {
sel
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
continue;
}
if rest.ends_with('*') || rest.ends_with('_') {
continue;
}
let bound = if let Some((_, alias)) = rest.split_once(" as ") {
alias.trim()
} else {
last_dotted_segment(rest)
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn swift_import_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("import ") else {
continue;
};
let rest = rest.trim();
let rest = ["class ", "struct ", "enum ", "protocol ", "typealias ", "var ", "func "]
.iter()
.find_map(|prefix| rest.strip_prefix(prefix))
.unwrap_or(rest)
.trim();
let bound = last_dotted_segment(rest);
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn php_use_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("use ") else {
continue;
};
let rest = rest.trim_end_matches(';').trim();
let rest = ["function ", "const "]
.iter()
.find_map(|p| rest.strip_prefix(p))
.unwrap_or(rest)
.trim();
if let Some(brace_idx) = rest.find('{') {
let inside = rest[brace_idx..]
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
for sel in inside.split(',') {
let sel = sel.trim();
if sel.is_empty() {
continue;
}
let bound = if let Some((_, alias)) = sel.split_once(" as ") {
alias.trim()
} else {
sel.rsplit('\\').next().unwrap_or(sel).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
continue;
}
let bound = if let Some((_, alias)) = rest.split_once(" as ") {
alias.trim()
} else {
rest.rsplit('\\').next().unwrap_or(rest).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn csharp_using_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("using ") else {
continue;
};
let rest = rest.trim_end_matches(';').trim();
if rest.starts_with("static ") {
continue;
}
let bound = if let Some((alias, _)) = rest.split_once('=') {
alias.trim()
} else {
rest.split('.').next().unwrap_or(rest).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn lua_require_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
let Some(rest) = line.strip_prefix("local ") else {
continue;
};
let Some(eq_idx) = rest.find('=') else {
continue;
};
let lhs = rest[..eq_idx].trim();
let rhs = rest[eq_idx + 1..].trim();
if !rhs.starts_with("require") {
continue;
}
let bound = lhs.split(':').next().unwrap_or(lhs).trim();
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
None
}
fn elixir_alias_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
for kw in &["alias ", "import ", "use ", "require "] {
if let Some(rest) = line.strip_prefix(kw) {
let rest = rest.trim().trim_end_matches(',');
let bound = if let Some(as_idx) = rest.find(", as:") {
let after = rest[as_idx + 5..].trim();
after.trim_end_matches(',').trim()
} else {
let path = rest.split(',').next().unwrap_or(rest).trim();
if let Some(brace_idx) = path.find('{') {
let prefix = &path[..brace_idx];
let inside = path[brace_idx..]
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
for sel in inside.split(',') {
let sel = sel.trim();
if !sel.is_empty() && sel == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
let _ = prefix;
return None;
}
path.rsplit('.').next().unwrap_or(path).trim()
};
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
}
}
None
}
fn ocaml_open_line(source: &str, symbol: &str) -> Option<(u32, u32)> {
for (idx, raw) in source.lines().enumerate() {
let line = raw.trim_start();
let leading = raw.len() - line.len();
if let Some(rest) = line.strip_prefix("open ") {
let rest = rest.trim_end_matches(";;").trim_end_matches(';').trim();
let bound = rest.rsplit('.').next().unwrap_or(rest).trim();
if bound == symbol {
return Some((idx as u32 + 1, leading as u32));
}
} else if let Some(rest) = line.strip_prefix("module ") {
if let Some((alias, _)) = rest.split_once('=') {
let alias = alias.trim();
if alias == symbol {
return Some((idx as u32 + 1, leading as u32));
}
}
}
}
None
}
const MAX_SYMBOL_LEN: usize = 256;
fn find_symbol_at_position(
source: &str,
line: u32,
column: u32,
language: Language,
file: &Path,
) -> RemainingResult<String> {
let line_count = source.lines().count();
let target_line_0 = line.saturating_sub(1) as usize;
if line as usize == 0 || target_line_0 >= line_count {
return Err(RemainingError::invalid_argument(format!(
"line {} out of range (file has {} lines)",
line, line_count
)));
}
let line_text = source.lines().nth(target_line_0).unwrap_or("");
if (column as usize) > line_text.len() {
return Err(RemainingError::invalid_argument(format!(
"column {} out of range on line {} (line has {} bytes)",
column,
line,
line_text.len()
)));
}
let mut effective_column = column as usize;
for _ in 0..6 {
let candidate =
extract_identifier_at_column(line_text, effective_column.min(line_text.len()));
if !candidate.is_empty() && is_language_keyword(&candidate, language) {
let bytes = line_text.as_bytes();
let is_ident =
|b: u8| b.is_ascii_alphanumeric() || b == b'_';
let mut i = effective_column.min(bytes.len());
while i < bytes.len() && is_ident(bytes[i]) {
i += 1;
}
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
if i < bytes.len() && bytes[i] == b'(' {
let mut depth = 0i32;
while i < bytes.len() {
match bytes[i] {
b'(' => depth += 1,
b')' => {
depth -= 1;
i += 1;
if depth == 0 {
break;
}
continue;
}
_ => {}
}
i += 1;
}
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
} else {
while i < bytes.len() && !is_ident(bytes[i]) {
i += 1;
}
}
if i < bytes.len() {
effective_column = i;
continue;
}
}
break;
}
let tree = PARSER_POOL
.parse_with_path(source, language, Some(file))
.map_err(|e| RemainingError::parse_error(file.to_path_buf(), e.to_string()))?;
let target_line = target_line_0;
let target_col = effective_column;
let root = tree.root_node();
let point = tree_sitter::Point::new(target_line, target_col);
let node = root
.descendant_for_point_range(point, point)
.ok_or_else(|| {
RemainingError::invalid_argument(format!(
"No symbol found at line {}, column {}",
line, column
))
})?;
let text = node.utf8_text(source.as_bytes()).map_err(|_| {
RemainingError::parse_error(file.to_path_buf(), "Invalid UTF-8".to_string())
})?;
if is_identifier_kind(node.kind()) {
if let Some(parent) = node.parent() {
let pkind = parent.kind();
if matches!(
pkind,
"dot_index_expression"
| "member_expression"
| "field_expression"
| "selector_expression"
| "qualified_identifier"
| "scoped_identifier"
| "field_access"
| "name_qualified"
| "field_identifier"
) {
if let Ok(full) = parent.utf8_text(source.as_bytes()) {
if !full.is_empty() && full.contains(text) {
return Ok(clamp_symbol(full));
}
}
}
}
return Ok(clamp_symbol(text));
}
let mut current = node.parent();
while let Some(n) = current {
if is_identifier_kind(n.kind()) {
let text = n.utf8_text(source.as_bytes()).map_err(|_| {
RemainingError::parse_error(file.to_path_buf(), "Invalid UTF-8".to_string())
})?;
return Ok(clamp_symbol(text));
}
current = n.parent();
}
Ok(extract_identifier_at_column(line_text, target_col))
}
fn clamp_symbol(s: &str) -> String {
if s.len() <= MAX_SYMBOL_LEN {
return s.to_string();
}
let mut end = MAX_SYMBOL_LEN;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
format!("{}…", &s[..end])
}
fn extract_identifier_at_column(line: &str, col: usize) -> String {
let bytes = line.as_bytes();
if bytes.is_empty() {
return String::new();
}
let is_ident = |b: u8| b.is_ascii_alphanumeric() || b == b'_';
let mut start = col.min(bytes.len().saturating_sub(1));
if !is_ident(bytes[start]) && start > 0 && is_ident(bytes[start - 1]) {
start -= 1;
}
if !is_ident(bytes[start]) {
return String::new();
}
while start > 0 && is_ident(bytes[start - 1]) {
start -= 1;
}
let mut end = start;
while end < bytes.len() && is_ident(bytes[end]) {
end += 1;
}
let slice = &line[start..end];
clamp_symbol(slice)
}
fn is_language_keyword(word: &str, language: Language) -> bool {
const SHARED: &[&str] = &[
"fn", "func", "function", "def", "defp", "defmodule", "defmacro",
"defmacrop", "defstruct", "let", "var", "const", "class", "struct",
"trait", "interface", "module", "namespace", "package", "import",
"from", "use", "using", "export", "pub", "public", "private",
"protected", "static", "final", "abstract", "override", "async",
"await", "return", "if", "else", "elif", "while", "for", "do",
"match", "switch", "case", "break", "continue", "type", "typedef",
"enum", "implements", "extends", "self", "this", "super", "void",
"new", "delete", "object", "trait",
];
if SHARED.contains(&word) {
return true;
}
match language {
Language::Rust => matches!(
word,
"fn" | "mod"
| "impl"
| "trait"
| "where"
| "ref"
| "mut"
| "dyn"
| "as"
| "in"
| "loop"
| "move"
| "unsafe"
| "extern"
| "crate"
| "Self"
),
Language::Ocaml => matches!(
word,
"let" | "rec"
| "and"
| "in"
| "fun"
| "function"
| "module"
| "open"
| "type"
| "of"
| "match"
| "with"
| "begin"
| "end"
| "val"
),
Language::Java | Language::CSharp | Language::Kotlin => {
matches!(word, "synchronized" | "throws" | "throw" | "try" | "catch" | "finally")
}
_ => false,
}
}
fn is_identifier_kind(kind: &str) -> bool {
kind == "identifier"
|| kind == "property_identifier"
|| kind == "field_identifier"
|| kind == "type_identifier"
|| kind == "shorthand_property_identifier"
|| kind == "constant"
|| kind == "name"
|| kind.ends_with("_identifier")
}
fn find_symbol_in_file(
symbol: &str,
file: &Path,
source: &str,
language: Language,
) -> RemainingResult<Option<DefinitionResult>> {
if language == Language::Python {
return find_symbol_in_file_python(symbol, file, source);
}
find_symbol_in_file_generic(symbol, file, source, language)
}
fn find_symbol_in_file_python(
symbol: &str,
file: &Path,
source: &str,
) -> RemainingResult<Option<DefinitionResult>> {
let tree = PARSER_POOL
.parse_with_path(source, Language::Python, Some(file))
.map_err(|e| RemainingError::parse_error(file.to_path_buf(), e.to_string()))?;
let root = tree.root_node();
if let Some((kind, location)) = find_definition_recursive(root, source, symbol, file) {
return Ok(Some(DefinitionResult {
symbol: SymbolInfo {
name: symbol.to_string(),
kind,
location: Some(location.clone()),
type_annotation: None,
docstring: None,
is_builtin: false,
module: None,
},
definition: Some(location),
type_definition: None,
}));
}
Ok(None)
}
fn find_symbol_in_file_generic(
symbol: &str,
file: &Path,
source: &str,
language: Language,
) -> RemainingResult<Option<DefinitionResult>> {
let tree = PARSER_POOL
.parse_with_path(source, language, Some(file))
.map_err(|e| RemainingError::parse_error(file.to_path_buf(), e.to_string()))?;
let registry = LanguageRegistry::with_defaults();
let handler = registry
.get(language.as_str())
.ok_or_else(|| RemainingError::unsupported_language(format!("{:?}", language)))?;
let (funcs, classes) = handler
.extract_definitions(source, file, &tree)
.map_err(|e| RemainingError::parse_error(file.to_path_buf(), e.to_string()))?;
if let Some((kind, location)) = match_definition(symbol, &funcs, &classes, file) {
return Ok(Some(DefinitionResult {
symbol: SymbolInfo {
name: symbol.to_string(),
kind,
location: Some(location.clone()),
type_annotation: None,
docstring: None,
is_builtin: false,
module: None,
},
definition: Some(location),
type_definition: None,
}));
}
Ok(None)
}
fn match_definition(
symbol: &str,
funcs: &[FuncDef],
classes: &[ClassDef],
file: &Path,
) -> Option<(SymbolKind, Location)> {
for f in funcs {
if f.name == symbol {
let kind = if f.is_method {
SymbolKind::Method
} else {
SymbolKind::Function
};
let col = locate_symbol_column(file, f.line, symbol);
let loc = match col {
Some(c) => Location::with_column(file.display().to_string(), f.line, c),
None => Location::new(file.display().to_string(), f.line),
};
return Some((kind, loc));
}
}
for c in classes {
if c.name == symbol {
let col = locate_symbol_column(file, c.line, symbol);
let loc = match col {
Some(col_v) => {
Location::with_column(file.display().to_string(), c.line, col_v)
}
None => Location::new(file.display().to_string(), c.line),
};
return Some((SymbolKind::Class, loc));
}
}
None
}
fn locate_symbol_column(file: &Path, line: u32, symbol: &str) -> Option<u32> {
let content = std::fs::read_to_string(file).ok()?;
let target_line = line.saturating_sub(1) as usize;
let line_text = content.lines().nth(target_line)?;
let byte_offset = line_text.find(symbol)?;
let col_chars = line_text[..byte_offset].chars().count();
Some(col_chars as u32 + 1)
}
fn find_definition_recursive(
node: Node,
source: &str,
target_name: &str,
file: &Path,
) -> Option<(SymbolKind, Location)> {
match node.kind() {
"function_definition" => {
if let Some(name_node) = node.child_by_field_name("name") {
if let Ok(name) = name_node.utf8_text(source.as_bytes()) {
if name == target_name {
let in_class = is_inside_class(node);
let kind = if in_class {
SymbolKind::Method
} else {
SymbolKind::Function
};
let location = Location::with_column(
file.display().to_string(),
name_node.start_position().row as u32 + 1,
name_node.start_position().column as u32,
);
return Some((kind, location));
}
}
}
}
"class_definition" => {
if let Some(name_node) = node.child_by_field_name("name") {
if let Ok(name) = name_node.utf8_text(source.as_bytes()) {
if name == target_name {
let location = Location::with_column(
file.display().to_string(),
name_node.start_position().row as u32 + 1,
name_node.start_position().column as u32,
);
return Some((SymbolKind::Class, location));
}
}
}
}
"assignment" => {
if let Some(left) = node.child_by_field_name("left") {
if left.kind() == "identifier" {
if let Ok(name) = left.utf8_text(source.as_bytes()) {
if name == target_name {
let location = Location::with_column(
file.display().to_string(),
left.start_position().row as u32 + 1,
left.start_position().column as u32,
);
return Some((SymbolKind::Variable, location));
}
}
}
}
}
_ => {}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(result) = find_definition_recursive(child, source, target_name, file) {
return Some(result);
}
}
}
None
}
fn is_inside_class(node: Node) -> bool {
let mut current = node.parent();
while let Some(n) = current {
if n.kind() == "class_definition" {
return true;
}
current = n.parent();
}
false
}
fn resolve_cross_file(
symbol: &str,
current_file: &Path,
project_root: &Path,
language: Language,
detector: &mut DefinitionCycleDetector,
depth: usize,
) -> RemainingResult<Option<DefinitionResult>> {
if depth >= MAX_IMPORT_DEPTH {
return Ok(None);
}
if detector.visit(current_file, symbol) {
return Ok(None);
}
if language == Language::Python {
return resolve_cross_file_python(symbol, current_file, project_root, detector, depth);
}
resolve_cross_file_walk(symbol, current_file, project_root, language)
}
fn resolve_cross_file_python(
symbol: &str,
current_file: &Path,
project_root: &Path,
detector: &mut DefinitionCycleDetector,
depth: usize,
) -> RemainingResult<Option<DefinitionResult>> {
let source = fs::read_to_string(current_file).map_err(RemainingError::Io)?;
let imports = extract_imports(&source);
for (module_path, imported_names) in imports {
let is_imported = imported_names.is_empty() || imported_names.contains(&symbol.to_string());
if is_imported {
if let Some(resolved_path) =
resolve_module_path(&module_path, current_file, project_root)
{
if resolved_path.exists() {
let module_source =
fs::read_to_string(&resolved_path).map_err(RemainingError::Io)?;
if let Some(result) = find_symbol_in_file(
symbol,
&resolved_path,
&module_source,
Language::Python,
)? {
return Ok(Some(result));
}
if let Some(result) = resolve_cross_file(
symbol,
&resolved_path,
project_root,
Language::Python,
detector,
depth + 1,
)? {
return Ok(Some(result));
}
}
}
}
}
Ok(None)
}
fn resolve_cross_file_walk(
symbol: &str,
current_file: &Path,
project_root: &Path,
language: Language,
) -> RemainingResult<Option<DefinitionResult>> {
let extensions = language.extensions();
let current_canonical = fs::canonicalize(current_file).ok();
let walker = walkdir::WalkDir::new(project_root)
.follow_links(false)
.into_iter()
.filter_entry(|e| !is_skipped_dir(e.path()));
for entry in walker.flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
let matches_ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| {
extensions
.iter()
.any(|ext| ext.trim_start_matches('.').eq_ignore_ascii_case(e))
})
.unwrap_or(false);
if !matches_ext {
continue;
}
if let Some(ref c) = current_canonical {
if let Ok(p) = fs::canonicalize(path) {
if &p == c {
continue;
}
}
}
let Ok(source) = fs::read_to_string(path) else {
continue;
};
if let Some(result) = find_symbol_in_file(symbol, path, &source, language)? {
return Ok(Some(result));
}
}
Ok(None)
}
pub(crate) fn find_workspace_root(file: &Path) -> Option<PathBuf> {
const MARKERS: &[&str] = &[
".git",
"Cargo.toml",
"pyproject.toml",
"setup.py",
"package.json",
"go.mod",
"pom.xml",
"build.gradle",
"build.gradle.kts",
"composer.json",
"Gemfile",
"mix.exs",
];
let start = if file.is_dir() {
file.to_path_buf()
} else {
file.parent()?.to_path_buf()
};
let mut current: Option<&Path> = Some(start.as_path());
while let Some(dir) = current {
for marker in MARKERS {
if dir.join(marker).exists() {
return Some(dir.to_path_buf());
}
}
current = dir.parent();
}
None
}
fn is_skipped_dir(path: &Path) -> bool {
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
return false;
};
matches!(
name,
".git"
| ".hg"
| ".svn"
| "node_modules"
| "target"
| "dist"
| "build"
| ".venv"
| "venv"
| "__pycache__"
| ".tox"
| ".mypy_cache"
| ".pytest_cache"
| ".idea"
| ".vscode"
)
}
fn extract_imports(source: &str) -> Vec<(String, Vec<String>)> {
let mut imports = Vec::new();
for line in source.lines() {
let line = line.trim();
if line.starts_with("from ") {
if let Some(import_idx) = line.find(" import ") {
let module = &line[5..import_idx];
let names_str = &line[import_idx + 8..];
let names: Vec<String> = names_str
.split(',')
.map(|s| {
s.trim()
.split(" as ")
.next()
.unwrap_or("")
.trim()
.to_string()
})
.filter(|s| !s.is_empty() && s != "*")
.collect();
imports.push((module.trim().to_string(), names));
}
} else if let Some(module) = line.strip_prefix("import ") {
let module = module.split(" as ").next().unwrap_or(module).trim();
imports.push((module.to_string(), Vec::new()));
}
}
imports
}
fn resolve_module_path(module: &str, current_file: &Path, project_root: &Path) -> Option<PathBuf> {
let current_dir = current_file.parent()?;
let dot_count = module.chars().take_while(|&c| c == '.').count();
if dot_count > 0 {
let remainder = &module[dot_count..];
let mut base = current_dir.to_path_buf();
for _ in 1..dot_count {
base = base.parent()?.to_path_buf();
}
if remainder.is_empty() {
let pkg_candidate = base.join("__init__.py");
if pkg_candidate.exists() {
return Some(pkg_candidate);
}
return None;
}
let rel_path = remainder.replace('.', "/");
let candidate = base.join(&rel_path).with_extension("py");
if candidate.exists() {
return Some(candidate);
}
let pkg_candidate = base.join(&rel_path).join("__init__.py");
if pkg_candidate.exists() {
return Some(pkg_candidate);
}
return None;
}
let rel_path = module.replace('.', "/");
let candidate = current_dir.join(&rel_path).with_extension("py");
if candidate.exists() {
return Some(candidate);
}
let pkg_candidate = current_dir.join(&rel_path).join("__init__.py");
if pkg_candidate.exists() {
return Some(pkg_candidate);
}
let candidate = project_root.join(&rel_path).with_extension("py");
if candidate.exists() {
return Some(candidate);
}
let pkg_candidate = project_root.join(&rel_path).join("__init__.py");
if pkg_candidate.exists() {
return Some(pkg_candidate);
}
None
}
pub fn is_builtin(name: &str, language: &Language) -> bool {
match language {
Language::Python => PYTHON_BUILTINS.contains(&name),
_ => false,
}
}
fn detect_language(file: &Path, hint: &str) -> RemainingResult<Language> {
if hint != "auto" {
let normalized = hint.to_lowercase();
let alias = match normalized.as_str() {
"py" => Some(Language::Python),
"ts" => Some(Language::TypeScript),
"tsx" => Some(Language::TypeScript),
"js" => Some(Language::JavaScript),
"jsx" => Some(Language::JavaScript),
"rs" => Some(Language::Rust),
"golang" => Some(Language::Go),
"c++" => Some(Language::Cpp),
"c#" => Some(Language::CSharp),
"cs" => Some(Language::CSharp),
"kt" => Some(Language::Kotlin),
"rb" => Some(Language::Ruby),
"ex" | "exs" => Some(Language::Elixir),
"ml" | "mli" => Some(Language::Ocaml),
_ => None,
};
if let Some(lang) = alias {
return Ok(lang);
}
for lang in Language::all() {
if lang.as_str() == normalized {
return Ok(*lang);
}
}
return Err(RemainingError::unsupported_language(hint));
}
Language::from_path(file).ok_or_else(|| {
let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
RemainingError::unsupported_language(ext)
})
}
fn format_definition_text(result: &DefinitionResult) -> String {
let mut output = String::new();
output.push_str("=== Definition Result ===\n\n");
output.push_str(&format!("Symbol: {}\n", result.symbol.name));
output.push_str(&format!("Kind: {:?}\n", result.symbol.kind));
if result.symbol.is_builtin {
output.push_str("Type: Built-in\n");
if let Some(ref module) = result.symbol.module {
output.push_str(&format!("Module: {}\n", module));
}
} else if let Some(ref location) = result.definition {
output.push_str("\nDefinition Location:\n");
output.push_str(&format!(" File: {}\n", location.file));
output.push_str(&format!(" Line: {}\n", location.line));
if location.column > 0 {
output.push_str(&format!(" Column: {}\n", location.column));
}
} else {
output.push_str("\nDefinition: Not found\n");
}
if let Some(ref type_def) = result.type_definition {
output.push_str("\nType Definition:\n");
output.push_str(&format!(" File: {}\n", type_def.file));
output.push_str(&format!(" Line: {}\n", type_def.line));
}
if let Some(ref docstring) = result.symbol.docstring {
output.push_str(&format!("\nDocstring:\n {}\n", docstring));
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_builtin_python() {
assert!(is_builtin("len", &Language::Python));
assert!(is_builtin("print", &Language::Python));
assert!(is_builtin("range", &Language::Python));
assert!(!is_builtin("my_func", &Language::Python));
}
#[test]
fn test_cycle_detector() {
let mut detector = DefinitionCycleDetector::new();
assert!(!detector.visit(Path::new("file.py"), "symbol"));
assert!(detector.visit(Path::new("file.py"), "symbol"));
assert!(!detector.visit(Path::new("other.py"), "symbol"));
}
#[test]
fn test_detect_language() {
assert_eq!(
detect_language(Path::new("test.py"), "auto").unwrap(),
Language::Python
);
}
#[test]
fn test_detect_language_with_hint() {
assert_eq!(
detect_language(Path::new("test.txt"), "python").unwrap(),
Language::Python
);
}
#[test]
fn test_extract_imports() {
let source = r#"
from os import path, getcwd
from sys import argv
import json
import re as regex
"#;
let imports = extract_imports(source);
assert_eq!(imports.len(), 4);
assert_eq!(imports[0].0, "os");
assert!(imports[0].1.contains(&"path".to_string()));
assert!(imports[0].1.contains(&"getcwd".to_string()));
assert_eq!(imports[1].0, "sys");
assert!(imports[1].1.contains(&"argv".to_string()));
assert_eq!(imports[2].0, "json");
assert_eq!(imports[3].0, "re");
}
#[test]
fn test_extract_imports_relative() {
let source = r#"
from .utils import echo, make_str
from .exceptions import Abort
from ._utils import FLAG_NEEDS_VALUE
from . import types
"#;
let imports = extract_imports(source);
assert_eq!(imports.len(), 4);
assert_eq!(imports[0].0, ".utils");
assert!(imports[0].1.contains(&"echo".to_string()));
assert!(imports[0].1.contains(&"make_str".to_string()));
assert_eq!(imports[1].0, ".exceptions");
assert!(imports[1].1.contains(&"Abort".to_string()));
assert_eq!(imports[2].0, "._utils");
assert!(imports[2].1.contains(&"FLAG_NEEDS_VALUE".to_string()));
assert_eq!(imports[3].0, ".");
assert!(imports[3].1.contains(&"types".to_string()));
}
#[test]
fn test_resolve_module_path_relative_import() {
let dir = tempfile::tempdir().unwrap();
let pkg = dir.path().join("mypkg");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("__init__.py"), "").unwrap();
fs::write(pkg.join("core.py"), "from .utils import helper\n").unwrap();
fs::write(pkg.join("utils.py"), "def helper(): pass\n").unwrap();
let current_file = pkg.join("core.py");
let project_root = dir.path();
let resolved = resolve_module_path(".utils", ¤t_file, project_root);
assert!(
resolved.is_some(),
"resolve_module_path should find .utils relative to core.py"
);
assert_eq!(
resolved.unwrap(),
pkg.join("utils.py"),
"Should resolve to sibling utils.py"
);
}
#[test]
fn test_resolve_module_path_relative_import_subpackage() {
let dir = tempfile::tempdir().unwrap();
let pkg = dir.path().join("mypkg");
let sub = pkg.join("sub");
fs::create_dir_all(&sub).unwrap();
fs::write(pkg.join("__init__.py"), "").unwrap();
fs::write(sub.join("__init__.py"), "").unwrap();
fs::write(pkg.join("core.py"), "").unwrap();
fs::write(sub.join("helpers.py"), "def helper(): pass\n").unwrap();
let current_file = pkg.join("core.py");
let project_root = dir.path();
let resolved = resolve_module_path(".sub.helpers", ¤t_file, project_root);
assert!(
resolved.is_some(),
"resolve_module_path should find .sub.helpers relative to core.py"
);
assert_eq!(
resolved.unwrap(),
sub.join("helpers.py"),
"Should resolve to sub/helpers.py"
);
}
#[test]
fn test_cross_file_definition_via_relative_import() {
let dir = tempfile::tempdir().unwrap();
let pkg = dir.path().join("mypkg");
fs::create_dir_all(&pkg).unwrap();
fs::write(pkg.join("__init__.py"), "").unwrap();
fs::write(
pkg.join("core.py"),
"from .utils import echo\n\ndef main():\n echo('hello')\n",
)
.unwrap();
fs::write(pkg.join("utils.py"), "def echo(msg):\n print(msg)\n").unwrap();
let result =
find_definition_by_name("echo", &pkg.join("core.py"), Some(dir.path()), "python");
assert!(
result.is_ok(),
"Should find echo via cross-file resolution: {:?}",
result.err()
);
let result = result.unwrap();
assert_eq!(result.symbol.name, "echo");
assert_eq!(result.symbol.kind, SymbolKind::Function);
assert!(
result.definition.is_some(),
"Should have a definition location"
);
let def_loc = result.definition.unwrap();
assert!(
def_loc.file.contains("utils.py"),
"Definition should be in utils.py, got: {}",
def_loc.file
);
assert_eq!(def_loc.line, 1, "echo is defined on line 1 of utils.py");
}
#[test]
fn test_find_definition_typescript_function() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.ts");
fs::write(
&file,
"export function target_fn(): number { return 42; }\n\
export function caller(): void { target_fn(); }\n",
)
.unwrap();
let result = find_definition_by_name("target_fn", &file, None, "typescript")
.expect("definition lookup should succeed for TypeScript");
assert_eq!(result.symbol.name, "target_fn");
assert_eq!(result.symbol.kind, SymbolKind::Function);
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 1, "target_fn is on line 1, got {}", loc.line);
}
#[test]
fn test_find_definition_rust_function() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("lib.rs");
fs::write(
&file,
"fn helper() -> i32 { 1 }\n\nfn target_fn() -> i32 { helper() }\n",
)
.unwrap();
let result = find_definition_by_name("target_fn", &file, None, "rust")
.expect("definition lookup should succeed for Rust");
assert_eq!(result.symbol.name, "target_fn");
assert_eq!(result.symbol.kind, SymbolKind::Function);
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 3, "target_fn is on line 3, got {}", loc.line);
}
#[test]
fn test_find_definition_go_function() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.go");
fs::write(
&file,
"package main\n\nfunc target_fn() int { return 1 }\n\nfunc main() { target_fn() }\n",
)
.unwrap();
let result = find_definition_by_name("target_fn", &file, None, "go")
.expect("definition lookup should succeed for Go");
assert_eq!(result.symbol.name, "target_fn");
assert_eq!(result.symbol.kind, SymbolKind::Function);
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 3, "target_fn is on line 3, got {}", loc.line);
}
#[test]
fn test_find_definition_java_method() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("Main.java");
fs::write(
&file,
"class Main {\n public static int target_fn() { return 1; }\n public static void main(String[] args) { target_fn(); }\n}\n",
)
.unwrap();
let result = find_definition_by_name("target_fn", &file, None, "java")
.expect("definition lookup should succeed for Java");
assert_eq!(result.symbol.name, "target_fn");
assert_eq!(
result.symbol.kind,
SymbolKind::Method,
"Java method inside class should be Method, got {:?}",
result.symbol.kind
);
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 2, "target_fn is on line 2, got {}", loc.line);
}
#[test]
fn test_find_definition_class_typescript() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("widget.ts");
fs::write(&file, "export class Widget {\n render(): void {}\n}\n").unwrap();
let result = find_definition_by_name("Widget", &file, None, "typescript")
.expect("definition lookup should succeed for TS class");
assert_eq!(result.symbol.name, "Widget");
assert_eq!(
result.symbol.kind,
SymbolKind::Class,
"Widget should be Class kind, got {:?}",
result.symbol.kind
);
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 1);
}
#[test]
fn test_find_definition_position_rust() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("lib.rs");
let source = "fn target_fn() -> i32 { 1 }\n\nfn caller() -> i32 { target_fn() }\n";
fs::write(&file, source).unwrap();
let result = find_definition_by_position(&file, 3, 22, None, "rust")
.expect("position-based lookup should succeed for Rust");
assert_eq!(result.symbol.name, "target_fn");
let loc = result.definition.expect("definition location must be Some");
assert_eq!(loc.line, 1, "definition is on line 1");
}
#[test]
fn test_detect_language_all_18() {
let cases: &[(&str, &str, Language)] = &[
("a.py", "auto", Language::Python),
("a.ts", "auto", Language::TypeScript),
("a.tsx", "auto", Language::TypeScript),
("a.js", "auto", Language::JavaScript),
("a.jsx", "auto", Language::JavaScript),
("a.rs", "auto", Language::Rust),
("a.go", "auto", Language::Go),
("a.java", "auto", Language::Java),
("a.c", "auto", Language::C),
("a.h", "auto", Language::C),
("a.cpp", "auto", Language::Cpp),
("a.cc", "auto", Language::Cpp),
("a.hpp", "auto", Language::Cpp),
("a.rb", "auto", Language::Ruby),
("a.kt", "auto", Language::Kotlin),
("a.swift", "auto", Language::Swift),
("a.cs", "auto", Language::CSharp),
("a.scala", "auto", Language::Scala),
("a.php", "auto", Language::Php),
("a.lua", "auto", Language::Lua),
("a.luau", "auto", Language::Luau),
("a.ex", "auto", Language::Elixir),
("a.exs", "auto", Language::Elixir),
("a.ml", "auto", Language::Ocaml),
];
for (path, hint, expected) in cases {
let got = detect_language(Path::new(path), hint)
.unwrap_or_else(|e| panic!("detect_language failed for {}: {:?}", path, e));
assert_eq!(got, *expected, "wrong language for {}", path);
}
}
#[test]
fn test_definition_resolves_local_param() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("local.py");
fs::write(&file, "def foo(x):\n return x + 1\n").unwrap();
let result = find_definition_by_position(&file, 2, 11, None, "python")
.expect("local-scope resolution should succeed");
assert_eq!(result.symbol.name, "x");
assert_eq!(
result.symbol.kind,
SymbolKind::Parameter,
"should resolve local `x` as Parameter, got {:?}",
result.symbol.kind
);
let def = result.definition.expect("definition location must be Some");
assert_eq!(def.line, 1, "param `x` is declared on line 1, got {}", def.line);
}
#[test]
fn test_definition_resolves_file_scope_function() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("filescope.py");
fs::write(
&file,
"def helper():\n return 1\n\ndef main():\n return helper()\n",
)
.unwrap();
let result = find_definition_by_position(&file, 5, 11, None, "python")
.expect("file-scope resolution should succeed");
assert_eq!(result.symbol.name, "helper");
assert_eq!(result.symbol.kind, SymbolKind::Function);
let def = result.definition.expect("definition location must be Some");
assert_eq!(
def.line, 1,
"helper is declared on line 1, got {}",
def.line
);
}
#[test]
fn test_definition_resolves_import_alias() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("imports.py");
fs::write(
&file,
"import click\n\ndef main():\n click.echo(\"hi\")\n",
)
.unwrap();
let result = find_definition_by_position(&file, 4, 4, None, "python")
.expect("import-scope resolution should succeed");
assert_eq!(result.symbol.name, "click");
let def = result
.definition
.expect("import-scope resolution must produce a definition location");
assert_eq!(
def.line, 1,
"import click is on line 1, got {}",
def.line
);
}
#[test]
fn test_definition_unresolved_message() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("unresolved.py");
fs::write(&file, "x = 1\nprint(nonexistent_name)\n").unwrap();
let err = find_definition_by_position(&file, 2, 6, None, "python")
.expect_err("unresolved name must produce an error");
let msg = err.to_string();
assert!(
msg.contains("unresolved at"),
"error should mention 'unresolved at', got: {}",
msg
);
assert!(
msg.contains("nonexistent_name"),
"error should mention the symbol, got: {}",
msg
);
}
#[test]
fn test_definition_resolves_js_import_alias() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("app.js");
fs::write(
&file,
"import express from \"express\";\nconst app = express();\n",
)
.unwrap();
let result = find_definition_by_position(&file, 2, 12, None, "javascript")
.expect("JS import resolution should succeed");
assert_eq!(result.symbol.name, "express");
let def = result.definition.expect("definition location must be Some");
assert_eq!(def.line, 1, "import is on line 1, got {}", def.line);
}
#[test]
fn test_definition_resolves_rust_let_binding() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("lib.rs");
fs::write(
&file,
"fn main() {\n let counter = 42;\n println!(\"{}\", counter);\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 3, 19, None, "rust")
.expect("Rust let-binding resolution should succeed");
assert_eq!(result.symbol.name, "counter");
assert_eq!(result.symbol.kind, SymbolKind::Variable);
let def = result.definition.expect("definition location must be Some");
assert_eq!(
def.line, 2,
"let counter is on line 2, got {}",
def.line
);
}
fn assert_resolves_param(
file: &Path,
line: u32,
column: u32,
lang: &str,
expected_name: &str,
expected_def_line: u32,
) {
let result = find_definition_by_position(file, line, column, None, lang)
.unwrap_or_else(|e| panic!("{} resolution should succeed: {}", lang, e));
assert_eq!(result.symbol.name, expected_name);
assert_eq!(
result.symbol.kind,
SymbolKind::Parameter,
"{}: expected Parameter, got {:?}",
lang,
result.symbol.kind
);
let def = result.definition.expect("definition must be Some");
assert_eq!(
def.line, expected_def_line,
"{}: param declared on line {}, got {}",
lang, expected_def_line, def.line
);
}
#[test]
fn test_definition_resolves_local_param_java() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("Foo.java");
fs::write(
&file,
"class Foo {\n int add(int a, int b) {\n return a + b;\n }\n}\n",
)
.unwrap();
assert_resolves_param(&file, 3, 11, "java", "a", 2);
}
#[test]
fn test_definition_resolves_local_param_c() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.c");
fs::write(&file, "int add(int a, int b) {\n return a + b;\n}\n").unwrap();
assert_resolves_param(&file, 2, 9, "c", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_cpp() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.cpp");
fs::write(&file, "int add(int a, int b) {\n return a + b;\n}\n").unwrap();
assert_resolves_param(&file, 2, 9, "cpp", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_ruby() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.rb");
fs::write(&file, "def add(a, b)\n a + b\nend\n").unwrap();
assert_resolves_param(&file, 2, 2, "ruby", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_kotlin() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.kt");
fs::write(
&file,
"fun add(a: Int, b: Int): Int {\n return a + b\n}\n",
)
.unwrap();
assert_resolves_param(&file, 2, 9, "kotlin", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_swift() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.swift");
fs::write(
&file,
"func add(a: Int, b: Int) -> Int {\n return a + b\n}\n",
)
.unwrap();
assert_resolves_param(&file, 2, 9, "swift", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_scala() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.scala");
fs::write(
&file,
"def add(a: Int, b: Int): Int = {\n a + b\n}\n",
)
.unwrap();
assert_resolves_param(&file, 2, 2, "scala", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_php() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.php");
fs::write(
&file,
"<?php\nfunction add($a, $b) {\n return $a + $b;\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 3, 10, None, "php")
.expect("php resolution should succeed");
let name = result.symbol.name.trim_start_matches('$');
assert_eq!(name, "a");
assert_eq!(result.symbol.kind, SymbolKind::Parameter);
let def = result.definition.expect("definition must be Some");
assert_eq!(def.line, 2);
}
#[test]
fn test_definition_resolves_local_param_lua() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.lua");
fs::write(&file, "local function add(a, b)\n return a + b\nend\n").unwrap();
assert_resolves_param(&file, 2, 9, "lua", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_luau() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.luau");
fs::write(&file, "local function add(a, b)\n return a + b\nend\n").unwrap();
assert_resolves_param(&file, 2, 9, "luau", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_elixir() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.ex");
fs::write(
&file,
"defmodule Foo do\n def add(a, b) do\n a + b\n end\nend\n",
)
.unwrap();
assert_resolves_param(&file, 3, 4, "elixir", "a", 2);
}
#[test]
fn test_definition_resolves_local_param_ocaml() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.ml");
fs::write(&file, "let add a b = a + b\n").unwrap();
assert_resolves_param(&file, 1, 14, "ocaml", "a", 1);
}
#[test]
fn test_definition_resolves_local_param_csharp() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("Foo.cs");
fs::write(
&file,
"class Foo {\n int Add(int a, int b) {\n return a + b;\n }\n}\n",
)
.unwrap();
assert_resolves_param(&file, 3, 11, "csharp", "a", 2);
}
#[test]
fn test_definition_resolves_import_alias_java() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("Foo.java");
fs::write(
&file,
"import java.util.List;\nclass Foo {\n List<String> xs;\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 3, 2, None, "java")
.expect("java import resolution should succeed");
assert_eq!(result.symbol.name, "List");
let def = result.definition.expect("definition must be Some");
assert_eq!(def.line, 1, "import is on line 1, got {}", def.line);
}
#[test]
fn test_definition_resolves_local_var_kotlin() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.kt");
fs::write(
&file,
"fun main() {\n val counter = 42\n println(counter)\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 3, 10, None, "kotlin")
.expect("kotlin val resolution should succeed");
assert_eq!(result.symbol.name, "counter");
assert_eq!(result.symbol.kind, SymbolKind::Variable);
let def = result.definition.expect("definition must be Some");
assert_eq!(def.line, 2);
}
#[test]
fn test_definition_resolves_param_swift() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.swift");
fs::write(
&file,
"func greet(name: String) -> String {\n return \"Hello, \" + name\n}\n",
)
.unwrap();
assert_resolves_param(&file, 2, 21, "swift", "name", 1);
}
#[test]
fn test_definition_resolves_use_statement_php() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("foo.php");
fs::write(
&file,
"<?php\nuse App\\Models\\User;\nfunction get(): User {\n return new User();\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 4, 14, None, "php")
.expect("php use resolution should succeed");
assert_eq!(result.symbol.name, "User");
let def = result.definition.expect("definition must be Some");
assert_eq!(def.line, 2, "use statement on line 2, got {}", def.line);
}
#[test]
fn test_definition_resolves_local_var_csharp() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("Foo.cs");
fs::write(
&file,
"class Foo {\n void M() {\n var counter = 42;\n System.Console.WriteLine(counter);\n }\n}\n",
)
.unwrap();
let result = find_definition_by_position(&file, 4, 29, None, "csharp")
.expect("csharp var resolution should succeed");
assert_eq!(result.symbol.name, "counter");
assert_eq!(result.symbol.kind, SymbolKind::Variable);
let def = result.definition.expect("definition must be Some");
assert_eq!(def.line, 3);
}
}