55 lines
2.8 KiB
Markdown
55 lines
2.8 KiB
Markdown
|
|
# 6. Argument pattern extraction via AST node classification
|
||
|
|
|
||
|
|
**Date:** 2026-07-03
|
||
|
|
|
||
|
|
**Status:** Accepted
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
A behavioral token like `assertEquals` tells the LLM that the function is called, but not *how*. Two codebases both use `assertEquals` — one writes `assertEquals(expected, actual)` and the other writes `assertEquals(actual, expected)` with swapped argument order. An LLM guessing the wrong order writes broken tests.
|
||
|
|
|
||
|
|
The highlights.scm captures tell us *that* a function is called. We need the argument *structure* — number of arguments, their types, and the common patterns.
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
For each behavioral capture node, walk up to its parent `call_expression` (or equivalent), find the argument list node, and classify each argument by structural role.
|
||
|
|
|
||
|
|
Argument classification is language-agnostic:
|
||
|
|
|
||
|
|
| Classification | Matches |
|
||
|
|
|---|---|
|
||
|
|
| `lit` | string, number, boolean, null |
|
||
|
|
| `var` | identifiers, names |
|
||
|
|
| `call` | nested call expressions, method invocations |
|
||
|
|
| `lambda` | lambda expressions, blocks, do-blocks |
|
||
|
|
| `kwarg` | keyword/named arguments |
|
||
|
|
| `expr` | binary/unary/ternary/operator expressions |
|
||
|
|
| `template` | string interpolation, template literals |
|
||
|
|
| `other` | anything else (fallback) |
|
||
|
|
|
||
|
|
Argument list node detection uses a tiered approach:
|
||
|
|
1. `child_by_field_name("arguments")` — works for Python, JS, TS, Java, Go, Ruby, Rust.
|
||
|
|
2. Fallback: scan children for `argument_list`, `arguments`, `template_string` (JS tagged templates).
|
||
|
|
|
||
|
|
The argument iterator is a simple generic traversal: yield all named children of the arglist node. No per-language special cases. This works for positional args, keyword args, lambdas inside argument lists, and template expressions.
|
||
|
|
|
||
|
|
Results are aggregated per cluster into a summary showing min/max/common arg counts and the top argument-type patterns.
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
**Positive:**
|
||
|
|
- Reveals argument ordering conventions: `assertEquals: n=2 [lit,var]` means expected-first.
|
||
|
|
- Reveals calling convention variance: `verify: n=0 [] | n=1 [lambda] | n=1 [var]` means three styles coexist.
|
||
|
|
- Zero per-language branches — generic tiered detection and iteration handles all 10 grammars.
|
||
|
|
|
||
|
|
**Negative:**
|
||
|
|
- `kwarg` detection only covers named arguments, not default values or spread operators.
|
||
|
|
- Nested destructuring patterns fall into `other` bucket — no granularity for complex argument shapes.
|
||
|
|
- `other` is a catch-all that can hide meaningful distinctions we haven't classified yet.
|
||
|
|
|
||
|
|
## Alternatives Considered
|
||
|
|
|
||
|
|
- **Extract raw argument text**: Language-agnostic but fragile — variable names change per test, producing high variance and low signal.
|
||
|
|
- **No argument extraction**: The LLM sees `assertEquals` but doesn't know argument order. Leads to wrong code.
|
||
|
|
- **Per-language argument extractors**: Would be more precise but violate the zero-adapters constraint.
|