grammar-inference-engine/docs/adr/0002-language-agnostic-method-extraction.md
tobjend e23922a1b7
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: adaptive multi-assignment clustering; add ADRs 1-10
- Multi-assignment clustering (no greedy 'used' set)
- Adaptive ngram fallback (shrink when (other) > 60%)
- Add docs/adr/ with 10 architecture decision records
- Fix ADR 1 (query modification description)
- Fix ADR 3 (multi-assignment + adaptive shrink)
- Fix ADR 5 (import sort order clarification)
- Fix ADR 6 (remove Kotlin call_suffix references)
- New ADR 9 (adaptive clustering rationale)
- New ADR 10 (universal package mapping via relpath)
2026-07-03 22:58:09 +02:00

2.3 KiB

2. Language-agnostic method extraction via child_by_field_name("body")

Date: 2026-07-03

Status: Accepted

Context

To analyze method-level behavioral conventions, we must extract the body of each function/method from the AST. The standard tree-sitter approach is node.child_by_field_name("body"), but this named field is not universal across all language grammars.

We need one code path that works for all 10 supported languages without per-language branches.

Decision

Use node.child_by_field_name("body") as the primary extraction method. When it returns None, fall back to scanning the node's children for any child with a type containing body, block, or compound_statement.

Parent nodes are further filtered to only include nodes whose type contains function or method — avoiding class bodies, loop bodies, and conditional blocks.

This logic lives in _find_method_bodies() in code.py:

def walk(node):
    body = node.child_by_field_name("body")
    if not body:
        for child in node.children:
            ctype = child.type.lower()
            if "body" in ctype or "block" in ctype or ctype == "compound_statement":
                body = child; break
    if body:
        ptype = node.type.lower()
        if "function" in ptype or "method" in ptype:
            bodies.append(body)
    for child in node.children: walk(child)

Consequences

Positive:

  • Works for 9/10 grammars via child_by_field_name("body") alone (Python, Go, Rust, JS, TS, Ruby, Java, C, C++).
  • Kotlin fallback works because the fwcd Kotlin grammar uses function_body as a child node type.
  • Zero per-language case analysis — just pattern matching on type strings.

Negative:

  • Fallback relies on string matching ("body" in ctype) which could produce false positives if future grammar versions introduce new body-like types.
  • C/C++ function_definition uses declarator field for the function name, not name — affects name extraction but not body extraction.

Alternatives Considered

  • Grammar-specific field names: Map each language to its body field name. Rejected because it creates a maintenance burden and violates the zero-adapters constraint.
  • Top-down sibling traversal: Walk from node start to next sibling to find the body. Fragile across grammars with different compound statement structures.