gobby-code 1.0.0

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
//! Language registry with tree-sitter query definitions.
//! Ports 16 language specs from src/gobby/code_index/languages.py.

use tree_sitter::Language;

/// Specification for a single language's tree-sitter queries.
pub struct LanguageSpec {
    pub extensions: &'static [&'static str],
    pub symbol_query: &'static str,
    pub import_query: &'static str,
    pub call_query: &'static str,
}

// ── Query Definitions ──────────────────────────────────────────────────

const PYTHON: LanguageSpec = LanguageSpec {
    extensions: &[".py", ".pyi"],
    symbol_query: r#"
        (function_definition name: (identifier) @name) @definition.function
        (class_definition name: (identifier) @name) @definition.class
    "#,
    import_query: r#"
        (import_statement) @import
        (import_from_statement) @import
    "#,
    call_query: r#"
        (call function: (identifier) @name) @call
        (call function: (attribute attribute: (identifier) @name)) @call
    "#,
};

const JAVASCRIPT: LanguageSpec = LanguageSpec {
    extensions: &[".js", ".jsx", ".cjs", ".mjs"],
    symbol_query: r#"
        (function_declaration name: (identifier) @name) @definition.function
        (class_declaration name: (identifier) @name) @definition.class
        (method_definition name: (property_identifier) @name) @definition.method
        (export_statement declaration: (function_declaration name: (identifier) @name)) @definition.function
        (export_statement declaration: (class_declaration name: (identifier) @name)) @definition.class
        (lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
    "#,
    import_query: r#"
        (import_statement) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
        (call_expression function: (member_expression property: (property_identifier) @name)) @call
    "#,
};

const TYPESCRIPT: LanguageSpec = LanguageSpec {
    extensions: &[".ts", ".tsx"],
    symbol_query: r#"
        (function_declaration name: (identifier) @name) @definition.function
        (class_declaration name: (type_identifier) @name) @definition.class
        (method_definition name: (property_identifier) @name) @definition.method
        (interface_declaration name: (type_identifier) @name) @definition.type
        (type_alias_declaration name: (type_identifier) @name) @definition.type
        (enum_declaration name: (identifier) @name) @definition.type
        (lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
        (export_statement declaration: (function_declaration name: (identifier) @name)) @definition.function
        (export_statement declaration: (class_declaration name: (type_identifier) @name)) @definition.class
        (export_statement declaration: (interface_declaration name: (type_identifier) @name)) @definition.type
        (export_statement declaration: (type_alias_declaration name: (type_identifier) @name)) @definition.type
        (export_statement declaration: (enum_declaration name: (identifier) @name)) @definition.type
        (export_statement declaration: (lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function)))) @definition.function
    "#,
    import_query: r#"
        (import_statement) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
        (call_expression function: (member_expression property: (property_identifier) @name)) @call
    "#,
};

const GO: LanguageSpec = LanguageSpec {
    extensions: &[".go"],
    symbol_query: r#"
        (function_declaration name: (identifier) @name) @definition.function
        (method_declaration name: (field_identifier) @name) @definition.method
        (type_declaration (type_spec name: (type_identifier) @name)) @definition.type
    "#,
    import_query: r#"
        (import_declaration) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
        (call_expression function: (selector_expression field: (field_identifier) @name)) @call
    "#,
};

const RUST: LanguageSpec = LanguageSpec {
    extensions: &[".rs"],
    symbol_query: r#"
        (function_item name: (identifier) @name) @definition.function
        (struct_item name: (type_identifier) @name) @definition.class
        (enum_item name: (type_identifier) @name) @definition.type
        (trait_item name: (type_identifier) @name) @definition.type
        (impl_item type: (type_identifier) @name) @definition.class
        (type_item name: (type_identifier) @name) @definition.type
    "#,
    import_query: r#"
        (use_declaration) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
        (call_expression function: (scoped_identifier name: (identifier) @name)) @call
        (call_expression function: (field_expression field: (field_identifier) @name)) @call
    "#,
};

const JAVA: LanguageSpec = LanguageSpec {
    extensions: &[".java"],
    symbol_query: r#"
        (method_declaration name: (identifier) @name) @definition.method
        (class_declaration name: (identifier) @name) @definition.class
        (interface_declaration name: (identifier) @name) @definition.type
        (enum_declaration name: (identifier) @name) @definition.type
        (constructor_declaration name: (identifier) @name) @definition.method
    "#,
    import_query: r#"
        (import_declaration) @import
    "#,
    call_query: r#"
        (method_invocation name: (identifier) @name) @call
    "#,
};

