Design
Every answer carries
how it was reached.
A refactoring tool that resolves a reference wrongly edits the wrong code, and the result usually compiles. This one attaches the resolution rule to every answer and rewrites only the two strongest tiers.
The alternatives
Find-and-replace covers every language and parses none. It renames the string in a comment, a substring inside another identifier, and the same-named field on an unrelated type.
A language server covers one language and stops at its edge: not Helm charts, Terraform modules or stylesheets, and not a file that does not currently compile.
This tool
It builds one index across nineteen languages out of syntax alone. A named rule resolves each reference: the scope chain, an import, the enclosing package. Every answer carries which rule answered it.
It rewrites the references it resolved exactly or through an import, and lists the rest: weakly resolved sites, names appearing in strings, files that did not parse. It prints the diff, then the list.
fr capabilities prints the live language support matrix.
Defects found by running it on real repositories
Each of these lived in this tool, produced output that parsed, and turned up when somebody ran it over an existing codebase instead of a fixture.
A guard clause that moved code out from under its guard
Go's grammar puts a statement_list between a block and its
statements. Counting it as a statement made every block look like a block of
one. The check "is this if the last thing here?" then passed for an
if with code after it, and the rewrite hoisted that code out from
under the condition guarding it.
The result parses. No syntax check catches it. Over 250 files of helm's
pkg/services, 1,258 of 1,498 applications were wrong,
or 84%. The fix refuses those and applies the remaining 240.
A de Morgan rewrite that dropped its own brackets
!(a && b) is one operand; !a || !b is two, and
the brackets that held it together left with the negation. Inside another
operator, x && !(a && b) became
x && !a || !b: valid, and a different program.
References that were never found at all
A Go package is a directory: code in one file calls a function in the next with
no import and no qualifier. The index treated only Terraform that way, so
fr refs returned zero for symbols helm calls from the file
next door. Dead-code analysis reported 238 internal Go symbols as
dead where 47 are. A rename rewrote the definition and filed the call
sites it could not see under "unresolved".
That last one reported the gap instead of rewriting wrongly. It was still a bug, and it is fixed.
Where it fits
Keep rust-analyzer and gopls. They read types; this reads syntax, and syntax stops where a receiver's type would tell you which method you meant. At that point the tool says field-based and leaves the line alone.
What it has that they do not:
- One index over the whole repository, whatever the file is written in.
- Configuration as a first-class language: a Helm values key knows which templates read it, and a rename moves them.
- Answers that survive a file that does not compile, because it never needed the compiler.
- An explicit account of what it did not do, every time.