Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/xtend-to-java/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ the `xtend-gen/` output — what Xtend compiled to Java before the migration.
> No exceptions regardless of file size. A 5-line file with one template expression can have
> surprising whitespace behavior. See [`workflow/overview.md`](./workflow/overview.md) for the full rationale.
>
> The ground truth is the `xtend-gen/` compiled output. **`xtend-gen/` is gitignored and only exists after a build** — if it is absent you must still obtain a ground truth before writing Java: either (a) build the module to generate `xtend-gen/`, or (b) if a pre-converted reference branch exists, read its `.java` (it was produced from `xtend-gen/` and encodes the same behavior). **Never write Java from the `.xtend` source alone.**
> The ground truth is the `xtend-gen/` compiled output. **`xtend-gen/` is gitignored and only exists after a build**, so a fresh checkout has none. **For every migration, build the module off the latest integration branch with `-T 3C` first** to (re)generate `xtend-gen/` — that freshly built output is the **authoritative, current** ground truth (and the first build gate). A pre-converted reference branch's `.java`, if one exists, is a **four-eyes cross-check** that may be **stale** relative to the current branch — read it and reconcile every divergence, but it is NOT a substitute for the fresh `xtend-gen/`. Only if a build is genuinely impossible may the reference `.java` serve as the fallback ground truth. **Never write Java from the `.xtend` source alone.**
>
> Separately, if the class extends/overrides a **generated supertype** (`Abstract…Module`, `…Setup`, runtime/UI bases), read that supertype under `src-gen/` (committed, present without a build) — it is the authoritative source for inherited constructor signatures, real `@Override` targets, and Xtend-inferred return types. See [`workflow/overview.md`](./workflow/overview.md) §3b-also.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ When Xtend is fully removed from a module, update these files.

| File | Change |
|------|--------|
| **META-INF/MANIFEST.MF** | Remove `org.eclipse.xtend.lib` and `org.eclipse.xtext.xbase.lib` from `Require-Bundle` (keep `xtext.xbase.lib` if the module still uses Xtext runtime classes like `XbaseTypeComputer`) |
| **META-INF/MANIFEST.MF** | Remove `org.eclipse.xtend.lib` / `org.eclipse.xtext.xbase.lib` from `Require-Bundle` **only after** grepping BOTH `src` and `src-gen` for any reference — imports plus `StringConcatenation`/`CollectionLiterals`/`Conversions`/`Exceptions`/`ObjectExtensions`/`IterableExtensions`/`Procedures`/`Functions`/`Pair`. Remove **iff zero references**; keep it if any remain (e.g. `src-gen` still uses it). At zero refs it is guaranteed-safe: `Require-Bundle` isn't re-exported, and with no bytecode reference transitive availability is irrelevant. |
| **build.properties** | Remove `xtend-gen/` from `source..` entries |
| **.classpath** | Remove `<classpathentry kind="src" path="xtend-gen">` (including any nested `<attributes>`) |
| **.project** | Remove `org.eclipse.xtext.ui.shared.xtextBuilder` from `<buildSpec>` and `org.eclipse.xtext.ui.shared.xtextNature` from `<natures>` |
Expand Down
18 changes: 16 additions & 2 deletions .agents/skills/xtend-to-java/workflow/known-pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,23 @@ Consolidated table of common mistakes and their fixes. Review before and after e
| **`--fail-at-end` hides failures** | Check the final BUILD line, not intermediate output. |
| **Dispatch method names** | Keep underscores. Suppress with `@SuppressWarnings`. Never rename. |
| **IDE save actions** | "Organize Imports" in Eclipse may trigger save actions that auto-convert string concatenation to text blocks. Auto-conversion produces wrong results. Review `git diff` after any IDE action. |
| **Import order** | The project uses `org.*`/`java.*` first, blank line, then `com.*` second. Wrong order causes diff churn. |
| **Import order** | Two groups (blank line between, no wildcards): framework (`java.*`/`javax.*`/`org.*`) first, then `com.*` alphabetically — so `com.avaloq.*` precedes `com.google.*`. Not enforced by checkstyle (no `ImportOrder` module); wrong order is diff churn only. |
| **Eclipse CLI formatter** | Does NOT organize imports — only code formatting. Import order must be correct from the start. |
| **IllegalCatch** | Do not suppress `PMD.AvoidCatchingGenericException`. Replace `catch (Exception e)` with specific exceptions. |
| **IllegalCatch / IllegalThrows** | checkstyle `IllegalCatch` bans `catch (Exception/Throwable/RuntimeException)` — use the specific type, or a multi-catch (`catch (BadLocationException \| TemplateException e)`) re-thrown as `new IllegalStateException(e)`. `IllegalThrows` bans `throws Throwable/RuntimeException/Error` (plain `throws Exception` IS allowed — acceptable on a `@Test` when the JUnit-invoked API declares it; otherwise narrow to the actual checked type). Don't suppress `PMD.AvoidCatchingGenericException`. |
| **Rollback** | If already pushed: `git revert -m 1 <sha>`. If local only: `git reset --hard HEAD~1`. After reverting, build and test. |
| **`ByteArrayInputStream.close()`** | It's a no-op. Safe to remove entirely. |
| **`==` in Xtend** | Xtend `==` is `.equals()`, not identity. Convert to `.equals()` or `Objects.equals()`. Only `===`/`!==` are identity. |

