Skip to main content

codelens_engine/call_graph/
language.rs

1use std::path::Path;
2use tree_sitter::Language;
3
4use super::queries::*;
5
6pub(crate) struct CallLanguageConfig {
7    /// Stable language/cache key. JS and TS can share query text but not compiled queries.
8    pub(crate) language_key: &'static str,
9    pub(crate) language: Language,
10    /// Query to find function definitions: captures @func.name
11    pub(crate) func_query: &'static str,
12    /// Query to find call sites: captures @callee
13    pub(crate) call_query: &'static str,
14}
15
16pub(crate) fn call_language_for_path(path: &Path) -> Option<CallLanguageConfig> {
17    let lang_config = crate::lang_config::language_for_path(path)?;
18    // Map canonical extension to call graph queries (not all languages support this)
19    let (language_key, func_query, call_query) = match lang_config.extension {
20        "py" => ("py", PYTHON_FUNC_QUERY, PYTHON_CALL_QUERY),
21        "js" => ("js", JS_FUNC_QUERY, JS_JSX_CALL_QUERY),
22        "ts" => ("ts", JS_FUNC_QUERY, JS_CALL_QUERY),
23        "tsx" => ("tsx", JS_FUNC_QUERY, JS_JSX_CALL_QUERY),
24        "go" => ("go", GO_FUNC_QUERY, GO_CALL_QUERY),
25        "java" => ("java", JAVA_FUNC_QUERY, JAVA_CALL_QUERY),
26        "kt" => ("kt", KOTLIN_FUNC_QUERY, KOTLIN_CALL_QUERY),
27        "rs" => ("rs", RUST_FUNC_QUERY, RUST_CALL_QUERY),
28        _ => return None,
29    };
30    Some(CallLanguageConfig {
31        language_key,
32        language: lang_config.language,
33        func_query,
34        call_query,
35    })
36}
37
38pub(crate) fn call_language_key_for_path(path: &str) -> Option<&'static str> {
39    match Path::new(path).extension().and_then(|value| value.to_str()) {
40        Some("py") => Some("py"),
41        Some("js") => Some("js"),
42        Some("jsx") => Some("jsx"),
43        Some("ts") => Some("ts"),
44        Some("tsx") => Some("tsx"),
45        Some("go") => Some("go"),
46        Some("java") => Some("java"),
47        Some("kt") => Some("kt"),
48        Some("rs") => Some("rs"),
49        _ => None,
50    }
51}
52
53pub(crate) fn same_call_language(a: &str, b: &str) -> bool {
54    call_language_key_for_path(a)
55        .is_some_and(|a_lang| Some(a_lang) == call_language_key_for_path(b))
56}
57
58pub(crate) fn shared_parent_component_count(a: &str, b: &str) -> usize {
59    let a_components: Vec<String> = Path::new(a)
60        .parent()
61        .into_iter()
62        .flat_map(|path| path.components())
63        .map(|component| component.as_os_str().to_string_lossy().into_owned())
64        .collect();
65    let b_components: Vec<String> = Path::new(b)
66        .parent()
67        .into_iter()
68        .flat_map(|path| path.components())
69        .map(|component| component.as_os_str().to_string_lossy().into_owned())
70        .collect();
71
72    a_components
73        .iter()
74        .zip(b_components.iter())
75        .take_while(|(a, b)| a == b)
76        .count()
77}
78
79pub(crate) fn best_path_proximity_candidate<'a>(
80    caller_file: &str,
81    defs: &'a [String],
82) -> Option<&'a String> {
83    defs.iter()
84        .filter(|def| {
85            same_call_language(caller_file, def)
86                && shared_parent_component_count(caller_file, def) > 0
87        })
88        .max_by_key(|def| shared_parent_component_count(caller_file, def))
89}