Over the past few weeks I have been using Claude Code, Anthropic’s AI coding assistant, to fix long-standing bugs in the GExperts Code Formatter. The results have been impressive – bugs that had been open for years were fixed in a matter of hours, including some that I had hesitated to tackle because of the formatter’s complexity.
Why the Formatter Is Hard to Fix
The GExperts Code Formatter engine is a single 3,000+ line Delphi unit that processes a token stream using a stack-based indentation system. Tokens like begin, if, procedure, and repeat push entries onto the stack, and their counterparts (end, then, until) pop them. The current indentation level is derived from the stack depth, with additional flags like FWrapIndent controlling whether continuation lines get extra indentation.
This architecture means that fixing one indentation bug can easily break something else. A change to how begin is handled affects every begin in every test case. The project has over 1,400 unit tests (238 test files × 6 formatter configurations), which is essential for catching regressions – but it also means you need deep understanding of the code to make safe changes.
The unit tests used to be run exclusively through the DUnit GUI runner. To enable Claude Code to build and run the tests on its own and check the results, I added – or rather had Claude Code add – a text-based test runner that writes results to a file. This way Claude Code can run the tests after every change, read the output, and immediately see whether all tests pass or which ones failed.
The Bugs
Here are some of the formatter bugs that Claude Code fixed:
Bug #165: Anonymous function indentation
When assigning an anonymous function with :=, the begin/end block was not indented correctly relative to the function keyword:
// Before (wrong):
OnCurrentDateTime :=
function: TDateTime
begin // should be indented to match 'function'
Result := EncodeDateTime(...);
end;
// After (correct):
OnCurrentDateTime :=
function: TDateTime
begin
Result := EncodeDateTime(...);
end;
The root cause was subtle: the formatter has a function CheckForFunctTypeDeclare that detects anonymous functions by checking the previous token. It looked for rtEquals (the = operator), but the := assignment operator is a different token type: rtAssignOper. So anonymous functions after := were misidentified as nested named procedures, which have different indentation rules. The fix required three coordinated changes across different methods.
Bug #473: Multiline repeat-until conditions
// Before (wrong): repeat until (AAA) or (BBB) or // should be indented as continuation (CCC); // After (correct): repeat until (AAA) or (BBB) or (CCC);
The while statement handled this correctly, but until did not. The line feed before until correctly cleared the continuation indent flag (so until itself would not be indented as a continuation). But the until handler never restored the flag for its own condition. One line of code fixed it.
Bug #468: “Never indent var declaration” broke if-conditions
A setting meant to prevent indentation of var declarations was accidentally applied to all wrapped lines, including and/or continuations in if conditions.
Bug #471: Generics with dot-qualified types
The formatter’s generic detection failed on expressions like TJSON.StringToObject<System.TJSONObject>(...) because the lookahead between < and > did not accept dot tokens. It misidentified the angle brackets as comparison operators, breaking the formatting.
How I Worked with Claude Code
My workflow for each bug was roughly:
- Describe the bug with a before/after code example
- Claude Code investigates – it reads the formatter source, traces the token flow, and identifies the root cause
- Create a test case first – a new input file goes into the test suite before any code changes
- Implement the fix – Claude Code makes the changes, often explaining why each change is needed
- Build and run all 1,400+ tests – verify the fix works and nothing else broke
- Build for multiple Delphi versions – the code must compile from Delphi 6 through RAD Studio 13
What surprised me most was Claude Code’s ability to trace through the stack-based formatter logic. For bug #165, it traced the full sequence of Push/Pop operations for anonymous functions vs. named procedures, identified exactly where the indentation level diverged, and determined that a simple one-line fix would not work – three coordinated changes were needed to properly handle the stack lifecycle. This kind of analysis would normally require setting breakpoints and stepping through the code in a debugger.
What Worked Well
- Root cause analysis. Claude Code consistently found the actual cause rather than applying surface-level patches. For bug #473, it could explain exactly why
whileworked butuntildid not – down to the specific line whereFWrapIndentwas set toFalse. - Test-first discipline. It followed the project’s convention of creating test cases before changing code, and knew to check all 1,400+ tests for regressions after every change.
- Multi-version awareness. It understood that the code must compile in Delphi 6 (no generics, no Unicode strings) and avoided using features that would break older compilers.
What Required Guidance
- Understanding the expected output. When a test failed, I sometimes had to verify that the new formatting was actually correct – the AI could determine what the formatter produces but not always whether that was the desired result.
- Complex interactions. For bug #165, the first attempt was too simple. The fix needed iteration: try an approach, examine the test output, understand why it did not work, and try again. This is normal for formatter work, but it meant I had to stay engaged rather than just accepting the first proposed fix.
Results
In about four weeks of part-time work, Claude Code helped fix over a dozen formatter bugs and implement several new features (ternary operator support, assignment alignment, independent compiler directive indentation, and more). Some of these bugs had been open for over a decade. The full list is in the changelog for version 1.3.27.
All fixes maintain backward compatibility across Delphi 6 through RAD Studio 13, pass over 1,400 unit tests, and follow the project’s existing code patterns. The AI did not just generate code – it understood the architecture.
I reviewed all the generated code carefully, and I was impressed by its quality. The fixes are clean, follow the existing coding conventions, and are well-structured. This stands in contrast to the widespread claim on the internet that AI produces unmaintainable spaghetti code. In my experience, the key factor is context: Claude Code reads the surrounding codebase and adapts to its style, rather than generating code in isolation.
In addition to fixing errors in the code formatter, Claude Code helped me keep the change log up to date, formulate commit messages, and even write blog posts like this one. Yes, I reviewed them and had to correct a few inaccurate statements, but overall, it took me much less time than usual to write the posts, and they are much better written than my own. You might now dismiss this as AI slop, and you might be right in some way, but on the other hand, I would possibly not have written this post because 1. I wouldn’t have had the time to fix the bugs and 2. neither the time to write such an extensive post about that.
Discussion about this in the corresponding post in the international Delphi Praxis forum.