Skip to content
Draft
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
13 changes: 13 additions & 0 deletions .agents/skills/xtend-to-java/rules/04-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ for (final String k : properties.keySet()) {
return builder;
```

**Tier 4 does not apply to every control-flow template.** A template whose `xtend-gen/` chain contains
`newLineIfNotEmpty()` after a dynamic value, a two-arg `append(value, indent)` of a possibly multi-line
value, or `appendImmediate(...)` **stays on the reference `StringConcatenation` chain** β€” none of the
three has a `StringBuilder`/text-block equivalent. Convert to `StringBuilder` (or a text block) only
when every `newLineIfNotEmpty()` in the chain follows a static literal tail (Β§4.8, rule 35).

## 4.2 Template control flow patterns

- `Β«IF conditionΒ»...Β«ENDIFΒ»` β†’ `if (condition) { builder.append(...); }`
Expand Down Expand Up @@ -243,6 +249,13 @@ these source-verified semantics decide what is safe:
- **Leave `appendImmediate(sep, indent)` loops untouched** β€” the separator insertion inspects
trailing segments; keep its surrounding append sequence as-is.

**When the chain stays as-is:** see the Tier 4 caveat above β€” `newLineIfNotEmpty()` after a dynamic value, two-arg `append(value, indent)` of a possibly multi-line value, or `appendImmediate(...)` keep the reference `StringConcatenation` chain (rule 35).

**Null values:** `StringConcatenation.append(null)` appends nothing, while `"%s".formatted(null)`
yields `"null"`. An interpolation of a nullable value converted to `.formatted()` must therefore wrap
it: `"…%s…".formatted(Strings.emptyIfNull(value))` (`org.eclipse.xtext.util.Strings`) β€” precedent
`FormatJvmModelInferrer.inferClass`.

**Verification:** each coalesced run must be proven byte-identical with an executable old-vs-new
harness over an input battery (empty / single-line / multi-line / newline-terminated / `%`-bearing
values).
6 changes: 5 additions & 1 deletion .agents/skills/xtend-to-java/rules/09-misc-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ public class MyFormatter extends AbstractFormatter {
Rules:
- **Keep the `_` prefix** β€” the Xtext runtime resolves dispatch by name.
- **Suppress at class level**: `@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter"})`
- Order `instanceof` checks from most specific to least specific.
- **Take the case ORDER from `xtend-gen/`, never from source order.** Xtend sorts dispatch cases by
type specificity, not by declaration order β€” e.g. `OperationCall` and `TypeSelectExpression` extend
both `Expression` and `FeatureCall`, so their relative position is not what the `.xtend` suggests.
Order `instanceof` checks most specific first, exactly as `xtend-gen/` did.
- A terminal `throw` after an exhaustive `!= null` / `== null` pair is unreachable β€” drop it.
- If the original `dispatch` had `override`, add `@Override` to the **dispatcher**, not the `_` methods.
- The dispatcher parameter type should be the common supertype (often `Object` or `EObject`).
- If the parent class has dispatch methods with the same name, the dispatcher must call `super._methodName()` for types not handled locally.
Expand Down
5 changes: 5 additions & 0 deletions .agents/skills/xtend-to-java/rules/10-jvm-model-inferrer.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ argument does not by itself trigger a null return in the field/method/parameter/
exact overload used rather than treating this list as a substitute for source inspection. Any local helper
with a `return null` fall-through (a `switch`/`if` that doesn't match) is a trigger too.

| Producer | Nullability | Add form |
|---|---|---|
| `JvmTypeReferenceBuilder.typeRef(Class, ...)` / `typeRef(String, ...)` | **Provably non-null** β€” a lookup miss returns `createUnknownTypeReference(name)` (bytecode: `findDeclaredType β†’ ifnonnull β†’ createUnknownTypeReference`, both paths `areturn`) | `superTypes += typeRef(X)` β†’ plain `it.getSuperTypes().add(_typeReferenceBuilder.typeRef(X))` |
| `toField` / `toMethod` / `toParameter` with a nullable **name** (e.g. a model element's `getName()`) | Nullable β€” the builders guard source element **and** name | Guarded add / `Objects::nonNull` filter |

So the faithful Java of any `+=` whose right-hand side can be null is a guarded add:

```java
Expand Down
13 changes: 13 additions & 0 deletions .agents/skills/xtend-to-java/workflow/formatting-and-commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ Known trade-offs (accepted):
Do not split the translate step per file β€” every intermediate commit before the last file
would be broken anyway, so per-file translate commits only multiply the broken range.

## Tycho baseline bump

When a migrated bundle's version still equals the latest release baseline, the Tycho baseline
comparison fails: the bundle's content changed but its version did not. Add a **separate `build:`
commit** bumping:

- `Bundle-Version` in the bundle's `META-INF/MANIFEST.MF`, and `<version>` in the module `pom.xml`;
- the containing features (`feature.xml` **and** their `pom.xml`) and the `ddk-repository/category.xml`
pins β€” **only if those also equal the baseline**; leave anything already ahead of it alone.

The **sources** artifact is compared too, so even an annotation-only or comment-only change to a
`.java` counts as a content change and needs the bump.

## Commit message format

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ When Xtend is fully removed from a module, update these files.
| **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>` |
| **.project** | **Leave alone.** `org.eclipse.xtext.ui.shared.xtextBuilder` / `…xtextNature` belong to the module's own **grammar**, not to Xtend β€” removing them breaks the Xtext build. Only the `xtend-gen` classpath / build.properties entries and the `xtend-gen/` marker go. |
| **xtend-gen/** | Delete the entire directory (including its `.gitignore` marker) |

## Verify: no leftover Xtend references
Expand Down
4 changes: 4 additions & 0 deletions .agents/skills/xtend-to-java/workflow/known-pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ Consolidated table of common mistakes and their fixes. Review before and after e
| **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`. |
| **`operator_add` (`+=`) skips nulls β€” both overloads** | Before translating any inferrer `EList +=`, follow [`rules/10-jvm-model-inferrer.md`](../rules/10-jvm-model-inferrer.md) Β§10.4. Both overloads skip nulls, while bare `add`/`addAll` rejects them; this exact mismatch shipped once in `FormatJvmModelInferrer.inferConstants`. |
| **`IterableExtensions.toSet` has stable order** | It returns an existing `Set` unchanged; otherwise it builds a `LinkedHashSet` in encounter order. `Collectors.toSet()` does not promise that order and can reorder generated output. Use `Collectors.toCollection(LinkedHashSet::new)` and check whether aliasing is observable. |
| **`JavadocMethod` vs the author's missing `@param`** | Checkstyle `JavadocMethod` runs with `allowMissingParamTags=false`, so *existing* Javadoc that lacks a `@param` for a parameter the Xtend author never documented (typically `context` next to `it`) fails the gate. The sanctioned resolution is to **add the missing tag using the author's wording from a sibling overload** β€” never delete the Javadoc, never invent prose. Related: when a dispatch method's Javadoc sits on the `Void` overload yet names parameters that only exist on the dispatcher, move it to the dispatcher. |
| **`xbase.lib` in signatures is not optional** | The "no `xbase.lib` in migrated Java" rule applies to **private internals only**. `org.eclipse.xtext.xbase.lib.Pair`, `Functions.Function1` and `Procedures.Procedure1` **stay** where they are part of a public/protected signature or demanded by a callee β€” `JvmTypesBuilder` and `IJvmDeclaredTypeAcceptor` take `Procedure1` directly. Replacing those with `java.util.function` types does not compile. |
| **`BooleanExpressionComplexity` vs `SimplifyBooleanReturns`** | Checkstyle caps a boolean expression at 3 operators while PMD rejects the `if (cond) return true; else return false;` split that would dodge it. Resolve with **guard clauses** or a named `final boolean` local β€” preserving operand order and short-circuiting β€” never with a `COUPLING-OFF`/`CHECK-OFF` suppression for this rule. |
| **PMD `StringToString`** | Forces dropping `.toString()` on a value already typed `String` (and `Integer.valueOf(x).toString()` β†’ `Integer.toString(x)`). Both are **sanctioned behaviour-neutral deviations** from `xtend-gen`: the only difference is a null the reference would have NPE'd on. Note them in the commit body. |
| **Behavioural equivalence β‰  literal-token equivalence** | When verifying a migration (or reconciling two migrations) against `xtend-gen`, do NOT decide "faithful" by whether a token (`filterNull`, a `catch`, a charset arg) textually appears. `xtend-gen` semantics can live in a call whose Java equivalent needs *extra* code (e.g. `operator_add`'s null-skip β†’ an explicit null filter; Β§10.4). **Prove every behavioural divergence against fresh `xtend-gen` and cover it with a test β€” gates and existing tests only catch what they already exercise** (the shipped null-leak passed them all because no test fed a null). The `filterNull`-looks-spurious trap cost a real regression when trusted without such proof. |
6 changes: 5 additions & 1 deletion .agents/skills/xtend-to-java/workflow/multi-file-batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ Batch files within the **same module** together where possible β€” they share Ma

After every batch:

1. **Compile gate**: `mvn -pl :<module> -am -DskipTests compile -f ./ddk-parent/pom.xml` β€” must pass.
1. **Compile gate**: `mvn -pl :ddk-target,:<module> -am -DskipTests compile -f ./ddk-parent/pom.xml` β€” must pass.
2. **Test gate**: `mvn verify -f ./ddk-parent/pom.xml --batch-mode --fail-at-end` β€” must pass.
3. **Static analysis gate**: `mvn checkstyle:check pmd:check spotbugs:check -f ./ddk-parent/pom.xml` β€” must pass.

`:ddk-target` must be in every `-pl` list β€” the target artifact is not in `~/.m2`. Before the first
compile after the rename commit, `rm -rf <module>/xtend-gen/com`: the stale generated twin masks
duplicate-class errors. Compare `xtend-gen/` trees with `diff -r -x '.*'` to skip `._trace` sidecars.

A red gate means you do not start the next batch. Diagnose first.

### Commit structure
Expand Down
8 changes: 8 additions & 0 deletions .agents/skills/xtend-to-java/workflow/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ See [`workflow/validation-checklist.md`](./validation-checklist.md) β€” every ru

## Step 5 β€” Build and verify

**Always pass `-pl :ddk-target,:<module>`.** The target-platform artifact is not in `~/.m2`, so a gate
command that lists only the migrated module fails to resolve it.

**Delete `<module>/xtend-gen/com` before the first compile after a rename.** The stale generated twin
of the class you just renamed masks the duplicate-class error you need to see.

**Compare `xtend-gen/` trees with `diff -r -x '.*'`** so the `._trace` sidecars are skipped.

Module-specific build first:
```bash
mvn -pl <module1>,<module2> -am verify -f ./ddk-parent/pom.xml > mvn-output.txt 2>&1
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/xtend-to-java/workflow/validation-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Every rule below is a hard gate.

| # | Rule | Requirement |
|---|------|-------------|
| 1 | Javadoc preservation | Copy **class and member Javadoc** from Xtend source verbatim. Never generate/guess Javadoc that wasn't in the original. (Does **not** cover the file copyright header β€” see rule 21, which always normalises it.) |
| 1 | Javadoc preservation | Copy **class and member Javadoc** from Xtend source verbatim. Never generate/guess Javadoc that wasn't in the original. (Does **not** cover the file copyright header β€” see rule 21, which always normalises it.) Two sanctioned edits: (a) Checkstyle `JavadocMethod` (`allowMissingParamTags=false`) rejects inherited Javadoc missing a `@param` for a parameter the Xtend author left undocumented (typically `context` beside `it`) β€” add the missing tag using **the author's own wording from a sibling overload**, never delete the Javadoc and never invent prose; (b) dispatch Javadoc sitting on the `Void` overload but naming parameters that only exist on the dispatcher moves to the dispatcher. |
| 2 | `@throws` tags | Only add when method already has Javadoc AND migrated signature declares `throws`. Don't create Javadoc just for the tag. |

### Types and variables
Expand All @@ -30,7 +30,7 @@ Every rule below is a hard gate.

| # | Rule | Requirement |
|---|------|-------------|
| 5 | String building idiom | Static single-line β†’ literal; static multi-line β†’ text block; interpolation without control flow β†’ `.formatted()`; control flow β†’ `StringBuilder`. |
| 5 | String building idiom | Static single-line β†’ literal; static multi-line β†’ text block; interpolation without control flow β†’ `.formatted()`; control flow β†’ `StringBuilder` β€” **except** a chain carrying `newLineIfNotEmpty()` after a dynamic value, a two-arg `append(value, indent)` of a possibly multi-line value, or `appendImmediate`, which stays on `StringConcatenation` (rule 35). A nullable value moved into `.formatted()` needs `Strings.emptyIfNull` (`append(null)` appends nothing). |
| 6 | Text block `\` escape | Use `\` on last content line to suppress trailing `\n` when `xtend-gen/` shows the string doesn't end with newline. |
| 17 | MultipleStringLiterals | Tests: extract to `private static final String` constants. Generators: `CHECKSTYLE:CONSTANTS-OFF/ON`. |
| 20 | InsufficientStringBufferDeclaration | Size generously: 512 small methods, 2048 generators. |
Expand Down Expand Up @@ -97,7 +97,7 @@ Every rule below is a hard gate.
|---|------|-------------|
| 21 | Copyright headers | File starts with the exact Avaloq `/**…**/` banner header (see [`formatting-and-commit.md`](./formatting-and-commit.md)). This **replaces** any header the source carried β€” a `/* generated by Xtext x.y */` stub marker or a `/** … */` Javadoc-style copyright block. Normalising to the banner is **not** "inventing" (rule 1 does not apply to the copyright header). Match a sibling `.java` in the module. |
| 22 | Commit format | Two-step: `(1/2: rename sources)` pure `git mv` commit + `(2/2: translate to Java 21)` in-place rewrite commit (+ infra commit when module fully off Xtend). See [`formatting-and-commit.md`](./formatting-and-commit.md). |
| 28 | Infrastructure cleanup | Remove Xtend from MANIFEST.MF, build.properties, .classpath, .project; delete `xtend-gen/` directory (when module fully off Xtend). |
| 28 | Infrastructure cleanup | Remove Xtend from MANIFEST.MF, build.properties, .classpath; delete `xtend-gen/` directory (when module fully off Xtend). Leave `.project` untouched β€” the Xtext builder/nature serve the module's grammar. |

### Migration-campaign gates (learned)

Expand Down