fix(refactoring): move_type und Package-Rename schreiben headless auf Disk (#79, #77) #95

Merged
hauschel.fred merged 3 commits from fix/79-move-type-cross-module into main 2026-09-07 20:56:09 +00:00
Collaborator

Closes #79

Schließt außerdem #77 (Forgejo wertet nur ein Keyword aus — #77 bitte beim Merge von Hand schließen).

Was war kaputt

  • #79: jdt_move_type rief change.perform() nackt auf. Headless landet die Änderung nur im ITextFileBufferManager-Puffer; ohne Editor speichert niemand. Im Testlauf gegen main: AssertionFailedException, Datei am alten Ort, keine einzige Referenz aktualisiert.
  • #77: Package-Rename mit renameSubpackages=true starb an NoClassDefFoundError: .../javaeditor/DocumentAdapter — und riss den ganzen Server mit, weil handleToolsCall nur Exception fing und der Error in den stdio-Reader-Thread durchschlug.

Was drin ist

  1. RefactoringSupport.performChange() (Basis-Commit, auf main rebased): applyForceSave(), Kinder eines CompositeChange einzeln ausführen, AssertionFailedException aus TextFileChange.releaseDocument() abfangen und den Edit von Hand auf die Datei anwenden, manueller Fallback für MoveCompilationUnitChange (dessen CompilationUnit.move() aktiviert org.eclipse.jdt.ui), refreshLocal danach. Dazu WORKSPACE_MUTATION_LOCK, das alle mutierenden Refactorings serialisiert, plus „⚠️ SEQUENTIAL ONLY" in den betroffenen Tool-Beschreibungen. Alle LTK-Rename-Pfade laufen darüber; der AST-Fallback in RenameRefactoring schreibt ohnehin direkt per ICompilationUnit.save().
  2. tests/refactoring-test.sh: End-to-End über stdio gegen das gebaute Produkt. Importiert fixture-parent und fixture-external als zwei getrennte Projekte und prüft danach die Festplatte, nicht die Tool-Antwort — genau dort log beide Bugs.
  3. Drei weitere Defekte, die dieser Test erst sichtbar gemacht hat (#77):
    • jdt.ui-Bufferprovider vor jedem Refactoring von DefaultWorkingCopyOwner.PRIMARY abhängen,
    • handleToolsCall fängt Throwable statt Exception (ein Error beendete sonst die Session),
    • nach dem manuellen Move die Working-Copy der Quelldatei verwerfen, sonst bleibt ein Phantom-Element im Java-Model und jedes spätere Refactoring im selben Package scheitert mit „Resource ... does not exist".

Verifikation (lokal, gegen das gebaute Produkt)

Lauf Ergebnis
tests/refactoring-test.sh gegen main-Produkt 0 passed, 2 failed (move: AssertionFailedException, nichts auf Disk; Rename: Server stirbt an NoClassDefFoundError)
tests/refactoring-test.sh gegen diesen Branch 2 passed, 0 failed
tests/smoke-test.sh 9 passed, 0 failed
tests/lifecycle-test.sh 5 passed, 0 failed
mvn clean package (Maven 3.9.14, Tycho) BUILD SUCCESS

Der Move-Test prüft: Datei am neuen Ort auf Disk, alte Datei weg, package-Deklaration angepasst, Import in fixture-app (cross-module) und in fixture-external (cross-project) aktualisiert. Der Rename-Test zusätzlich: Subpackage internal mitgewandert, kein .java mehr im alten Package, keine org.fixture.core-Referenz mehr in beiden Projekten.

Fremdbug-Notiz (Eclipse JDT, headless)

JavaPlugin installiert beim Aktivieren des Bundles org.eclipse.jdt.ui einen editor-gebundenen Bufferprovider auf DefaultWorkingCopyOwner.PRIMARY. Headless ist das eine Zeitbombe: jedes openBuffer() danach wirft NoClassDefFoundError. Wir hängen den Provider vor jedem Refactoring ab. Aktiviert sich das UI-Bundle später erneut und ruft ein Nicht-Refactoring-Tool danach openBuffer() auf, kann derselbe Fehler wiederkommen — der Server stirbt daran seit diesem PR aber nicht mehr.

🤖 Generated with Claude Code

https://claude.ai/code/session_018q6miiQHwQYFgZBy71aUhL

Closes #79 Schließt außerdem #77 (Forgejo wertet nur ein Keyword aus — #77 bitte beim Merge von Hand schließen). ## Was war kaputt - **#79:** `jdt_move_type` rief `change.perform()` nackt auf. Headless landet die Änderung nur im `ITextFileBufferManager`-Puffer; ohne Editor speichert niemand. Im Testlauf gegen `main`: `AssertionFailedException`, Datei am alten Ort, keine einzige Referenz aktualisiert. - **#77:** Package-Rename mit `renameSubpackages=true` starb an `NoClassDefFoundError: .../javaeditor/DocumentAdapter` — und riss den ganzen Server mit, weil `handleToolsCall` nur `Exception` fing und der `Error` in den stdio-Reader-Thread durchschlug. ## Was drin ist 1. `RefactoringSupport.performChange()` (Basis-Commit, auf `main` rebased): `applyForceSave()`, Kinder eines `CompositeChange` einzeln ausführen, `AssertionFailedException` aus `TextFileChange.releaseDocument()` abfangen und den Edit von Hand auf die Datei anwenden, manueller Fallback für `MoveCompilationUnitChange` (dessen `CompilationUnit.move()` aktiviert `org.eclipse.jdt.ui`), `refreshLocal` danach. Dazu `WORKSPACE_MUTATION_LOCK`, das alle mutierenden Refactorings serialisiert, plus „⚠️ SEQUENTIAL ONLY" in den betroffenen Tool-Beschreibungen. Alle LTK-Rename-Pfade laufen darüber; der AST-Fallback in `RenameRefactoring` schreibt ohnehin direkt per `ICompilationUnit.save()`. 2. `tests/refactoring-test.sh`: End-to-End über stdio gegen das gebaute Produkt. Importiert `fixture-parent` und `fixture-external` als **zwei getrennte Projekte** und prüft danach die **Festplatte**, nicht die Tool-Antwort — genau dort log beide Bugs. 3. Drei weitere Defekte, die dieser Test erst sichtbar gemacht hat (#77): - jdt.ui-Bufferprovider vor jedem Refactoring von `DefaultWorkingCopyOwner.PRIMARY` abhängen, - `handleToolsCall` fängt `Throwable` statt `Exception` (ein `Error` beendete sonst die Session), - nach dem manuellen Move die Working-Copy der Quelldatei verwerfen, sonst bleibt ein Phantom-Element im Java-Model und jedes spätere Refactoring im selben Package scheitert mit „Resource ... does not exist". ## Verifikation (lokal, gegen das gebaute Produkt) | Lauf | Ergebnis | |---|---| | `tests/refactoring-test.sh` gegen `main`-Produkt | 0 passed, **2 failed** (move: `AssertionFailedException`, nichts auf Disk; Rename: Server stirbt an `NoClassDefFoundError`) | | `tests/refactoring-test.sh` gegen diesen Branch | **2 passed**, 0 failed | | `tests/smoke-test.sh` | 9 passed, 0 failed | | `tests/lifecycle-test.sh` | 5 passed, 0 failed | | `mvn clean package` (Maven 3.9.14, Tycho) | BUILD SUCCESS | Der Move-Test prüft: Datei am neuen Ort auf Disk, alte Datei weg, `package`-Deklaration angepasst, Import in `fixture-app` (cross-module) **und** in `fixture-external` (cross-project) aktualisiert. Der Rename-Test zusätzlich: Subpackage `internal` mitgewandert, kein `.java` mehr im alten Package, keine `org.fixture.core`-Referenz mehr in beiden Projekten. ## Fremdbug-Notiz (Eclipse JDT, headless) `JavaPlugin` installiert beim Aktivieren des Bundles `org.eclipse.jdt.ui` einen editor-gebundenen Bufferprovider auf `DefaultWorkingCopyOwner.PRIMARY`. Headless ist das eine Zeitbombe: jedes `openBuffer()` danach wirft `NoClassDefFoundError`. Wir hängen den Provider vor jedem Refactoring ab. Aktiviert sich das UI-Bundle später erneut und ruft ein Nicht-Refactoring-Tool danach `openBuffer()` auf, kann derselbe Fehler wiederkommen — der Server stirbt daran seit diesem PR aber nicht mehr. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018q6miiQHwQYFgZBy71aUhL
WIP / UNVERIFIED: jdt_move_type still does not reliably write all changes
to disk (see #79: 1 of 85 files written).

- move_type: switch from MoveDescriptor to internal JavaMoveProcessor API
  for full workspace-scope reference search; add targetProject param for
  cross-module moves; add heartbeat to keep MCP connection alive.
- RefactoringSupport.performChange(): global WORKSPACE_MUTATION_LOCK,
  headless AssertionFailedException handling, manual-move + manual-edit
  fallbacks (reflection-based) for MoveCompilationUnitChange.
- Route all refactorings through performChange; add SEQUENTIAL ONLY warnings.
- MANIFEST: add org.eclipse.core.filebuffers.

Do NOT merge until move_type is verified end-to-end against fixture-external.

Co-Authored-By: Claude <noreply@anthropic.com>
Both defects (#79, #77) reported success or a plausible error in the tool
response while the workspace on disk was untouched, so a response-level test
would not have caught them. The new script imports fixture-parent and
fixture-external as two separate projects — as a real session does — runs
jdt_move_type and a package rename with renameSubpackages=true, and asserts
against the filesystem: moved file present, original gone, package declaration
rewritten, and the cross-module and cross-project references updated.

Request ids are kept in a file: rpc() runs inside command substitution, and a
counter variable incremented in a subshell hands out the same id twice, which
makes id-based response matching return a stale response.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6miiQHwQYFgZBy71aUhL
Three defects surfaced by the new end-to-end test, all in the same path:

- RenamePackageProcessor.checkForMainAndNativeMethods() opens every
  compilation unit of the package. Once the org.eclipse.jdt.ui bundle is
  active, its JavaPlugin has installed an editor-backed buffer provider on
  DefaultWorkingCopyOwner.PRIMARY, and opening the buffer fails headless with
  NoClassDefFoundError: .../javaeditor/DocumentAdapter. RefactoringSupport
  now detaches that provider at the start of every refactoring, so JDT falls
  back to its own file-based buffers.
- The Error above killed the whole server: handleToolsCall caught Exception,
  so it escaped into the stdio reader thread and ended the session. It now
  catches Throwable and answers with isError instead.
- After the manual move fallback, the source compilation unit stayed in the
  Java model as a working copy without a resource. Every later operation on
  that package then failed with "Resource ... does not exist" — a package
  rename after a move could not run at all. The working copy is now discarded
  before the file is deleted.

Verified end-to-end against the built product with tests/refactoring-test.sh
(both tests red on main, green here), plus smoke and lifecycle tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6miiQHwQYFgZBy71aUhL
Commenting is not possible because the repository is archived.
No description provided.