const PHP: LanguageSpec = LanguageSpec {
    extensions: &[".php"],
    symbol_query: r#"
        (function_definition name: (name) @name) @definition.function
        (class_declaration name: (name) @name) @definition.class
        (method_declaration name: (name) @name) @definition.method
        (interface_declaration name: (name) @name) @definition.type
        (trait_declaration name: (name) @name) @definition.type
    "#,
    import_query: r#"
        (namespace_use_declaration) @import
    "#,
    call_query: r#"
        (function_call_expression function: (name) @name) @call
        (function_call_expression function: (qualified_name) @name) @call
        (scoped_call_expression scope: [(name) (qualified_name)] name: (name) @name) @call
        (member_call_expression name: (name) @name) @call
    "#,
};

const DART: LanguageSpec = LanguageSpec {
    extensions: &[".dart"],
    symbol_query: r#"
        (function_signature name: (identifier) @name) @definition.function
        (class_declaration name: (identifier) @name) @definition.class
        (method_signature (function_signature (identifier) @name)) @definition.method
        (enum_declaration name: (identifier) @name) @definition.type
    "#,
    import_query: r#"
        (import_or_export) @import
    "#,
    // Dart calls are extracted by parser.rs because this grammar models calls as
    // selector chains rather than a stable call-expression node.
    call_query: "",
};

const CSHARP: LanguageSpec = LanguageSpec {
    extensions: &[".cs"],
    symbol_query: r#"
        (method_declaration name: (identifier) @name) @definition.method
        (class_declaration name: (identifier) @name) @definition.class
        (interface_declaration name: (identifier) @name) @definition.type
        (struct_declaration name: (identifier) @name) @definition.type
        (enum_declaration name: (identifier) @name) @definition.type
        (constructor_declaration name: (identifier) @name) @definition.method
    "#,
    import_query: r#"
        (using_directive) @import
    "#,
    call_query: r#"
        (invocation_expression function: (identifier) @name) @call
        (invocation_expression function: (member_access_expression name: (identifier) @name)) @call
    "#,
};

