comindly 0.3.2

Deterministic, always-fresh, cross-repo code intelligence for coding agents — self-hosted, single binary.
Documentation
//! Java syntax adapter (tree-sitter-java). Packages come from the `package` declaration.

use tree_sitter::Node;

use super::{field_text, line, named_children, spring, text, LangSpec, RawContract, Target};
use crate::facts::{Import, ImportItem, RefKind, TypeSource, ValueExpr};
use crate::model::SymbolKind;

pub struct Java;

impl LangSpec for Java {
    fn receiver(&self) -> &'static str {
        "this"
    }

    fn package(&self, root: Node, src: &[u8]) -> Option<String> {
        let decl = named_children(root)
            .into_iter()
            .find(|n| n.kind() == "package_declaration")?;
        named_children(decl)
            .into_iter()
            .find(|n| matches!(n.kind(), "scoped_identifier" | "identifier"))
            .map(|n| text(n, src).to_string())
    }

    fn def(&self, node: Node, src: &[u8], _in_class: bool) -> Option<(SymbolKind, String)> {
        let kind = match node.kind() {
            "class_declaration" | "record_declaration" => SymbolKind::Class,
            "interface_declaration" | "annotation_type_declaration" => SymbolKind::Interface,
            "enum_declaration" => SymbolKind::Enum,
            "method_declaration" => SymbolKind::Method,
            "enum_constant" => SymbolKind::Constant,
            _ => return None,
        };
        Some((kind, field_text(node, "name", src)?.to_string()))
    }

    fn variable(&self, _node: Node, _src: &[u8]) -> Option<String> {
        None
    }

    fn contract(&self, node: Node, src: &[u8]) -> Option<RawContract> {
        if let Some(r) = spring::route(node, src) {
            return Some(r);
        }
        if node.kind() != "method_invocation" {
            return None;
        }
        let method = field_text(node, "name", src)?;
        if method == "uri" {
            let inner = node
                .child_by_field_name("object")
                .filter(|o| o.kind() == "method_invocation")?;
            let verb = spring::webclient_verb(field_text(inner, "name", src)?)?;
            return Some(RawContract::Op {
                receiver: vec![spring::WEBCLIENT.into()],
                method: verb,
                args: spring::args(node.child_by_field_name("arguments")?, src),
            });
        }
        if !spring::REST_TEMPLATE.iter().any(|(m, _)| *m == method) {
            return None;
        }
        Some(RawContract::Op {
            receiver: node
                .child_by_field_name("object")
                .and_then(|o| path(o, src))
                .unwrap_or_default(),
            method: method.to_string(),
            args: spring::args(node.child_by_field_name("arguments")?, src),
        })
    }

    fn value(&self, node: Node, src: &[u8]) -> Option<(String, ValueExpr)> {
        if !matches!(
            node.kind(),
            "field_declaration" | "local_variable_declaration" | "constant_declaration"
        ) {
            return None;
        }
        let d = node.child_by_field_name("declarator")?;
        Some((
            field_text(d, "name", src)?.to_string(),
            spring::value(d.child_by_field_name("value")?, src)?,
        ))
    }

    fn import(&self, node: Node, src: &[u8]) -> Option<Vec<Import>> {
        if node.kind() != "import_declaration" {
            return None;
        }
        let kids = named_children(node);
        let full = kids
            .iter()
            .find(|n| matches!(n.kind(), "scoped_identifier" | "identifier"))
            .map(|n| text(*n, src).to_string())?;
        let all = kids.iter().any(|n| n.kind() == "asterisk");
        let (module, items) = if all {
            (full, vec![ImportItem::All])
        } else {
            let (m, name) = full.rsplit_once('.')?;
            (
                m.to_string(),
                vec![ImportItem::Name {
                    name: name.to_string(),
                    alias: None,
                }],
            )
        };
        Some(vec![Import {
            module,
            level: 0,
            items,
            line: line(node),
            file: None,
        }])
    }

    fn reference(&self, node: Node, src: &[u8]) -> Option<(RefKind, Vec<String>)> {
        match node.kind() {
            "method_invocation" => {
                let name = field_text(node, "name", src)?.to_string();
                let mut p = match node.child_by_field_name("object") {
                    Some(o) => path(o, src)?,
                    None => Vec::new(),
                };
                p.push(name);
                Some((RefKind::Call, p))
            }
            "object_creation_expression" => Some((
                RefKind::New,
                type_path(node.child_by_field_name("type")?, src)?,
            )),
            _ => None,
        }
    }

    fn assignment(&self, node: Node, src: &[u8]) -> Option<(Target, TypeSource)> {
        let local = match node.kind() {
            "field_declaration" => false,
            "local_variable_declaration" => true,
            _ => return None,
        };
        let decl = node.child_by_field_name("declarator")?;
        let name = field_text(decl, "name", src)?.to_string();
        let target = if local {
            Target::Var(name)
        } else {
            Target::Attr(name)
        };
        let ty = node.child_by_field_name("type")?;
        if text(ty, src) != "var" {
            return Some((target, TypeSource::Annotation(type_path(ty, src)?)));
        }
        let v = decl.child_by_field_name("value")?;
        let source = match v.kind() {
            "object_creation_expression" => {
                TypeSource::Construct(type_path(v.child_by_field_name("type")?, src)?)
            }
            "method_invocation" => TypeSource::Construct(self.reference(v, src)?.1),
            _ => return None,
        };
        Some((target, source))
    }

    fn bases(&self, def: Node, src: &[u8]) -> Vec<(RefKind, Vec<String>)> {
        let mut out = Vec::new();
        let types = |n: Node| -> Vec<Vec<String>> {
            let list = named_children(n)
                .into_iter()
                .find(|c| c.kind() == "type_list")
                .map(named_children)
                .unwrap_or_else(|| named_children(n));
            list.into_iter().filter_map(|t| type_path(t, src)).collect()
        };
        for c in named_children(def) {
            let kind = match c.kind() {
                "superclass" | "extends_interfaces" => RefKind::Inherits,
                "super_interfaces" => RefKind::Implements,
                _ => continue,
            };
            out.extend(types(c).into_iter().map(|p| (kind, p)));
        }
        out
    }

    fn returns(&self, def: Node, src: &[u8]) -> Option<Vec<String>> {
        type_path(def.child_by_field_name("type")?, src)
    }

    fn params(&self, def: Node, src: &[u8]) -> Vec<(String, Vec<String>, bool)> {
        let Some(ps) = def.child_by_field_name("parameters") else {
            return Vec::new();
        };
        named_children(ps)
            .into_iter()
            .filter(|p| p.kind() == "formal_parameter")
            .filter_map(|p| {
                Some((
                    field_text(p, "name", src)?.to_string(),
                    type_path(p.child_by_field_name("type")?, src)?,
                    false,
                ))
            })
            .collect()
    }
}

fn path(node: Node, src: &[u8]) -> Option<Vec<String>> {
    match node.kind() {
        "identifier" | "type_identifier" => Some(vec![text(node, src).to_string()]),
        "this" => Some(vec!["this".into()]),
        "field_access" => {
            let mut p = path(node.child_by_field_name("object")?, src)?;
            p.push(field_text(node, "field", src)?.to_string());
            Some(p)
        }
        "scoped_identifier" => {
            let mut p = path(node.child_by_field_name("scope")?, src)?;
            p.push(field_text(node, "name", src)?.to_string());
            Some(p)
        }
        _ => None,
    }
}

fn type_path(node: Node, src: &[u8]) -> Option<Vec<String>> {
    match node.kind() {
        "type_identifier" | "identifier" => Some(vec![text(node, src).to_string()]),
        "scoped_type_identifier" => {
            let mut out = Vec::new();
            for c in named_children(node) {
                out.extend(type_path(c, src)?);
            }
            (!out.is_empty()).then_some(out)
        }
        "generic_type" => type_path(named_children(node).into_iter().next()?, src),
        _ => None,
    }
}