use std::sync::{Arc, OnceLock};
use tree_sitter::{Language, Query};
pub struct CallConfig {
pub language: Language,
pub query: Query,
}
pub struct LangConfig {
pub language: Language,
pub query: Query,
}
#[must_use]
pub fn config_for_extension(ext: &str) -> Option<Arc<LangConfig>> {
static CACHE: OnceLock<std::collections::HashMap<&'static str, Arc<LangConfig>>> =
OnceLock::new();
let cache = CACHE.get_or_init(|| {
let mut m = std::collections::HashMap::new();
for &ext in &[
"rs", "py", "js", "jsx", "ts", "tsx", "go", "java", "c", "h", "cpp", "cc", "cxx",
"hpp", "sh", "bash", "bats", "rb", "tf", "tfvars", "hcl", "kt", "kts", "swift",
"scala", "toml", "json", "yaml", "yml", "md",
] {
if let Some(cfg) = compile_config(ext) {
m.insert(ext, Arc::new(cfg));
}
}
m
});
cache.get(ext).cloned()
}
#[expect(
clippy::too_many_lines,
reason = "one match arm per language — flat by design"
)]
fn compile_config(ext: &str) -> Option<LangConfig> {
let (lang, query_str): (Language, &str) = match ext {
"rs" => (
tree_sitter_rust::LANGUAGE.into(),
concat!(
"(function_item name: (identifier) @name) @def\n",
"(struct_item name: (type_identifier) @name) @def\n",
"(enum_item name: (type_identifier) @name) @def\n",
"(type_item name: (type_identifier) @name) @def\n",
"(field_declaration name: (field_identifier) @name) @def\n",
"(enum_variant name: (identifier) @name) @def\n",
"(impl_item type: (type_identifier) @name) @def\n",
"(trait_item name: (type_identifier) @name) @def\n",
"(const_item name: (identifier) @name) @def\n",
"(static_item name: (identifier) @name) @def\n",
"(mod_item name: (identifier) @name) @def",
),
),
"py" => (
tree_sitter_python::LANGUAGE.into(),
concat!(
"(function_definition name: (identifier) @name) @def\n",
"(class_definition name: (identifier) @name body: (block) @def)\n",
"(assignment left: (identifier) @name) @def",
),
),
"js" | "jsx" => (
tree_sitter_javascript::LANGUAGE.into(),
concat!(
"(function_declaration name: (identifier) @name) @def\n",
"(method_definition name: (property_identifier) @name) @def\n",
"(class_declaration name: (identifier) @name) @def\n",
"(variable_declarator name: (identifier) @name) @def",
),
),
"ts" => (
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
concat!(
"(function_declaration name: (identifier) @name) @def\n",
"(method_definition name: (property_identifier) @name) @def\n",
"(class_declaration name: (type_identifier) @name) @def\n",
"(interface_declaration name: (type_identifier) @name) @def\n",
"(variable_declarator name: (identifier) @name) @def\n",
"(type_alias_declaration name: (type_identifier) @name) @def\n",
"(enum_declaration name: (identifier) @name) @def",
),
),
"tsx" => (
tree_sitter_typescript::LANGUAGE_TSX.into(),
concat!(
"(function_declaration name: (identifier) @name) @def\n",
"(method_definition name: (property_identifier) @name) @def\n",
"(class_declaration name: (type_identifier) @name) @def\n",
"(interface_declaration name: (type_identifier) @name) @def\n",
"(variable_declarator name: (identifier) @name) @def\n",
"(type_alias_declaration name: (type_identifier) @name) @def\n",
"(enum_declaration name: (identifier) @name) @def",
),
),
"go" => (
tree_sitter_go::LANGUAGE.into(),
concat!(
"(function_declaration name: (identifier) @name) @def\n",
"(method_declaration name: (field_identifier) @name) @def\n",
"(type_declaration (type_spec name: (type_identifier) @name)) @def\n",
"(const_spec name: (identifier) @name) @def",
),
),
"java" => (
tree_sitter_java::LANGUAGE.into(),
concat!(
"(method_declaration name: (identifier) @name) @def\n",
"(class_declaration name: (identifier) @name) @def\n",
"(interface_declaration name: (identifier) @name) @def\n",
"(field_declaration declarator: (variable_declarator name: (identifier) @name)) @def\n",
"(enum_constant name: (identifier) @name) @def\n",
"(enum_declaration name: (identifier) @name) @def\n",
"(constructor_declaration name: (identifier) @name) @def",
),
),
"c" | "h" => (
tree_sitter_c::LANGUAGE.into(),
concat!(
"(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def\n",
"(declaration declarator: (init_declarator declarator: (identifier) @name)) @def\n",
"(struct_specifier name: (type_identifier) @name) @def\n",
"(enum_specifier name: (type_identifier) @name) @def\n",
"(type_definition declarator: (type_identifier) @name) @def",
),
),
"cpp" | "cc" | "cxx" | "hpp" => (
tree_sitter_cpp::LANGUAGE.into(),
concat!(
"(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def\n",
"(class_specifier name: (type_identifier) @name) @def\n",
"(declaration declarator: (init_declarator declarator: (identifier) @name)) @def\n",
"(struct_specifier name: (type_identifier) @name) @def\n",
"(enum_specifier name: (type_identifier) @name) @def\n",
"(type_definition declarator: (type_identifier) @name) @def\n",
"(namespace_definition name: (namespace_identifier) @name) @def\n",
"(field_declaration declarator: (field_identifier) @name) @def",
),
),
"sh" | "bash" | "bats" => (
tree_sitter_bash::LANGUAGE.into(),
concat!(
"(function_definition name: (word) @name) @def\n",
"(variable_assignment name: (variable_name) @name) @def",
),
),
"rb" => (
tree_sitter_ruby::LANGUAGE.into(),
concat!(
"(method name: (identifier) @name) @def\n",
"(class name: (constant) @name) @def\n",
"(module name: (constant) @name) @def\n",
"(assignment left: (identifier) @name) @def\n",
"(assignment left: (constant) @name) @def",
),
),
"tf" | "tfvars" | "hcl" => (
tree_sitter_hcl::LANGUAGE.into(),
"(block (identifier) @name) @def",
),
"kt" | "kts" => (
tree_sitter_kotlin_ng::LANGUAGE.into(),
concat!(
"(function_declaration name: (identifier) @name) @def\n",
"(class_declaration name: (identifier) @name) @def\n",
"(object_declaration name: (identifier) @name) @def\n",
"(property_declaration (identifier) @name) @def\n",
"(enum_entry (identifier) @name) @def",
),
),
"swift" => (
tree_sitter_swift::LANGUAGE.into(),
concat!(
"(function_declaration name: (simple_identifier) @name) @def\n",
"(class_declaration name: (type_identifier) @name) @def\n",
"(protocol_declaration name: (type_identifier) @name) @def\n",
"(property_declaration name: (pattern bound_identifier: (simple_identifier) @name)) @def\n",
"(typealias_declaration name: (type_identifier) @name) @def",
),
),
"scala" => (
tree_sitter_scala::LANGUAGE.into(),
concat!(
"(function_definition name: (identifier) @name) @def\n",
"(class_definition name: (identifier) @name) @def\n",
"(trait_definition name: (identifier) @name) @def\n",
"(object_definition name: (identifier) @name) @def\n",
"(val_definition pattern: (identifier) @name) @def\n",
"(var_definition pattern: (identifier) @name) @def\n",
"(type_definition name: (type_identifier) @name) @def",
),
),
"toml" => (
tree_sitter_toml_ng::LANGUAGE.into(),
concat!(
"(table (bare_key) @name) @def\n",
"(pair (bare_key) @name) @def",
),
),
"json" => (
tree_sitter_json::LANGUAGE.into(),
"(pair key: (string (string_content) @name)) @def",
),
"yaml" | "yml" => (
tree_sitter_yaml::LANGUAGE.into(),
"(block_mapping_pair key: (flow_node (plain_scalar (string_scalar) @name))) @def",
),
"md" => (
tree_sitter_md::LANGUAGE.into(),
"(atx_heading heading_content: (inline) @name) @def",
),
_ => return None,
};
let query = match Query::new(&lang, query_str) {
Ok(q) => q,
Err(e) => {
tracing::warn!(ext, %e, "tree-sitter query compilation failed — language may be ABI-incompatible");
return None;
}
};
Some(LangConfig {
language: lang,
query,
})
}
#[must_use]
pub fn call_query_for_extension(ext: &str) -> Option<Arc<CallConfig>> {
static CACHE: OnceLock<std::collections::HashMap<&'static str, Arc<CallConfig>>> =
OnceLock::new();
let cache = CACHE.get_or_init(|| {
let mut m = std::collections::HashMap::new();
for &ext in &[
"rs", "py", "js", "jsx", "ts", "tsx", "go", "java", "c", "h", "cpp", "cc", "cxx",
"hpp", "sh", "bash", "bats", "rb", "tf", "tfvars", "hcl", "kt", "kts", "swift",
"scala",
] {
if let Some(cfg) = compile_call_config(ext) {
m.insert(ext, Arc::new(cfg));
}
}
m
});
cache.get(ext).cloned()
}
#[expect(
clippy::too_many_lines,
reason = "one match arm per language — flat by design"
)]
fn compile_call_config(ext: &str) -> Option<CallConfig> {
let (lang, query_str): (Language, &str) = match ext {
"rs" => (
tree_sitter_rust::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (field_expression field: (field_identifier) @callee)) @call\n",
"(call_expression function: (scoped_identifier name: (identifier) @callee)) @call",
),
),
"py" => (
tree_sitter_python::LANGUAGE.into(),
concat!(
"(call function: (identifier) @callee) @call\n",
"(call function: (attribute attribute: (identifier) @callee)) @call",
),
),
"js" | "jsx" => (
tree_sitter_javascript::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (member_expression property: (property_identifier) @callee)) @call",
),
),
"ts" => (
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (member_expression property: (property_identifier) @callee)) @call",
),
),
"tsx" => (
tree_sitter_typescript::LANGUAGE_TSX.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (member_expression property: (property_identifier) @callee)) @call",
),
),
"go" => (
tree_sitter_go::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (selector_expression field: (field_identifier) @callee)) @call",
),
),
"java" => (
tree_sitter_java::LANGUAGE.into(),
"(method_invocation name: (identifier) @callee) @call",
),
"c" | "h" => (
tree_sitter_c::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (field_expression field: (field_identifier) @callee)) @call",
),
),
"cpp" | "cc" | "cxx" | "hpp" => (
tree_sitter_cpp::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (field_expression field: (field_identifier) @callee)) @call",
),
),
"sh" | "bash" | "bats" => (
tree_sitter_bash::LANGUAGE.into(),
"(command name: (command_name (word) @callee)) @call",
),
"rb" => (
tree_sitter_ruby::LANGUAGE.into(),
"(call method: (identifier) @callee) @call",
),
"tf" | "tfvars" | "hcl" => (
tree_sitter_hcl::LANGUAGE.into(),
"(function_call (identifier) @callee) @call",
),
"kt" | "kts" => (
tree_sitter_kotlin_ng::LANGUAGE.into(),
"(call_expression (identifier) @callee) @call",
),
"swift" => (
tree_sitter_swift::LANGUAGE.into(),
"(call_expression (simple_identifier) @callee) @call",
),
"scala" => (
tree_sitter_scala::LANGUAGE.into(),
concat!(
"(call_expression function: (identifier) @callee) @call\n",
"(call_expression function: (field_expression field: (identifier) @callee)) @call",
),
),
_ => return None,
};
let query = match Query::new(&lang, query_str) {
Ok(q) => q,
Err(e) => {
tracing::warn!(ext, %e, "tree-sitter call query compilation failed");
return None;
}
};
Some(CallConfig {
language: lang,
query,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rust_extension_resolves() {
assert!(config_for_extension("rs").is_some());
}
#[test]
fn python_extension_resolves() {
assert!(config_for_extension("py").is_some());
}
#[test]
fn unknown_extension_returns_none() {
assert!(config_for_extension("xyz").is_none());
}
#[test]
fn all_supported_extensions() {
let exts = [
"rs", "py", "js", "jsx", "ts", "tsx", "go", "java", "c", "h", "cpp", "cc", "cxx",
"hpp", "sh", "bash", "bats", "rb", "tf", "tfvars", "hcl", "kt", "kts", "swift",
"scala", "toml", "json", "yaml", "yml", "md",
];
for ext in &exts {
assert!(config_for_extension(ext).is_some(), "failed for {ext}");
}
}
#[test]
fn all_call_query_extensions() {
let exts = [
"rs", "py", "js", "jsx", "ts", "tsx", "go", "java", "c", "h", "cpp", "cc", "cxx",
"hpp", "sh", "bash", "bats", "rb", "tf", "tfvars", "hcl", "kt", "kts", "swift",
"scala",
];
for ext in &exts {
assert!(
call_query_for_extension(ext).is_some(),
"call query failed for {ext}"
);
}
}
#[test]
fn toml_has_no_call_query() {
assert!(call_query_for_extension("toml").is_none());
}
}