Skip to content

Commit aa4b176

Browse files
joaodinissfclaude
andcommitted
build: add Xtend → Java migration skill at .agents/skills/
Adds a structured project skill encoding the Xtend → Java conversion rules and methodology for repeatable, slice-based migrations. The skill is split so an LLM can load only the parts it needs: - SKILL.md — 1-line entry point with a quick-recall cheat sheet, binding decisions, and an Xtend → Java syntax reference table. - rules/ — 10 rule files covering binding decisions, imports, variables, methods, templates, control flow, extension methods, lambdas, operator overloads, and misc syntax. - workflow/ — Steps 0–7 overview, one-file and multi-file batch conversion, validation checklist (30 quality rules + per-file pass/fail list), module infrastructure cleanup, formatting and commit protocol, and known pitfalls. - references/xtend-library-replacements.md — lookup table from Xtend stdlib calls to Java stdlib equivalents. - examples/ — two worked end-to-end conversions. Cross-tool generic location at .agents/skills/ is the source of truth. A POSIX shell script .agents/sync.sh mirrors it into .claude/skills/ (gitignored) for Claude Code's auto-discovery — symlink on macOS/Linux, recursive copy on Windows where Git symlinks need Developer Mode. The script anchors paths to its own location, so running it from either the repo root or .agents/ produces the same result. After cloning, run ./.agents/sync.sh once. Re-run after pulling changes that touch .agents/skills/. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c843b00 commit aa4b176

24 files changed