## Learnings from the per-module migration campaign

| Pitfall | What to do |
|---------|------------|
| **Constant fields → `static final`** | A `final` field initialized to a literal (String/number/boolean) MUST be `private static final` with an UPPER_SNAKE name, or PMD `FinalFieldCouldBeStatic` fails the build. Fields initialized via `mock(...)` / `new X()` stay instance `private final`. (`CHECKSTYLE:CONSTANTS-OFF` suppresses the *checkstyle* constants check, NOT this PMD rule.) |
| **`@RegisterExtension` fields must be `public`** | JUnit 5 programmatic-extension fields must NOT be private — a private one fails at *test runtime* (invisible to a `-DskipTests` compile). Declare `public` and bracket with `// CHECKSTYLE:CHECK-OFF Visibility MethodRules cannot be private` / `// CHECKSTYLE:CHECK-ON Visibility` (matches `AbstractTest`). |
| **Active annotations are NOT leaf conversions** | An Xtend `@Active(...) annotation X {}` backed by a `TransformationParticipant` (e.g. `Tag`/`TagCompilationParticipant`, which compile-time-injects `@Tag` field initializers) must NOT be renamed to `.java` as a routine leaf. The macro is an Xtend-compiler mechanism; its consumers may live **downstream / outside this repo**, so local CI (esp. `-DskipTests`) cannot validate that it still fires. **Detect** (`@Active`, the `annotation` keyword, `TransformationParticipant`, `xtend.lib.macro`) during scope and **defer** to a dedicated active-annotation pass with downstream validation. |
| **#1274 is a parity oracle, not a compliance oracle** | The complete-migration reference branch is invaluable for behaviour parity, but it predates current gates and itself violates some (observed: `String.format`, `catch (Exception)`, `throws Exception`+suppression, `private @RegisterExtension`, redundant `@SuppressWarnings("unchecked")` under `@SafeVarargs`). When it conflicts with a checklist rule, **diverge to the compliant form** and note the improvement — never copy the violation. |
| **Method-count parity (sanity heuristic)** | Quick check: `def`/`override` count in the `.xtend` vs method count in the `.java` should match 1:1 EXCEPT documented transforms — `dispatch` (→ multiple `_method`s), `@Data`/`@Accessors` (→ generated members), field-initializer `=> [...]` stubbing (→ a `@BeforeEach`/helper method), active annotations, lambdas. Investigate any UNEXPLAINED delta. Not a hard gate. |
| **Empty method body needs a comment** | PMD `UncommentedEmptyMethodBody` fires on a bare `{}`. Keep a comment (e.g. the original `// TODO …`) in genuinely-empty bodies. |
| **Text block ≠ inline-`'''` exactly** | Java text blocks strip trailing whitespace on each content line and add a trailing newline before the closing `"""`; an inline-`'''` Xtend template preserves trailing spaces and omits the trailing newline. For string OUTPUT, match `xtend-gen` exactly (`\s` / `\` escapes). When the delta is provably behaviour-inert (e.g. a parser "no syntax errors" assertion) a clean text block is fine — say so in the commit/PR. |
| **`final`-on-locals consistency** | Not an enforced gate, but keep locals consistently `final` within a file; mixed `final`/non-`final` siblings is a readability nit only. |
| **Don't carry `xbase.lib` types into migrated Java** | The `->` pair operator compiles to `org.eclipse.xtext.xbase.lib.Pair` — an Xtend runtime type. Don't keep it in the `.java`: replace with a small `private record` (named fields, accepts `null`) or `java.util.Map.entry` — but `Map.entry` **rejects null** keys/values, so use a record when nulls are possible. Bonus: a non-generic record vararg drops the `@SafeVarargs` a `Pair<…>` vararg required. Migrating off Xtend means migrating off `xbase.lib` too. |
2 changes: 0 additions & 2 deletions .agents/skills/xtend-to-java/workflow/multi-file-batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ refactor: migrate Xtend to Java - <plugin name>
```
Example: `refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core.test`

**Never add co-authoring trailers** (`Co-authored-by`, `Signed-off-by`, etc.) to migration commits.

### Rollback strategy

If a batch fails any validation gate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Use this when converting a single `.xtend` to its `.java` counterpart.
## Steps

1. **Read the Xtend source in full.** Note its package, imports, and the constructs it uses.
2. **Read the `xtend-gen/` output in full.** Same package, same class name, `.java` extension. This is mandatory — no exceptions. See [`workflow/overview.md`](./overview.md) Step 3.
2. **Build the module off the latest integration branch (`-T 3C`) to (re)generate `xtend-gen/`, then read that output in full.** Same package, same class name, `.java` extension. The freshly built `xtend-gen/` is the authoritative, current ground truth (and first build gate). Mandatory — no exceptions. If a pre-converted reference branch exists, also read its `.java` as a four-eyes cross-check (may be stale; reconcile divergences). See [`workflow/overview.md`](./overview.md) Step 3.
3. **Identify which rule files apply.** Template-heavy? Extension-heavy? Simple POJO? Load only those.
4. **Apply the binding decisions.** [`rules/00-decisions.md`](../rules/00-decisions.md) — Java 21, 4-tier string building, no `var`, explicit visibility, Java stdlib.
5. **Apply rules in order.** Walk [`rules/01-...`](../rules/01-imports-and-package.md) through [`rules/09-...`](../rules/09-misc-syntax.md) for the constructs you identified.
Expand Down
23 changes: 18 additions & 5 deletions .agents/skills/xtend-to-java/workflow/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,24 @@ Do not write the Java file first and then vet it — read the references first,
>
> **There is no file small enough to skip this step.**
>
> **If `xtend-gen/` is not present:** it is gitignored and only exists after a build, so a fresh
> checkout often has none. You may NOT skip the ground truth — obtain it one of two ways:
> (a) build the module to generate `xtend-gen/`, or (b) if a pre-converted reference branch exists,
> read its `.java` for this file (it was produced from `xtend-gen/` and encodes the same behavior).
> Wherever this document says "read `xtend-gen/`", that reference `.java` is the equivalent ground truth.
> **Always build for a CURRENT ground truth — do not rely on a stale reference alone.**
> `xtend-gen/` is gitignored and only exists after a build, so a fresh checkout has none.
> For **every** migration, build the module off the **latest integration branch** with `-T 3C`
> first to (re)generate `xtend-gen/`:
>
> ```bash
> mvn -f ./ddk-parent/pom.xml -pl :<module> -am -DskipTests -T 3C compile --batch-mode
> ```
>
> The freshly built `xtend-gen/` is the **authoritative** ground truth: it is the Xtend compiler's
> output for the `.xtend` *as it exists on the current branch*, so it is always up to date and
> Java-authoritative. This compile is also the first build gate.
>
> A pre-converted **reference branch's `.java`** (if one exists) is a valuable **four-eyes
> cross-check** — read it too and reconcile every divergence (either side may be wrong) — but it
> may be **stale** relative to the current branch, so it is NOT a substitute for the freshly built
> `xtend-gen/`. Only if a build is genuinely impossible does the reference `.java` become the
> fallback ground truth, and then note the staleness risk.

### 3a. Read the Xtend source — understand intent and structure

Expand Down
11 changes: 10 additions & 1 deletion .agents/skills/xtend-to-java/workflow/validation-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Run through this list before declaring a conversion done. Every item is a hard g

---

## Quality rules (all 30 must pass)
## Quality rules (all 34 must pass)

### Javadoc and comments

Expand Down Expand Up @@ -86,6 +86,15 @@ Run through this list before declaring a conversion done. Every item is a hard g
| 22 | Commit format | `refactor: migrate Xtend to Java - <plugin name>`. Single commit. |
| 28 | Infrastructure cleanup | Remove Xtend from MANIFEST.MF, build.properties, .classpath, .project; delete `xtend-gen/` directory (when module fully off Xtend). |

### Migration-campaign gates (learned)

| # | Rule | Requirement |
|---|------|-------------|
| 31 | FinalFieldCouldBeStatic | A `final` field with a literal initializer → `private static final` UPPER_SNAKE (PMD). Fields from `mock(...)`/`new X()` stay instance `private final`. |
| 32 | `@RegisterExtension` visibility | Declare `public` + bracket with `// CHECKSTYLE:CHECK-OFF Visibility MethodRules cannot be private`; NEVER private (fails at test runtime, invisible to `-DskipTests`). |
| 33 | Active annotations | Detect `@Active` / `annotation`+`TransformationParticipant`; do NOT leaf-convert — defer (consumers may be downstream; local CI can't validate the macro). |
| 34 | Method-count parity | `def`/`override` count ≈ `.java` method count, modulo documented transforms (dispatch, `@Data`, field-init→`@BeforeEach`, active annotations). Investigate unexplained deltas. |

---

## Per-file checklist (quick pass/fail)
Expand Down