const C_LANG: LanguageSpec = LanguageSpec {
    extensions: &[".c", ".h"],
    symbol_query: r#"
        (function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
        (struct_specifier name: (type_identifier) @name) @definition.type
        (enum_specifier name: (type_identifier) @name) @definition.type
        (type_definition declarator: (type_identifier) @name) @definition.type
    "#,
    import_query: r#"
        (preproc_include) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
    "#,
};

const CPP: LanguageSpec = LanguageSpec {
    extensions: &[".cpp", ".cc", ".cxx", ".hpp", ".hxx", ".hh"],
    symbol_query: r#"
        (function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
        (function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.function
        (class_specifier name: (type_identifier) @name) @definition.class
        (struct_specifier name: (type_identifier) @name) @definition.type
    "#,
    import_query: r#"
        (preproc_include) @import
    "#,
    call_query: r#"
        (call_expression function: (identifier) @name) @call
        (call_expression function: (field_expression field: (field_identifier) @name)) @call
    "#,
};

const ELIXIR: LanguageSpec = LanguageSpec {
    extensions: &[".ex", ".exs"],
    symbol_query: r#"
        (call target: (identifier) @_keyword (#any-of? @_keyword "def" "defp" "defmacro") (arguments (identifier) @name)) @definition.function
        (call target: (identifier) @_keyword (#any-of? @_keyword "defmodule") (arguments (alias) @name)) @definition.class
    "#,
    import_query: r#"
        (call target: (identifier) @_keyword (#any-of? @_keyword "import" "alias" "use" "require")) @import
    "#,
    call_query: r#"
        (call target: (identifier) @name) @call
        (call target: (dot right: (identifier) @name)) @call
    "#,
};

const RUBY: LanguageSpec = LanguageSpec {
    extensions: &[".rb", ".rake", ".gemspec"],
    symbol_query: r#"
        (method name: (identifier) @name) @definition.function
        (singleton_method name: (identifier) @name) @definition.function
        (class name: (constant) @name) @definition.class
        (module name: (constant) @name) @definition.class
    "#,
    import_query: r#"
        (call method: (identifier) @_m (#any-of? @_m "require" "require_relative" "load" "include" "extend" "prepend")) @import
    "#,
    call_query: r#"
        (call method: (identifier) @name) @call
    "#,
};

const KOTLIN: LanguageSpec = LanguageSpec {
    extensions: &[".kt", ".kts"],
    symbol_query: r#"
        (function_declaration name: (identifier) @name) @definition.function
        (class_declaration name: (identifier) @name) @definition.class
        (object_declaration name: (identifier) @name) @definition.class
    "#,
    import_query: r#"
        (import) @import
    "#,
    call_query: r#"
        (call_expression (identifier) @name) @call
        (call_expression (navigation_expression (identifier) (identifier) @name)) @call
    "#,
};

const YAML: LanguageSpec = LanguageSpec {
    extensions: &[".yaml", ".yml"],
    symbol_query: r#"
        (block_mapping_pair key: (_) @name) @definition.property
    "#,
    import_query: "",
    call_query: "",
};

const JSON_LANG: LanguageSpec = LanguageSpec {
    extensions: &[".json", ".jsonc"],
    symbol_query: r#"
        (pair key: (string (string_content) @name)) @definition.property
    "#,
    import_query: "",
    call_query: "",
};

const SWIFT: LanguageSpec = LanguageSpec {
    extensions: &[".swift"],
    symbol_query: r#"
        (function_declaration name: (simple_identifier) @name) @definition.function
        (class_declaration declaration_kind: "class" name: (type_identifier) @name) @definition.class
        (class_declaration declaration_kind: "actor" name: (type_identifier) @name) @definition.class
        (protocol_declaration name: (type_identifier) @name) @definition.type
        (class_declaration declaration_kind: "struct" name: (type_identifier) @name) @definition.type
        (class_declaration declaration_kind: "enum" name: (type_identifier) @name) @definition.type
    "#,
    import_query: r#"
        (import_declaration) @import
    "#,
    call_query: r#"
        (call_expression (simple_identifier) @name) @call
        (call_expression (navigation_expression suffix: (navigation_suffix suffix: (simple_identifier) @name))) @call
    "#,
};

// ── Registry ───────────────────────────────────────────────────────────

/// All supported languages and their specs.
const SPECS: &[(&str, &LanguageSpec)] = &[
    ("python", &PYTHON),
    ("javascript", &JAVASCRIPT),
    ("typescript", &TYPESCRIPT),
    ("go", &GO),
    ("rust", &RUST),
    ("java", &JAVA),
    ("php", &PHP),
    ("dart", &DART),
    ("csharp", &CSHARP),
    ("c", &C_LANG),
    ("cpp", &CPP),
    ("elixir", &ELIXIR),
    ("ruby", &RUBY),
    ("kotlin", &KOTLIN),
    ("swift", &SWIFT),
    ("yaml", &YAML),
    ("json", &JSON_LANG),
];

/// Detect language name from file extension.
pub fn detect_language(file_path: &str) -> Option<&'static str> {
    let path = std::path::Path::new(file_path);
    let ext = path
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy().to_lowercase()))?;

    for (name, spec) in SPECS {
        if spec.extensions.contains(&ext.as_str()) {
            return Some(name);
        }
    }
    None
}

/// Get the language spec for a given language name.
pub fn get_spec(lang: &str) -> Option<&'static LanguageSpec> {
    SPECS
        .iter()
        .find(|(name, _)| *name == lang)
        .map(|(_, s)| *s)
}

/// Get the tree-sitter Language object for a given language name.
pub fn get_ts_language(lang: &str) -> Option<Language> {
    let lang_fn = match lang {
        "python" => tree_sitter_python::LANGUAGE,
        "javascript" => tree_sitter_javascript::LANGUAGE,
        "typescript" => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
        "go" => tree_sitter_go::LANGUAGE,
        "rust" => tree_sitter_rust::LANGUAGE,
        "java" => tree_sitter_java::LANGUAGE,
        "c" => tree_sitter_c::LANGUAGE,
        "cpp" => tree_sitter_cpp::LANGUAGE,
        "csharp" => tree_sitter_c_sharp::LANGUAGE,
        "ruby" => tree_sitter_ruby::LANGUAGE,
        "php" => tree_sitter_php::LANGUAGE_PHP,
        "swift" => tree_sitter_swift::LANGUAGE,
        "kotlin" => tree_sitter_kotlin_ng::LANGUAGE,
        "dart" => tree_sitter_dart::LANGUAGE,
        "elixir" => tree_sitter_elixir::LANGUAGE,
        "json" => tree_sitter_json::LANGUAGE,
        "yaml" => tree_sitter_yaml::LANGUAGE,
        _ => return None,
    };
    Some(lang_fn.into())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn markdown_extensions_are_not_detected() {
        // Markdown is intentionally handled as content-only text, not AST.
        assert_eq!(detect_language("README.md"), None);
        assert_eq!(detect_language("docs/guide.markdown"), None);
    }

    #[test]
    fn javascript_extensions_still_detect() {
        assert_eq!(detect_language("src/app.js"), Some("javascript"));
        assert_eq!(detect_language("src/app.jsx"), Some("javascript"));
        assert_eq!(detect_language("src/app.cjs"), Some("javascript"));
        assert_eq!(detect_language("src/generated.mjs"), Some("javascript"));
    }
}