Lines changed: 1776 additions & 0 deletions
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
---
2+
name: xtend-to-java
3+
description: >
4+
Migrates Xtend (.xtend) source files to idiomatic Java 21 using a repeatable slice-based process.
5+
Use this skill whenever the user wants to convert Xtend files to Java, port a group of Xtend files,
6+
work through the xtend-to-java migration checklist, clean up PMD/Checkstyle violations in migrated files,
7+
or verify that a migrated Java file faithfully preserves the behavior of its Xtend original.
8+
Also triggers for phrases like "convert this xtend", "migrate these files", "port to java", or "xtend migration".
9+
globs:
10+
- "**/*.xtend"
11+
- "**/*.java"
12+
---
13+
14+
# Xtend → Java Migration Skill
15+
16+
> For general coding standards (copyright headers, Javadoc, import rules, naming), see `AGENTS.md` at the repository root.
17+
18+
## Overview
19+
20+
This skill drives a slice-based Xtend-to-Java migration. A *slice* is a user-defined batch of files
21+
migrated together, verified locally, and merged independently. The ground truth for behavior is always
22+
the `xtend-gen/` output — what Xtend compiled to Java before the migration.
23+
24+
## Hard Rules — Non-Negotiable
25+
26+
> **Hard rule — read BOTH `.xtend` source AND `xtend-gen/` output in full before writing ANY Java.**
27+
> No exceptions regardless of file size. A 5-line file with one template expression can have
28+
> surprising whitespace behavior. See [`workflow/overview.md`](./workflow/overview.md) for the full rationale.
29+
30+
> **Template whitespace is the #1 source of migration bugs.**
31+
> Xtend strips indentation relative to control structures. The only reliable way to know what
32+
> a template produces is to read `xtend-gen/`. Never guess from Xtend source — always verify.
33+
34+
## When to invoke
35+
36+
Use this skill when:
37+
38+
- A user asks to convert an `.xtend` file (or several) to `.java`.
39+
- A user asks to migrate a module off Xtend.
40+
- You are touching an `.xtend` file with the intent to replace it (not just edit it).
41+
42+
Do **not** invoke for normal Xtend editing where the file is staying in Xtend.
43+
44+
## Decision tree
45+
46+
1. **One file or several?**
47+
- One file → [`workflow/one-file-conversion.md`](./workflow/one-file-conversion.md).
48+
- Several files → [`workflow/multi-file-batch.md`](./workflow/multi-file-batch.md) for batching strategy.
49+
2. **Read the full workflow** in [`workflow/overview.md`](./workflow/overview.md) — Steps 0–7.
50+
3. **Internalise the binding decisions** in [`rules/00-decisions.md`](./rules/00-decisions.md).
51+
4. **Walk the rules** for the constructs that actually appear in your source file:
52+
- [`rules/01-imports-and-package.md`](./rules/01-imports-and-package.md)
53+
- [`rules/02-variables.md`](./rules/02-variables.md)
54+
- [`rules/03-methods.md`](./rules/03-methods.md)
55+
- [`rules/04-templates.md`](./rules/04-templates.md)
56+
- [`rules/05-control-flow.md`](./rules/05-control-flow.md)
57+
- [`rules/06-extension-methods.md`](./rules/06-extension-methods.md)
58+
- [`rules/07-lambdas.md`](./rules/07-lambdas.md)
59+
- [`rules/08-operator-overloads.md`](./rules/08-operator-overloads.md)
60+
- [`rules/09-misc-syntax.md`](./rules/09-misc-syntax.md)
61+
5. **Look up Xtend stdlib calls** in [`references/xtend-library-replacements.md`](./references/xtend-library-replacements.md).
62+
6. **Apply the quality checklist** in [`workflow/validation-checklist.md`](./workflow/validation-checklist.md) before declaring done.
63+
7. **Handle module infrastructure** via [`workflow/infrastructure-cleanup.md`](./workflow/infrastructure-cleanup.md) when all Xtend is removed from a module.
64+
8. **Format and commit** via [`workflow/formatting-and-commit.md`](./workflow/formatting-and-commit.md).
65+
9. **Review known pitfalls** in [`workflow/known-pitfalls.md`](./workflow/known-pitfalls.md).
66+
67+
## Binding decisions cheat-sheet
68+
69+
Detail in [`rules/00-decisions.md`](./rules/00-decisions.md). Quick recall:
70+
71+
- Target **Java 21**.
72+
- **String building**: literal → text block → `.formatted()``StringBuilder` (control flow only).
73+
- **No `var` keyword.** Explicit types everywhere.
74+
- **Explicit visibility** on classes, methods, fields.
75+
- **Java stdlib by default** for collections and streams. Guava only where genuinely superior.
76+
- **Never `String.format()`** — always `.formatted()`.
77+
- **Parameterized Log4j2**`{}` placeholders, never concatenation in loggers.
78+
79+
## Xtend → Java Syntax Reference
80+
81+
Use this table for quick mechanical transforms. Full details in the rule files.
82+
83+
### Declarations and types
84+
85+
| Xtend | Java |
86+
|-------|------|
87+
| `def methodName()` | `public ReturnType methodName()` (explicit access + return type) |
88+
| `def private methodName()` | `private ReturnType methodName()` |
89+
| `val x = expr` | `final ExplicitType x = expr;` (never `var`) |
90+
| `var x = expr` | `ExplicitType x = expr;` |
91+
| `typeof(MyClass)` | `MyClass.class` |
92+
| `def dispatch method(Type1 x)` | Keep as `_method(Type1 x)` with `@SuppressWarnings` |
93+
94+
### Operators and null handling
95+
96+
| Xtend | Java |
97+
|-------|------|
98+
| `obj?.method()` | `obj != null ? obj.method() : null` (or guard clause) |
99+
| `x ?: default` | `x != null ? x : default` |
100+
| `===` / `!==` (identity) | `==` / `!=` |
101+
| `==` / `!=` (equality) | `.equals()` / `!Objects.equals(a, b)` |
102+
| `a..b` (range) | `IntStream.rangeClosed(a, b)` |
103+
104+
### Lambdas and collections
105+
106+
| Xtend | Java |
107+
|-------|------|
108+
| `[param \| body]` | `(param) -> { return body; }` |
109+
| `[body]` (implicit `it`) | `(it) -> { return body; }` — name the parameter explicitly |
110+
| `list.filter[condition]` | `list.stream().filter(x -> condition).toList()` |
111+
| `list.map[transform]` | `list.stream().map(x -> transform).toList()` |
112+
| `list.forEach[action]` | `list.forEach(x -> action)` (or `for` loop) |
113+
| `list.exists[condition]` | `list.stream().anyMatch(x -> condition)` |
114+
| `list.forall[condition]` | `list.stream().allMatch(x -> condition)` |
115+
| `list.findFirst[condition]` | `list.stream().filter(x -> condition).findFirst().orElse(null)` |
116+
| `list.head` | `list.isEmpty() ? null : list.get(0)` |
117+
| `list.tail` | `list.subList(1, list.size())` |
118+
| `#[a, b, c]` (immutable list) | `List.of(a, b, c)` |
119+
| `#{a, b, c}` (immutable set) | `Set.of(a, b, c)` |
120+
| `newArrayList(...)` | `new ArrayList<>(List.of(...))` |
121+
| `newHashMap(...)` | `new HashMap<>(Map.of(...))` |
122+
| `list += element` | `list.add(element)` |
123+
| `list += otherList` | `list.addAll(otherList)` |
124+
| `list -= element` | `list.remove(element)` |
125+
126+
### Extension methods
127+
128+
| Xtend pattern | Java equivalent |
129+
|---------------|-----------------|
130+
| `obj.extensionMethod()` | `ExtensionClass.extensionMethod(obj)` or `helper.extensionMethod(obj)` |
131+
| `@Inject extension MyHelper helper` | `@Inject private MyHelper helper;` then `helper.method(obj)` |
132+
| `import static extension com.foo.Util.*` | `import com.foo.Util;` then `Util.method(obj)` |
133+
134+
### Property access
135+
136+
| Xtend | Java |
137+
|-------|------|
138+
| `obj.name` (property access) | `obj.getName()` |
139+
| `obj.name = value` (property write) | `obj.setName(value)` |
140+
141+
### Template expressions
142+
143+
| Xtend | Java |
144+
|-------|------|
145+
| `'''static text'''` | `"static text"` or text block |
146+
| `'''text «expr» more'''` | `"text %s more".formatted(expr)` or concatenation |
147+
| `'''«IF cond»...«ENDIF»'''` | `StringBuilder` with explicit `if` |
148+
| `'''«FOR item : list»...«ENDFOR»'''` | `StringBuilder` with `for` loop |
149+
150+
## Examples
151+
152+
Worked end-to-end examples:
153+
154+
- [`examples/00-basic-generator.md`](./examples/00-basic-generator.md)
155+
- [`examples/01-template-with-for-loop.md`](./examples/01-template-with-for-loop.md)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Example: basic generator conversion
2+
3+
A small generator with `@Inject extension`, `override`, null-safe navigation, `typeof`, template expression with `«FOR»` and `«IF»`, and a `static extension` import. Touches rules 01, 02, 03, 04, 06, 08, and 09.
4+
5+
## Xtend input
6+
7+
```xtend
8+
package com.example
9+
10+
import com.google.inject.Inject
11+
import org.eclipse.emf.ecore.resource.Resource
12+
import static org.eclipse.xtext.xbase.lib.IteratorExtensions.*
13+
import static extension com.example.NamingExtensions.*
14+
15+
class MyGenerator {
16+
@Inject extension MyHelper helper
17+
18+
override void doGenerate(Resource resource) {
19+
val config = getConfig(resource?.URI)
20+
for (model : toIterable(resource.allContents).filter(typeof(MyModel))) {
21+
model.compile
22+
}
23+
}
24+
25+
def compile(MyModel it) '''
26+
package «packageName»;
27+
«IF !imports.isNullOrEmpty»
28+
29+
«FOR imp : imports»
30+
import «imp»;
31+
«ENDFOR»
32+
«ENDIF»
33+
34+
public class «name» {
35+
}
36+
'''
37+
}
38+
```
39+
40+
## Java output
41+
42+
```java
43+
package com.example;
44+
45+
import org.eclipse.emf.common.util.URI;
46+
import org.eclipse.emf.ecore.resource.Resource;
47+
import org.eclipse.xtext.xbase.lib.IteratorExtensions;
48+
49+
import com.google.common.collect.Iterables;
50+
import com.google.inject.Inject;
51+
52+
@SuppressWarnings("nls")
53+
public class MyGenerator {
54+
55+
@Inject private MyHelper helper;
56+
57+
@Override
58+
public void doGenerate(final Resource resource) {
59+
final URI uri = resource != null ? resource.getURI() : null;
60+
final Config config = getConfig(uri);
61+
for (final MyModel model : Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), MyModel.class)) {
62+
helper.compile(model);
63+
}
64+
}
65+
66+
public CharSequence compile(final MyModel model) {
67+
final StringBuilder builder = new StringBuilder(512);
68+
builder.append("package ").append(model.getPackageName()).append(";\n");
69+
if (!(model.getImports() == null || model.getImports().isEmpty())) {
70+
builder.append("\n");
71+
for (final String imp : model.getImports()) {
72+
builder.append("import ").append(imp).append(";\n");
73+
}
74+
}
75+
builder.append("\npublic class ").append(model.getName()).append(" {\n}\n");
76+
return builder;
77+
}
78+
}
79+
```
80+
81+
## What changed
82+
83+
| Xtend construct | Java equivalent | Rule |
84+
|---|---|---|
85+
| `class MyGenerator` (implicit public) | `public class MyGenerator` | 01 |
86+
| `@Inject extension MyHelper helper` | `@Inject private MyHelper helper;` + rewrite call sites | 06 |
87+
| `override void doGenerate(...)` | `@Override public void doGenerate(...)` | 03 |
88+
| `val config = getConfig(...)` | `final Config config = getConfig(...);` | 02 |
89+
| `resource?.URI` | `resource != null ? resource.getURI() : null` | 08 |
90+
| `typeof(MyModel)` | `MyModel.class` | 09 |
91+
| `toIterable(resource.allContents).filter(typeof(MyModel))` | `Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), MyModel.class)` | Guava exception (type-safe filtering) |
92+
| `model.compile` (extension call) | `helper.compile(model)` | 06 |
93+
| `def compile(MyModel it) '''...'''` | `public CharSequence compile(final MyModel model)` + StringBuilder | 04, 09.8 |
94+
| `«packageName»` (property on `it`) | `model.getPackageName()` | 09.5 |
95+
| `!imports.isNullOrEmpty` | `!(model.getImports() == null \|\| model.getImports().isEmpty())` | 09.4 |
96+
| `«FOR imp : imports»` | `for (final String imp : model.getImports())` | 04 |
97+
| `import static extension com.example.NamingExtensions.*` | Removed (calls rewritten) | 06 |
98+
99+
## Key decisions
100+
101+
- **Template → StringBuilder** (tier 4) because the template has `«IF»` and `«FOR»` control flow.
102+
- **`it` parameter renamed** to `model` (descriptive name).
103+
- **`Iterables.filter(iter, Type.class)`** kept as Guava — genuinely more concise for type-safe filtering.
104+
- **Whitespace verified against `xtend-gen/`** — the template output was confirmed by reading the generated code.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Example: template with `«FOR»` and `«SEPARATOR»`
2+
3+
Demonstrates the `StringBuilder` conversion pattern for templates that emit repeated content with a separator. Touches rule 04 in detail.
4+
5+
## Xtend input
6+
7+
```xtend
8+
def renderArgs(List<Argument> args) '''
9+
(«FOR a : args SEPARATOR ", "»«a.type» «a.name»«ENDFOR»)
10+
'''
11+
```
12+
13+
## Java output — stream approach
14+
15+
When the body is a simple expression, use `Collectors.joining()`:
16+
17+
```java
18+
public CharSequence renderArgs(final List<Argument> args) {
19+
final StringBuilder builder = new StringBuilder();
20+
builder.append("(");
21+
builder.append(args.stream()
22+
.map(a -> a.getType() + " " + a.getName())
23+
.collect(Collectors.joining(", ")));
24+
builder.append(")\n");
25+
return builder;
26+
}
27+
```
28+
29+
## Java output — boolean flag approach
30+
31+
When the body has multi-statement logic, use a flag:
32+
33+
```java
34+
public CharSequence renderArgs(final List<Argument> args) {
35+
final StringBuilder builder = new StringBuilder();
36+
builder.append("(");
37+
boolean first = true;
38+
for (final Argument a : args) {
39+
if (!first) {
40+
builder.append(", ");
41+
}
42+
builder.append(a.getType()).append(" ").append(a.getName());
43+
first = false;
44+
}
45+
builder.append(")\n");
46+
return builder;
47+
}
48+
```
49+
50+
## What changed
51+
52+
- The `«FOR … SEPARATOR ", "»` block became either a stream with `Collectors.joining(", ")` or a boolean flag.
53+
- The `«a.type»` and `«a.name»` interpolations became `a.getType()` and `a.getName()`.
54+
- The trailing newline (implicit `\n` at end of template `'''`) is preserved by explicit `"\n"`.
55+
- **Whitespace was verified against `xtend-gen/`** — the template starts with `(` directly (no leading newline because the first `'''` line has content after the opening mark).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Xtend library replacements
2+
3+
When a converted file calls Xtend's runtime library, replace the call with a Java stdlib equivalent.
4+
Use Guava **only** where it is genuinely more concise (marked with ★).
5+
6+
| Xtend library call | Java replacement |
7+
|---|---|
8+
| `IterableExtensions.map(iter, fn)` | `iter.stream().map(fn).toList()` |
9+
| `IterableExtensions.filter(iter, fn)` | `iter.stream().filter(fn).toList()` |
10+
| `IterableExtensions.filter(iter, Type.class)` |`Iterables.filter(iter, Type.class)` (Guava — type-safe, no cast needed) |
11+
| `IterableExtensions.toList(iter)` | `StreamSupport.stream(iter.spliterator(), false).toList()` or loop |
12+
| `IterableExtensions.toSet(iter)` | `StreamSupport.stream(iter.spliterator(), false).collect(Collectors.toSet())` |
13+
| `IterableExtensions.head(iter)` |`Iterables.getFirst(iter, null)` (Guava — null-safe one-liner) |
14+
| `IterableExtensions.join(iter, sep)` | `String.join(sep, iter)` (if `Iterable<String>`) or `StreamSupport.stream(...).map(Object::toString).collect(Collectors.joining(sep))` |
15+
| `IterableExtensions.join(iter, sep, fn)` | `iter.stream().map(fn).collect(Collectors.joining(sep))` |
16+
| `IterableExtensions.exists(iter, fn)` | `iter.stream().anyMatch(fn)` |
17+
| `IterableExtensions.forall(iter, fn)` | `iter.stream().allMatch(fn)` |
18+
| `IterableExtensions.findFirst(iter, fn)` | `iter.stream().filter(fn).findFirst().orElse(null)` |
19+
| `IterableExtensions.sortBy(iter, fn)` | `iter.stream().sorted(Comparator.comparing(fn)).toList()` |
20+
| `IterableExtensions.sort(iter)` | `iter.stream().sorted().toList()` |
21+
| `IterableExtensions.isEmpty(iter)` | `!iter.iterator().hasNext()` |
22+
| `IterableExtensions.size(iter)` | `(int) StreamSupport.stream(iter.spliterator(), false).count()` or loop |
23+
| `IterableExtensions.toMap(iter, keyFn, valFn)` | `iter.stream().collect(Collectors.toMap(keyFn, valFn))` |
24+
| `IteratorExtensions.toIterable(iter)` | `(Iterable<T>) () -> iter` (keep as-is for Xtext patterns) |
25+
| `StringExtensions.isNullOrEmpty(s)` | `s == null \|\| s.isEmpty()` |
26+
| `StringExtensions.toFirstUpper(s)` | `Character.toUpperCase(s.charAt(0)) + s.substring(1)` |
27+
| `StringExtensions.toFirstLower(s)` | `Character.toLowerCase(s.charAt(0)) + s.substring(1)` |
28+
| `CollectionLiterals.newArrayList(...)` | `new ArrayList<>(List.of(...))` |
29+
| `CollectionLiterals.newHashSet(...)` | `new HashSet<>(Set.of(...))` |
30+
| `CollectionLiterals.newHashMap(...)` | `new HashMap<>(Map.of(...))` |
31+
| `ObjectExtensions.operator_doubleArrow(obj, fn)` | Inline — see [`rules/08-operator-overloads.md`](../rules/08-operator-overloads.md) §8.4 |
32+
| `Functions.Function1` | `java.util.function.Function` |
33+
| `Procedures.Procedure1` | `java.util.function.Consumer` |
34+
35+
## Guava utilities to keep as-is (do not rewrite)
36+
37+
These are already Guava or Xtext-idiomatic patterns that should stay:
38+
39+
- `Iterables.filter(iter, Type.class)` — type-safe class filtering
40+
- `Iterables.getFirst(iter, default)` — null-safe first element
41+
- `Joiner.on(sep).skipNulls()` — null-skipping joins (no Java stdlib equivalent)
42+
- `IteratorExtensions.toIterable(resource.getAllContents())` — standard Xtext pattern
43+
44+
## When in doubt
45+
46+
Check the `xtend-gen/` output to see what the Xtend compiler actually generated.
47+
The generated code shows the exact static method calls Xtend resolved to.

0 commit comments

Comments
 (0)