From ea936bb5d54828e882884bc766b2b3c99d49536e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Fri, 11 Sep 2026 14:59:28 +0200 Subject: [PATCH] docs(skill): record jvmmodel-batch learnings in xtend-to-java Learned migrating the ten jvmmodel files (expression, export, scope), written so the skill applies to any Eclipse/Tycho/Xtext repository: commands use and placeholders, and repository conventions (license header, shared JDT settings) are taken from the repository itself. 1. Tier 4 stays on the reference StringConcatenation chain when it has newLineIfNotEmpty after a dynamic value, two-arg append of a possibly multi-line value, appendImmediate, or a StringConcatenationClient value; append(null) appends nothing, so a nullable String moved into .formatted() wraps in Strings.emptyIfNull and other nullable values in Objects.toString(value, ""). 2. JvmTypeReferenceBuilder.typeRef is provably non-null; to* builders with a nullable name stay guarded. 3. Dispatcher case order comes from xtend-gen, not source order; the terminal else-throw stays, Java's definite-return analysis needs it. 4. Checkstyle JavadocMethod vs rule 1: complete missing @param tags from a sibling overload; move dispatch Javadoc off the Void overload. 5. xbase.lib types stay in public signatures and callee-demanded callbacks. 6. BooleanExpressionComplexity vs SimplifyBooleanReturns: guard clauses or a named local, never a suppression. 7. PMD StringToString: Integer.toString(x) is neutral; dropping toString() on a String needs a non-null proof, else Objects.requireNonNull. 8. Module-scoped gate commands must include the target-platform module; clear the stale xtend-gen output before the first compile; diff -r -x '.*' for the freshness check. 9. If the reactor runs compare-version-with-baselines (tycho-p2-extras-plugin): separate build: bump commit; features and category only when they also equal the baseline; the sources artifact is compared. 10. .project Xtext builder/nature stay only while the module still has Xtext-language resources; otherwise they were Xtend's and go. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- .../xtend-to-java/rules/00-decisions.md | 4 +- .../rules/01-imports-and-package.md | 4 +- .../xtend-to-java/rules/04-templates.md | 21 +++++++- .../rules/06-extension-methods.md | 2 +- .../xtend-to-java/rules/09-misc-syntax.md | 26 ++++++---- .../rules/10-jvm-model-inferrer.md | 43 +++++++++------- .../workflow/formatting-and-commit.md | 49 ++++++++++--------- .../workflow/infrastructure-cleanup.md | 10 ++-- .../xtend-to-java/workflow/known-pitfalls.md | 16 +++--- .../workflow/multi-file-batch.md | 17 +++++-- .../workflow/one-file-conversion.md | 4 +- .../skills/xtend-to-java/workflow/overview.md | 32 ++++++++---- .../workflow/validation-checklist.md | 18 +++---- 13 files changed, 151 insertions(+), 95 deletions(-) diff --git a/.agents/skills/xtend-to-java/rules/00-decisions.md b/.agents/skills/xtend-to-java/rules/00-decisions.md index 47e0582be3..6b70e08498 100644 --- a/.agents/skills/xtend-to-java/rules/00-decisions.md +++ b/.agents/skills/xtend-to-java/rules/00-decisions.md @@ -88,9 +88,7 @@ LOGGER.info("Processing file: %s".formatted(path)); Add at class level **only when the module's effective JDT settings have `nonExternalizedStringLiteral=warning`**. -To check: look at `/.settings/org.eclipse.jdt.core.prefs`. If absent (most modules), check the project's shared settings at `ddk-configuration/.settings/org.eclipse.jdt.core.prefs`. - -**Current DDK state:** all modules inherit `warning` from `ddk-configuration`, so all migrated classes currently need `@SuppressWarnings("nls")` at class level. If a test-specific settings profile is later added with `ignore` (as in ASMD), test classes would not need it. +To check: look at `/.settings/org.eclipse.jdt.core.prefs`. If absent, check the shared settings project's `.settings/org.eclipse.jdt.core.prefs`. ```java @SuppressWarnings("nls") diff --git a/.agents/skills/xtend-to-java/rules/01-imports-and-package.md b/.agents/skills/xtend-to-java/rules/01-imports-and-package.md index 0c15298958..94b65ea19d 100644 --- a/.agents/skills/xtend-to-java/rules/01-imports-and-package.md +++ b/.agents/skills/xtend-to-java/rules/01-imports-and-package.md @@ -28,8 +28,8 @@ import java.util.List; import org.eclipse.xtext.testing.InjectWith; import org.junit.jupiter.api.Test; -// Group 3: com.* (com.avaloq.* before com.google.*) -import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil; +// Group 3: com.* (com.example.* before com.google.*) +import com.example.mydsl.test.util.MyTestUtil; import com.google.inject.Inject; ``` diff --git a/.agents/skills/xtend-to-java/rules/04-templates.md b/.agents/skills/xtend-to-java/rules/04-templates.md index 1553404e8d..39941976f4 100644 --- a/.agents/skills/xtend-to-java/rules/04-templates.md +++ b/.agents/skills/xtend-to-java/rules/04-templates.md @@ -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(...); }` @@ -151,8 +157,8 @@ string must NOT end with `\n`, use a **line continuation** `\` on the last conte String s = """ check configuration mdlc - for com.avaloq.tools.dsl.labeldef.LabelDef { - catalog com.avaloq.tools.dsl.labeldef.validation.LabelDefCoreChecks { } + for com.example.mydsl.MyDsl { + catalog com.example.mydsl.validation.MyDslCoreChecks { } }\ """; ``` @@ -243,6 +249,17 @@ 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"`. Wrap a nullable `String` moved into `.formatted()` with `Strings.emptyIfNull(value)` +(`org.eclipse.xtext.util.Strings`, String-only overload). +**Keep any `StringConcatenationClient`-valued or otherwise specially-rendered interpolation on the +`StringConcatenation` chain (rule 35)**; never convert it to `.formatted()`. +For other nullable values, use `Objects.toString(value, "")` (`java.util.Objects`) **only when +`toString()` is exactly what `append` would render** (boxed numbers, plain `CharSequence`); this +preserves the "append nothing" semantics for null. + **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). diff --git a/.agents/skills/xtend-to-java/rules/06-extension-methods.md b/.agents/skills/xtend-to-java/rules/06-extension-methods.md index d8740001fb..f71fd04802 100644 --- a/.agents/skills/xtend-to-java/rules/06-extension-methods.md +++ b/.agents/skills/xtend-to-java/rules/06-extension-methods.md @@ -10,7 +10,7 @@ Convert the field, then rewrite every call site: - Field: `@Inject extension MyHelper helper` → `@Inject private MyHelper helper;` - Call site: `obj.extensionMethod(args)` → `helper.extensionMethod(obj, args)` (the implicit receiver `obj` moves to the first parameter). -- If the extension field had no name (`@Inject extension CheckGeneratorNaming`), invent one following the convention: camelCase class name starting lowercase (`checkGeneratorNaming`). +- If the extension field had no name (`@Inject extension MyGeneratorNaming`), invent one following the convention: camelCase class name starting lowercase (`myGeneratorNaming`). Tip: The `xtend-gen/` output shows exactly how the Xtend compiler resolved every extension call — use it as the reference. diff --git a/.agents/skills/xtend-to-java/rules/09-misc-syntax.md b/.agents/skills/xtend-to-java/rules/09-misc-syntax.md index b347924ea1..c576e8a24b 100644 --- a/.agents/skills/xtend-to-java/rules/09-misc-syntax.md +++ b/.agents/skills/xtend-to-java/rules/09-misc-syntax.md @@ -65,12 +65,14 @@ Generates a constructor taking all final fields as parameters. Write it manually ### `@Tag` +Applies only if the project uses DDK's test framework (`@Tag` / `TagExtension` from `com.avaloq.tools.ddk.xtext.test.core`). + Assigns sequential integers (starting at `TagCompilationParticipant.COUNTER_BASE = 10000`) to test marker fields at Xtend compile time via `TagCompilationParticipant`. Standard Java APT (`AbstractProcessor`) **cannot** replicate this — it can only generate new source files, not modify initializers of existing fields. Use `TagExtension` instead: a JUnit 5 -`BeforeEachCallback` in `com.avaloq.tools.ddk.xtext.test.core` that performs the same sequential +`BeforeEachCallback` that performs the same sequential assignment at runtime via reflection. **Migration steps:** @@ -84,10 +86,7 @@ assignment at runtime via reflection. ```java @ExtendWith({InjectionExtension.class, TagExtension.class}) ``` -3. Add the import: - ```java - import com.avaloq.tools.ddk.xtext.test.TagExtension; - ``` +3. Import `TagExtension` from the test framework's package. **Why not `final`?** `TagExtension` uses `Field.setInt()` to write the value at runtime. `Field.setInt()` on a `final` field throws `IllegalAccessException` even after `setAccessible(true)` @@ -138,7 +137,14 @@ 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. +- **Keep the terminal `else { throw new IllegalArgumentException("Unhandled parameter types: …"); }` + exactly as `xtend-gen/` emits it**, even after a `!= null` / `== null` pair. Java's definite-return + analysis does not treat that pair as exhaustive; deleting the terminal throw leaves a non-void + dispatcher without a return on every path. - 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. @@ -181,8 +187,8 @@ the **data contract** first: - if the API or format supplies an encoding, honour it — e.g. pass `file.getCharset()` when reading an Eclipse `IFile` (the `InputStreamReader(InputStream, String)` overload accepts that value); -- for repository-owned text governed by `ddk-parent/pom.xml`'s UTF-8 project encoding, use - `java.nio.charset.StandardCharsets.UTF_8`: +- for repository-owned text governed by the parent POM's `project.build.sourceEncoding`, use + the corresponding charset; for UTF-8, use `java.nio.charset.StandardCharsets.UTF_8`: `new InputStreamReader(stream, StandardCharsets.UTF_8)`; - for opaque external data with no documented encoding, do **not** guess UTF-8 from the Java source-encoding setting. Establish the contract. If platform-default encoding is genuinely part of that contract, use @@ -190,5 +196,5 @@ the **data contract** first: This is a sanctioned divergence from `xtend-gen` only when the chosen charset follows a verified contract; call it out and test it. Do not use a lint warning as blanket permission for an unrelated behaviour change. -Two legacy `// NOPMD` suppressions of this rule exist in hand-written code (`CheckPreferencesHelper`, -`XtextGMFResourceUtil`); they are grandfathered, not a precedent for migrations. +Any legacy `// NOPMD` suppressions of this rule in hand-written code are grandfathered, +not a precedent for migrations. diff --git a/.agents/skills/xtend-to-java/rules/10-jvm-model-inferrer.md b/.agents/skills/xtend-to-java/rules/10-jvm-model-inferrer.md index 8cf4611ca6..436ac803f7 100644 --- a/.agents/skills/xtend-to-java/rules/10-jvm-model-inferrer.md +++ b/.agents/skills/xtend-to-java/rules/10-jvm-model-inferrer.md @@ -1,9 +1,8 @@ # JVM model inferrer (JvmTypesBuilder) Xtend model inferrers (`class X extends AbstractModelInferrer` with `def dispatch infer(...)`) -build Java types through the `JvmTypesBuilder` extension DSL. The migrated -`FormatJvmModelInferrer.java` (`com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/`) -is the canonical worked example — read it in full before migrating another inferrer. +build Java types through the `JvmTypesBuilder` extension DSL. Read the inferrer's `.xtend` source +and `xtend-gen/` output in full before migrating it. ## 10.1 Core mapping @@ -12,12 +11,12 @@ is the canonical worked example — read it in full before migrating another inf | `@Inject extension JvmTypesBuilder` | `@Inject private JvmTypesBuilder jvmTypesBuilder;` — every extension call becomes explicit (`jvmTypesBuilder.toClass(...)`) | | `def dispatch infer(X x, IJvmDeclaredTypeAcceptor acceptor, boolean preIndexingPhase)` | `_infer(final X x, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPreIndexingPhase)` + the dispatcher pattern ([`rules/09-misc-syntax.md`](./09-misc-syntax.md) §9.7) | | `x.toClass(name)` | `jvmTypesBuilder.toClass(x, name)` | -| `acceptor.accept(cls, [ ... ])` | `acceptor.accept(cls, initializer)` where `initializer` is a `Procedure1` (see `FormatJvmModelInferrer._infer`) | +| `acceptor.accept(cls, [ ... ])` | `acceptor.accept(cls, initializer)` where `initializer` is a `Procedure1` | | `members += x` / `superTypes += x` / `annotations += x` | `it.getMembers().add(x)` / … — **only when `x` is provably non-null**; both `JvmTypesBuilder.operator_add` overloads skip nulls (single element and collection), so see §10.4 before translating any `+=` | -| `x.toMethod(name, type) [ ... ]` | `jvmTypesBuilder.toMethod(x, name, type, initializer)` with a `Procedure1` (see `FormatJvmModelInferrer.inferGetGrammarAccess`) | +| `x.toMethod(name, type) [ ... ]` | `jvmTypesBuilder.toMethod(x, name, type, initializer)` with a `Procedure1` | | `x.toField(name, type) [ ... ]` / `x.toParameter(name, type)` | `jvmTypesBuilder.toField(x, name, type, initializer)` / `jvmTypesBuilder.toParameter(x, name, type)` | | `typeRef(T)` / `typeRef(name)` | `_typeReferenceBuilder.typeRef(...)` — the protected field inherited from `AbstractModelInferrer`; for lookups needing a context object use `typeReferences.getTypeForName(name, context)` | -| `documentation = '''...'''` | `jvmTypesBuilder.setDocumentation(it, "...".formatted(...))` (see `FormatJvmModelInferrer.inferClass`) | +| `documentation = '''...'''` | `jvmTypesBuilder.setDocumentation(it, "...".formatted(...))` | | `static = true` / `visibility = PROTECTED` / `abstract = true` | `it.setStatic(true)` / `method.setVisibility(JvmVisibility.PROTECTED)` / `it.setAbstract(true)` | | `initializer = expr` (on a field) | set inside the field's initializer `Procedure1` via the corresponding setter/`jvmTypesBuilder` call — read `xtend-gen/` for the exact form | @@ -25,15 +24,13 @@ is the canonical worked example — read it in full before migrating another inf Xtend assigns bodies two ways; both become `jvmTypesBuilder.setBody(method, ...)`: -- `body = [append('''...''')]` (procedure form) → `Procedure1` that appends — - the form the migrated file uses throughout: +- `body = [append('''...''')]` (procedure form) → `Procedure1` that appends: ```java final Procedure1 body = (final ITreeAppendable appendable) -> { - appendable.append("return (%sGrammarAccess) super.getGrammarAccess();".formatted(...)); + appendable.append("return %s;".formatted(expression)); }; jvmTypesBuilder.setBody(method, body); ``` - (see `FormatJvmModelInferrer.inferGetGrammarAccess`) - `body = '''template'''` (template form) → the Xtend compiler emits the `StringConcatenationClient` overload of `setBody`. Either keep that overload (check `xtend-gen/`) or convert to the `Procedure1` form with the template text @@ -43,10 +40,8 @@ Xtend assigns bodies two ways; both become `jvmTypesBuilder.setBody(method, ...) ## 10.3 Gate notes specific to inferrers - The inference closures are long by design; bracket the class with - `// CHECKSTYLE:CHECK-OFF LambdaBodyLength the model-inference closures mirror the Xtext JvmTypesBuilder API and are kept whole` - (see the class-level suppression in `FormatJvmModelInferrer`). -- Emitted Java source fragments are repeated literals — `// CHECKSTYLE:CONSTANTS-OFF` applies - (see the class-level suppression in `FormatJvmModelInferrer`). + `// CHECKSTYLE:CHECK-OFF LambdaBodyLength the model-inference closures mirror the Xtext JvmTypesBuilder API and are kept whole`. +- Emitted Java source fragments are repeated literals — `// CHECKSTYLE:CONSTANTS-OFF` applies. - `members += list.map(...).flatten.filterNull` chains: see [`references/xtend-library-replacements.md`](../references/xtend-library-replacements.md) for `flatten`/`filterNull` stream equivalents; the result feeds the add — but read §10.4 first @@ -75,6 +70,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 @@ -104,10 +104,17 @@ return null (nullable source element or name, or a `return null` branch)? If yes guard / `Objects::nonNull` filter. A bare `add`/`addAll` over a null-capable producer is a faithfulness regression. -> Real shipped example: `FormatJvmModelInferrer.inferConstants` used a bare add for -> `members += allConstants.map[createConstant]`, although `createConstant` returns null for a value-less -> constant. The guard now in that method and its regression test are the canonical fix; the defect escaped -> the gates because no earlier test supplied the null-producing input. +> Example: a bare `members += list.map[create]` skips nulls even without an explicit `filterNull`. +> If `create` returns null for some inputs, translating it to an unguarded `add`/`addAll` throws. +> Preserve the null-skip with a guarded add: +> ```java +> for (final Input input : list) { +> final JvmMember member = create(input); +> if (member != null) { it.getMembers().add(member); } +> } +> ``` +> Test the null-producing inputs explicitly: static gates and tests that never supply them cannot +> detect this regression. ## 10.5 Verification diff --git a/.agents/skills/xtend-to-java/workflow/formatting-and-commit.md b/.agents/skills/xtend-to-java/workflow/formatting-and-commit.md index e70167a835..310ce2a2f9 100644 --- a/.agents/skills/xtend-to-java/workflow/formatting-and-commit.md +++ b/.agents/skills/xtend-to-java/workflow/formatting-and-commit.md @@ -2,33 +2,22 @@ ## Copyright headers -Every `.java` file must start with the exact Avaloq banner header below: -```java -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -``` +Every `.java` file must start with the repository's required license header (see `AGENTS.md` or +copy it from an existing Java file in the same module), replacing whatever header the Xtend source had. -> **This banner REPLACES whatever header the source carried — it is not "preserve the original".** -> The `.xtend` source (or its `xtend-gen/` output) commonly starts with a different header, e.g. +> **The repository's required header REPLACES whatever header the source carried — it is not "preserve the original".** +> The `.xtend` source (or its `xtend-gen/` output) may start with a different header, e.g. > a generated stub marker `/* generated by Xtext 2.x */`, or a `/** ... */` Javadoc-style copyright -> block without the asterisk banner. **Normalise all of these to the banner above**, matching the -> existing hand-written `.java` files in the same module (check a sibling like `Activator.java`). +> block. **Normalise all of these to the repository's required header**, matching the +> existing hand-written `.java` files in the same module. > > The "never invent / preserve Javadoc exactly" rule (Step 3d, validation rule 1, the *Invented > Javadoc* pitfall) applies to **class and member Javadoc and to behaviour** — it does **not** apply -> to the file-level copyright header, which is always normalised to this banner. Swapping in the -> required banner is not "inventing". +> to the file-level copyright header, which is always normalised to the repository's required header. +> Swapping in the required header is not "inventing". > > This holds even for files that look generated (IDE module/setup stubs): once migrated to -> hand-maintained Java they get the standard banner like every other `.java` in the repo. +> hand-maintained Java they get the repository's required header like every other `.java` in the repo. Verify before committing. @@ -43,7 +32,7 @@ Run the Eclipse headless formatter from the command line: ```powershell $eclipse = "\eclipsec.exe" $ini = "\eclipse-formatter.ini" # custom ini fixing the p2.mirrors bug -$config = "ddk-configuration\.settings\org.eclipse.jdt.core.prefs" +$config = "\.settings\org.eclipse.jdt.core.prefs" # is the shared settings project directory $ws = "$env:TEMP\eclipse-fmt-ws" # temp workspace to avoid conflicts & $eclipse --launcher.ini $ini -noSplash -data $ws ` @@ -81,7 +70,8 @@ Structure every migration slice as **two commits plus an optional infrastructure faithfulness notes (null-semantics decisions, documented deviations) in this commit's body. 3. **Infrastructure commit** (only when the module is now fully off Xtend) — the [`infrastructure-cleanup.md`](./infrastructure-cleanup.md) changes: build.properties, - .classpath, .project, `xtend-gen/` deletion, MANIFEST check. + .classpath, .project (only when no Xtext-language resources remain; see + infrastructure-cleanup.md), `xtend-gen/` deletion, MANIFEST check. Known trade-offs (accepted): - The rename commit **intentionally does not compile** (Xtend syntax in `.java` files). PR CI @@ -95,6 +85,21 @@ 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 + +**Only if the reactor runs the `compare-version-with-baselines` goal** (of `tycho-p2-extras-plugin`; search the parent POM for the goal name); otherwise skip this section. + +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 `` in the module `pom.xml`; +- the containing features (`feature.xml` **and** their `pom.xml`) and the update site's `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 ``` diff --git a/.agents/skills/xtend-to-java/workflow/infrastructure-cleanup.md b/.agents/skills/xtend-to-java/workflow/infrastructure-cleanup.md index 085468fd28..ccb0193aa1 100644 --- a/.agents/skills/xtend-to-java/workflow/infrastructure-cleanup.md +++ b/.agents/skills/xtend-to-java/workflow/infrastructure-cleanup.md @@ -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 `` (including any nested ``) | -| **.project** | Remove `org.eclipse.xtext.ui.shared.xtextBuilder` from `` and `org.eclipse.xtext.ui.shared.xtextNature` from `` | +| **.project** | Keep `org.eclipse.xtext.ui.shared.xtextBuilder` / `…xtextNature` if the module still contains Xtext-language resources the IDE must build (a `.xtext` grammar or DSL model files); otherwise remove them in the infrastructure commit, because they were there for Xtend. | | **xtend-gen/** | Delete the entire directory (including its `.gitignore` marker) | ## Verify: no leftover Xtend references @@ -20,12 +20,12 @@ grep -r "xtend" /META-INF/ /build.properties /.classpath ## What stays put -- **`.mwe2.launch` files** (e.g. `GenerateCheck.mwe2.launch`) — drive MWE2 src-gen regeneration, which is independent of Xtend. They remain after a module is off Xtend. -- **`com.avaloq.tools.ddk.workflow/`** — the MWE2 workflow bundle. Same reason. -- **`ddk-target/ddk.target`** — keep any Xtend SDK reference until the LAST module migrates; removing earlier breaks builds for modules still on Xtend. +- **`.mwe2.launch` files** — drive MWE2 src-gen regeneration, which is independent of Xtend. They remain after a module is off Xtend. +- **The MWE2 workflow bundle** — same reason. +- **The target definition** — keep any Xtend SDK reference until the LAST module migrates; removing earlier breaks builds for modules still on Xtend. ## If last Xtend module in the repository Also update: -- `ddk-parent/pom.xml` — remove `xtend-maven-plugin`, `xtend.version`, xtend-gen entries in `maven-clean-plugin` and `tycho-source-plugin` +- The parent POM — remove `xtend-maven-plugin`, `xtend.version`, xtend-gen entries in `maven-clean-plugin` and `tycho-source-plugin` - Root `.gitignore` — remove `/*/xtend-gen/*` and `!/*/xtend-gen/.gitignore` diff --git a/.agents/skills/xtend-to-java/workflow/known-pitfalls.md b/.agents/skills/xtend-to-java/workflow/known-pitfalls.md index a5efda4bd6..dabe9146db 100644 --- a/.agents/skills/xtend-to-java/workflow/known-pitfalls.md +++ b/.agents/skills/xtend-to-java/workflow/known-pitfalls.md @@ -13,16 +13,16 @@ Consolidated table of common mistakes and their fixes. Review before and after e | **Checked-exception handling** | Xtend may let a checked exception escape without declaring it. Do not copy its generated exception-handling scaffolding. Declare the exact checked type when compatible; otherwise obtain explicit review for a project-established boundary strategy and preserve the cause. See [`rules/05-control-flow.md`](../rules/05-control-flow.md) §5.5. | | **Invented Javadoc** | Never add **class/member Javadoc** that wasn't in the original. This is a migration, not a rewrite. (The file copyright header is the one exception — see next row — it is always normalised, not preserved.) | | **Generated supertypes live in `src-gen/`, not `xtend-gen/`** | When the class extends/overrides a generated `Abstract*` base (Module/Setup/runtime/UI), read that base in `src-gen/` (committed, present without a build — unlike `xtend-gen/`) for inherited constructor signatures, the real `@Override` targets, and the return types Xtend inferred. Don't guess the supertype API. | -| **Copyright header ≠ "preserve original"** | Always normalise to the Avaloq banner, replacing whatever the source had — see [`formatting-and-commit.md`](./formatting-and-commit.md) §Copyright header. | +| **Copyright header ≠ "preserve original"** | Always normalise to the repository's required header, replacing whatever the source had — see [`formatting-and-commit.md`](./formatting-and-commit.md) §Copyright header. | | **`@Data` / `@Accessors`** | These generate code at compile time. The `xtend-gen/` output shows exactly what — copy equals/hashCode/toString/getters from there. | -| **`@Tag` fields must not be `final`** | `TagExtension` assigns tag values via `Field.setInt()` at runtime. `Field.setInt()` on a `final` field fails on Java 9+ even after `setAccessible(true)`. IDE formatters and save-actions silently add `final` to `int` fields — always strip it from `@Tag` fields. See [`rules/09-misc-syntax.md`](../rules/09-misc-syntax.md) §9.6. | +| **`@Tag` fields must not be `final`** | Applies only if the project uses DDK's test framework (`@Tag` / `TagExtension` from `com.avaloq.tools.ddk.xtext.test.core`). `TagExtension` assigns tag values via `Field.setInt()` at runtime. `Field.setInt()` on a `final` field fails on Java 9+ even after `setAccessible(true)`. IDE formatters and save-actions silently add `final` to `int` fields — always strip it from `@Tag` fields. See [`rules/09-misc-syntax.md`](../rules/09-misc-syntax.md) §9.6. | | **`BasicEList` in generic code** | Needs explicit type parameter — `new BasicEList()`. | | **StringBuilder in `xtend-gen/`** | If `xtend-gen/` has `StringConcatenation` but Xtend has a template, that's the signal to use text block or `.formatted()` (tier 1–3) or `StringBuilder` (tier 4). | | **Non-parameterized logging** | Xtend files often have `"msg" + x` in log calls. Fix to `{}` placeholders. | | **PMD missing type-resolution** | Always `compile` before `pmd:check` or you'll miss `MissingOverride`, `LooseCoupling` etc. | | **`--fail-at-end` hides failures** | Check the final BUILD line, not intermediate output. | | **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** | See [`rules/01-imports-and-package.md`](../rules/01-imports-and-package.md) for the canonical order. Not enforced by checkstyle (no `ImportOrder` module); wrong order is diff churn only — `com.avaloq.*` precedes `com.google.*`. | +| **Import order** | See [`rules/01-imports-and-package.md`](../rules/01-imports-and-package.md) for the canonical order. Not enforced by checkstyle (no `ImportOrder` module); wrong order is diff churn only — `com.example.*` precedes `com.google.*`. | | **Eclipse CLI formatter** | Does NOT organize imports — only code formatting. Import order must be correct from the start. | | **IllegalCatch / IllegalThrows** | Checkstyle `IllegalCatch` bans `catch (Exception/Throwable/RuntimeException)`. Do not copy broad catches from `xtend-gen`; catch the narrow checked types Java requires. Suppress `IllegalCatch` only in the exceptional case where an invoked API itself declares a broad type and no narrower catch compiles. Wrap only when the original behaviour or an intentional change calls for wrapping. `IllegalThrows` bans `throws Throwable/RuntimeException/Error` (plain `throws Exception` is allowed on a `@Test` when the JUnit-invoked API declares it; otherwise preserve/narrow the actual signature). Don't suppress `PMD.AvoidCatchingGenericException`. | | **Rollback** | A slice is 2-3 commits — plain `HEAD~1` strands the rename commit. Use the recipe in [`formatting-and-commit.md`](./formatting-and-commit.md) §Rollback. After reverting, build and test. | @@ -34,14 +34,18 @@ Consolidated table of common mistakes and their fixes. Review before and after e | 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`). | +| **`@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`. | | **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. | +| **A complete-migration reference branch 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`. | -| **`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`. | +| **`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. | | **`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`** | `Integer.valueOf(x).toString()` → `Integer.toString(x)` is a **sanctioned behaviour-neutral deviation** for primitive `x` (the boxed receiver cannot be null). Drop `.toString()` on a `String`-typed receiver **only when proven non-null**; cite the producer's source/bytecode and exact overload, as for `+=` producers in [`rules/10-jvm-model-inferrer.md`](../rules/10-jvm-model-inferrer.md) §10.4. Otherwise use `Objects.requireNonNull(value)` (`java.util.Objects`): PMD-clean, preserving the NPE at the same point. Note the rewrite and proof or preserved failure in the commit body. NPE → propagated null is an observable behaviour change; see **Behavioural equivalence ≠ literal-token equivalence** below. | | **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. | diff --git a/.agents/skills/xtend-to-java/workflow/multi-file-batch.md b/.agents/skills/xtend-to-java/workflow/multi-file-batch.md index 32332f8008..a454254bb4 100644 --- a/.agents/skills/xtend-to-java/workflow/multi-file-batch.md +++ b/.agents/skills/xtend-to-java/workflow/multi-file-batch.md @@ -24,7 +24,7 @@ Process modules bottom-up: 1. Leaf modules first: test utilities, small standalone bundles. 2. Then mid-level: language cores, simple generators. -3. Heavy generators and dispatch-heavy code last: `xtext.generator`, `xtext.format`, large `xtext.export.*`. +3. Heavy generators and dispatch-heavy code last: code generators, formatters, large exporters. ### Group by module @@ -34,9 +34,16 @@ Batch files within the **same module** together where possible — they share Ma After every batch: -1. **Compile gate**: `mvn -pl : -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. +1. **Compile gate**: `mvn -pl :,: -am -DskipTests compile -f ` — must pass. +2. **Test gate**: `mvn verify -f --batch-mode --fail-at-end` — must pass. +3. **Static analysis gate**: `mvn checkstyle:check pmd:check spotbugs:check -f ` — must pass. + +Include the target-platform module in every `-pl` list — its artifact is not in `~/.m2`. Before the first +compile after the rename commit, delete the generated files under `/xtend-gen/` except its +`.gitignore`, e.g. `find /xtend-gen -mindepth 1 ! -name .gitignore -delete`; otherwise the stale +generated copy of the renamed class stays on the source path, collides with the new `.java` or hides +a class you have not translated yet, and the compile result no longer tells you anything. +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. @@ -47,7 +54,7 @@ a pure `git mv` rename commit for all `.xtend` in the slice, an in-place transla when the module is fully off Xtend — an infrastructure-cleanup commit. Commit message format: see [`formatting-and-commit.md`](./formatting-and-commit.md). -Example: `refactor: migrate Xtend to Java - com.avaloq.tools.ddk.check.core.test (1/2: rename sources)` +Example: `refactor: migrate Xtend to Java - com.example.mydsl.test (1/2: rename sources)` ### Rollback strategy diff --git a/.agents/skills/xtend-to-java/workflow/one-file-conversion.md b/.agents/skills/xtend-to-java/workflow/one-file-conversion.md index e564912901..030ec37a65 100644 --- a/.agents/skills/xtend-to-java/workflow/one-file-conversion.md +++ b/.agents/skills/xtend-to-java/workflow/one-file-conversion.md @@ -14,11 +14,11 @@ Use this when converting a single `.xtend` to its `.java` counterpart. 8. **Commit as two steps** — a pure `git mv` rename commit, then an in-place translate commit; see [`formatting-and-commit.md`](./formatting-and-commit.md) §Commit structure. For a single file the `git mv` IS the removal; do not delete+re-add. 9. **Verify the file compiles:** ```bash - mvn -pl : -am -DskipTests compile -f ./ddk-parent/pom.xml > mvn-output.txt 2>&1 + mvn -pl :,: -am -DskipTests compile -f > mvn-output.txt 2>&1 ``` 10. **Run quality checks:** ```bash - mvn -pl : -am checkstyle:check pmd:check -f ./ddk-parent/pom.xml > mvn-output.txt 2>&1 + mvn -pl :,: -am checkstyle:check pmd:check -f > mvn-output.txt 2>&1 ``` ## Reference diff --git a/.agents/skills/xtend-to-java/workflow/overview.md b/.agents/skills/xtend-to-java/workflow/overview.md index 0a9e58e318..b1d1d52f0d 100644 --- a/.agents/skills/xtend-to-java/workflow/overview.md +++ b/.agents/skills/xtend-to-java/workflow/overview.md @@ -1,6 +1,8 @@ # Conversion workflow overview -This is the full end-to-end workflow for migrating Xtend files to Java in dsl-devkit. +This is the full end-to-end workflow for migrating Xtend files to Java in an Eclipse/Tycho repository. + +`` is the reactor's parent POM, and `` is the Maven module holding the target-platform definition. ## Step 0 — Establish scope @@ -41,7 +43,7 @@ Before touching any files, establish: Collect the list of `.xtend` source files to migrate. 2. **What should the branch be called?** - Convention: `migrate/xtend-to-java/` (e.g., `migrate/xtend-to-java/check-core-test`). + Convention: `migrate/xtend-to-java/`. 3. **Is there an existing migration branch with pre-converted files?** If yes, you can pull already-converted `.java` files from it (Step 2 Option A). @@ -60,8 +62,7 @@ git fetch upstream git checkout -b "$SLICE" upstream/master ``` -For stacked multi-slice migrations, suffix the branch name with `-step-N` -(e.g. `migrate/xtend-to-java/check-core-step-1`, `...-step-2`) per the +For stacked multi-slice migrations, suffix the branch name with `-step-N` per the project's stacked-PR convention. --- @@ -106,7 +107,7 @@ Do not write the Java file first and then vet it — read the references first, > first to (re)generate `xtend-gen/`: > > ```bash -> mvn -f ./ddk-parent/pom.xml -pl : -am -DskipTests -T 3C compile --batch-mode +> mvn -f -pl :,: -am -DskipTests -T 3C compile --batch-mode > ``` > > The freshly built `xtend-gen/` is the **authoritative** ground truth: it is the Xtend compiler's @@ -168,7 +169,7 @@ Xtend's template whitespace rules: After reading both references, write Java that: 1. **Matches the `xtend-gen/` behavior exactly** for all string outputs, method signatures, and control flow 2. **Uses idiomatic Java** (text blocks, `.formatted()`, concatenation) instead of `StringConcatenation` -3. **Preserves the original class/member Javadoc exactly** — never invent. (Copyright header excepted: always normalise it to the Avaloq banner per [`formatting-and-commit.md`](./formatting-and-commit.md).) +3. **Preserves the original class/member Javadoc exactly** — never invent. (Copyright header excepted: always normalise it to the repository's required header per [`formatting-and-commit.md`](./formatting-and-commit.md).) 4. **Follows the quality checklist** in [`workflow/validation-checklist.md`](./validation-checklist.md) --- @@ -181,25 +182,36 @@ See [`workflow/validation-checklist.md`](./validation-checklist.md) — every ru ## Step 5 — Build and verify +**Include the target-platform module in every `-pl` list.** Its artifact is not in `~/.m2`, so a gate +command that lists only the migrated module fails to resolve it. + +**Before the first compile after the rename commit, delete the generated files under +`/xtend-gen/` except its `.gitignore`**, e.g. +`find /xtend-gen -mindepth 1 ! -name .gitignore -delete`; otherwise the stale generated copy +of the renamed class stays on the source path, collides with the new `.java` or hides a class you +have not translated yet, and the compile result no longer tells you anything. + +**Compare `xtend-gen/` trees with `diff -r -x '.*'`** so the `._trace` sidecars are skipped. + Module-specific build first: ```bash -mvn -pl , -am verify -f ./ddk-parent/pom.xml > mvn-output.txt 2>&1 +mvn -pl :,, -am verify -f > mvn-output.txt 2>&1 ``` **PMD needs compiled classes** for type-resolution rules (`MissingOverride`, `UnnecessaryCast`, `LooseCoupling`, `UseCollectionIsEmpty`). Always compile first: ```bash -mvn clean compile pmd:check -f ./ddk-parent/pom.xml -pl -am > mvn-output.txt 2>&1 +mvn clean compile pmd:check -f -pl :, -am > mvn-output.txt 2>&1 ``` Checkstyle works on source only: ```bash -mvn checkstyle:check -f ./ddk-parent/pom.xml -pl > mvn-output.txt 2>&1 +mvn checkstyle:check -f -pl :, > mvn-output.txt 2>&1 ``` Full CI-equivalent: ```bash -mvn clean verify checkstyle:check pmd:pmd pmd:cpd pmd:check pmd:cpd-check spotbugs:check -f ./ddk-parent/pom.xml --batch-mode --fail-at-end > mvn-output.txt 2>&1 +mvn clean verify checkstyle:check pmd:pmd pmd:cpd pmd:check pmd:cpd-check spotbugs:check -f --batch-mode --fail-at-end > mvn-output.txt 2>&1 ``` Always check the final `BUILD SUCCESS/FAILURE` line. With `--fail-at-end`, intermediate lines can diff --git a/.agents/skills/xtend-to-java/workflow/validation-checklist.md b/.agents/skills/xtend-to-java/workflow/validation-checklist.md index 8694b228a2..0159952174 100644 --- a/.agents/skills/xtend-to-java/workflow/validation-checklist.md +++ b/.agents/skills/xtend-to-java/workflow/validation-checklist.md @@ -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 @@ -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 `String` moved into `.formatted()` needs `Strings.emptyIfNull(value)` (`org.eclipse.xtext.util.Strings`, String-only overload). **Keep any `StringConcatenationClient`-valued or otherwise specially-rendered interpolation on the `StringConcatenation` chain (rule 35)**; never convert it to `.formatted()`. For other nullable values, use `Objects.toString(value, "")` (`java.util.Objects`) **only when `toString()` is exactly what `append` would render** (boxed numbers, plain `CharSequence`); this preserves the "append nothing" semantics for null. | | 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. | @@ -43,7 +43,7 @@ Every rule below is a hard gate. | # | Rule | Requirement | |---|------|-------------| -| 7 | `@SuppressWarnings("nls")` | At class level when module's JDT settings have `nonExternalizedStringLiteral=warning`. Check `ddk-configuration/.settings/org.eclipse.jdt.core.prefs`. | +| 7 | `@SuppressWarnings("nls")` | At class level when module's JDT settings have `nonExternalizedStringLiteral=warning`. Check the shared settings project's `.settings/org.eclipse.jdt.core.prefs`. | | 8 | `@Override` | On every override including interface implementations. | | 15 | Dispatch methods | Keep `_` prefix. Suppress with `@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter"})`. | @@ -95,9 +95,9 @@ Every rule below is a hard gate. | # | Rule | Requirement | |---|------|-------------| -| 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. | +| 21 | Copyright headers | File starts with the repository's required 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 repository's required header 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). In `.project`, keep `org.eclipse.xtext.ui.shared.xtextBuilder` / `…xtextNature` if the module still contains Xtext-language resources the IDE must build (a `.xtext` grammar or DSL model files); otherwise remove them in the infrastructure commit, because they were there for Xtend. | ### Migration-campaign gates (learned) @@ -137,7 +137,7 @@ Every rule below is a hard gate. - [ ] Explicit visibility on every class, method, and field. - [ ] All imports updated (no wildcards, no unused, correct order). - [ ] All comments and class/member Javadoc preserved exactly. -- [ ] Copyright header is the exact Avaloq banner — **replacing** any generated-stub or Javadoc-style header the source had (not preserved from source). +- [ ] Copyright header is the repository's required header — **replacing** any generated-stub or Javadoc-style header the source had (not preserved from source). - [ ] Checked exceptions use explicit Java contracts: exact `throws` types where compatible, or an explicitly reviewed project-established boundary strategy with the original exception preserved as the cause. - [ ] The `.xtend` no longer exists — renamed to `.java` via `git mv` then translated in place; no `.xtend`/`.java` pair coexists. See rule 22. @@ -145,9 +145,9 @@ Every rule below is a hard gate. ## Build / test gates -- [ ] `mvn -pl : -am -DskipTests compile -f ./ddk-parent/pom.xml` — passes. -- [ ] `mvn verify -f ./ddk-parent/pom.xml` — passes (or failures are known flakes). -- [ ] `mvn checkstyle:check pmd:check spotbugs:check -f ./ddk-parent/pom.xml` — passes. +- [ ] `mvn -pl :,: -am -DskipTests compile -f ` — passes. +- [ ] `mvn verify -f ` — passes (or failures are known flakes). +- [ ] `mvn checkstyle:check pmd:check spotbugs:check -f ` — passes. ---