Skip to content

refactor: migrate Xtend to Java - com.avaloq.tools.ddk.xtext.generator (parser group)#1429

Merged
joaodinissf merged 6 commits into
dsldevkit:masterfrom
joaodinissf:migrate/xtend-to-java/xtext-generator-parser
Jul 13, 2026
Merged

refactor: migrate Xtend to Java - com.avaloq.tools.ddk.xtext.generator (parser group)#1429
joaodinissf merged 6 commits into
dsldevkit:masterfrom
joaodinissf:migrate/xtend-to-java/xtext-generator-parser

Conversation

@joaodinissf

@joaodinissf joaodinissf commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Migrates the parser group of com.avaloq.tools.ddk.xtext.generator (parser/antlr + parser/common, 6 files) from Xtend to hand-written Java:
AbstractAnnotationAwareAntlrGrammarGenerator, AnnotationAwareAntlrContentAssistGrammarGenerator, AnnotationAwareAntlrGrammarGenerator, AnnotationAwareXtextAntlrGeneratorFragment2, GrammarRuleAnnotations, PredicatesNaming.

Notes

  • The ANTLR StringConcatenation code-generation templates were byte-verified against the xtend-gen ground truth.
  • Local gate green: clean verify checkstyle:check pmd:check pmd:cpd-check spotbugs:check (0 violations).
  • Review round 3 (2026-07-11): all remaining static append-runs coalesced into text blocks / .formatted() (net −375 lines); StringConcatenation kept only where load-bearing (appendImmediate separators, two-arg indented appends of multi-line values, newLineIfNotEmpty after dynamic values, import-registering append(TypeReference)). Byte fidelity: executable old-vs-new harness (39 cases) + independent per-hunk adversarial review (0 divergences); branch rebased onto current master.
  • Split module: this is one half of xtext.generator; the per-module Xtend build infra (xtend-gen wiring, MANIFEST, build.properties/.classpath/.project) is intentionally left untouched — it will be cleaned once both the parser and the builder PRs merge (12 sibling .xtend remain).

Blind adversarial review verdict: SHIP (only 2 invented-Javadoc nits).

joaodinissf added a commit to joaodinissf/dsl-devkit that referenced this pull request Jun 17, 2026
…r (builder group)

Migrates 12 generator fragment/support classes from Xtend to Java: BundleVersionStripperFragment, DefaultFragmentWithOverride, builder/BuilderIntegrationFragment2, builder/LspBuilderIntegrationFragment2, builder/StandaloneBuilderIntegrationFragment2, formatting/FormatterFragment2, languageconstants/LanguageConstantsFragment2, model/project/ProjectConfig, modelinference/ModelInferenceFragment2, resourceFactory/ResourceFactoryFragment2, ui/compare/CompareFragment2, and ui/contentAssist/AnnotationAwareContentAssistFragment2. Xtend rich-string templates were rewritten as explicit StringConcatenation/string building, implicit getters/extension methods made explicit, and null-safe operators expanded to guarded Java. Behaviour is preserved; only language form changes. This is the builder half of the com.avaloq.tools.ddk.xtext.generator split (parser half tracked in dsldevkit#1429); per-module Xtend infrastructure cleanup is deferred until both halves merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf
joaodinissf marked this pull request as ready for review June 17, 2026 16:25
@joaodinissf
joaodinissf requested a review from rubenporras June 17, 2026 16:25

@rubenporras rubenporras left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I have seen that Claude constantly forgets that he should try to use multi-line strings for readability. I use to interject it as steering once the skill has been kicked in, and that has worked fine so far.


protected CharSequence compileLexerMembers(final Grammar it, final AntlrOptions options) {
final StringConcatenation builder = new StringConcatenation();
builder.append("@members {");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is clearly a multi line string.

protected CharSequence compileLexer(final Grammar it, final AntlrOptions options) {
final StringConcatenation builder = new StringConcatenation();
builder.append(codeConfig.getFileHeader());
builder.newLineIfNotEmpty();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a builder is justified for this case, but it would be better to remove the usage of org.eclipse.xtend2.lib.StringConcatenation, do you agree?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you see this comment?


@Override
protected String compileParserImports(final Grammar it, final AntlrOptions options) {
final StringConcatenation builder = new StringConcatenation();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is clearly a multi line string.


protected CharSequence compileParserSetTokenStreamMethod() {
final StringConcatenation builder = new StringConcatenation();
builder.append("/**");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is clearly a multi line string, and so on, I did not continue the review.

joaodinissf added a commit to joaodinissf/dsl-devkit that referenced this pull request Jun 18, 2026
Address review feedback (dsldevkit#1429): convert the static and single-statement
interpolated grammar-fragment templates to Java text blocks with .formatted()
- compileLexerMembers, compileParserImports, compileParserMemberDeclarations,
compileParserSetTokenStreamMethod, compileRuleCatch.

Builders that compose sub-results via newLineIfNotEmpty (compileLexer), use the
two-argument append(value, indent) re-indentation, or contain FOR/IF control
flow are left as StringConcatenation, per the skill's "StringBuilder for control
flow only" rule - so the org.eclipse.xtend2.lib.StringConcatenation import stays.

Separate commit on top of the migration; output proven byte-identical to the
prior code for every converted method (verified against the real
StringConcatenation runtime).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf
joaodinissf requested a review from rubenporras June 22, 2026 15:19
protected CharSequence compileLexer(final Grammar it, final AntlrOptions options) {
final StringConcatenation builder = new StringConcatenation();
builder.append(codeConfig.getFileHeader());
builder.newLineIfNotEmpty();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you see this comment?

builder.append(_grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil.getOriginalElement(it)));
builder.append("__Impl");
builder.newLineIfNotEmpty();
builder.append(" ");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this for example, I think it would truly benefit from a multi-line string

builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getSimpleName(), " ");
builder.append(" grammarAccess, ParserContext parserContext, ");
builder.append(this.predicatesNaming.getSemanticPredicatesSimpleName(it), " ");
builder.append(" predicates) {");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this

joaodinissf added a commit that referenced this pull request Jun 24, 2026
…r (builder group)

Migrates 12 generator fragment/support classes from Xtend to Java: BundleVersionStripperFragment, DefaultFragmentWithOverride, builder/BuilderIntegrationFragment2, builder/LspBuilderIntegrationFragment2, builder/StandaloneBuilderIntegrationFragment2, formatting/FormatterFragment2, languageconstants/LanguageConstantsFragment2, model/project/ProjectConfig, modelinference/ModelInferenceFragment2, resourceFactory/ResourceFactoryFragment2, ui/compare/CompareFragment2, and ui/contentAssist/AnnotationAwareContentAssistFragment2. Xtend rich-string templates were rewritten as explicit StringConcatenation/string building, implicit getters/extension methods made explicit, and null-safe operators expanded to guarded Java. Behaviour is preserved; only language form changes. This is the builder half of the com.avaloq.tools.ddk.xtext.generator split (parser half tracked in #1429); per-module Xtend infrastructure cleanup is deferred until both halves merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf

Copy link
Copy Markdown
Collaborator Author

Coalesced all the self-contained static append/newLine runs across the ANTLR generators into text blocks (byte-verified against the Xtend compiler's output), and left compileLexer + the dynamic builders on StringConcatenation since they rely on newLineIfNotEmpty empty-suppression that a plain StringBuilder can't replicate without changing the generated grammar.

— drafted by Claude, reviewed by João

@joaodinissf
joaodinissf requested a review from rubenporras June 25, 2026 15:11
joaodinissf added a commit to joaodinissf/dsl-devkit that referenced this pull request Jul 5, 2026
Address review feedback (dsldevkit#1429): convert the static and single-statement
interpolated grammar-fragment templates to Java text blocks with .formatted()
- compileLexerMembers, compileParserImports, compileParserMemberDeclarations,
compileParserSetTokenStreamMethod, compileRuleCatch.

Builders that compose sub-results via newLineIfNotEmpty (compileLexer), use the
two-argument append(value, indent) re-indentation, or contain FOR/IF control
flow are left as StringConcatenation, per the skill's "StringBuilder for control
flow only" rule - so the org.eclipse.xtend2.lib.StringConcatenation import stays.

Separate commit on top of the migration; output proven byte-identical to the
prior code for every converted method (verified against the real
StringConcatenation runtime).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/xtext-generator-parser branch from 92b87bc to 52001c4 Compare July 5, 2026 16:02

@rubenporras rubenporras left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not looked all the places where this patterns make sense but I think there are more.

builder.append(_lexerSuperClass);
builder.append(".class)");
builder.newLineIfNotEmpty();
builder.append(" ");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and the next append can be put together, can they not?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

(comment written by Claude Code on João's behalf)

builder.append(_typeRef, " ");
builder.append(".CONTENT_ASSIST))");
builder.newLineIfNotEmpty();
builder.append(" ");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

(comment written by Claude Code on João's behalf)

.addConfiguredBinding("IProposalConflictHelper", new StringConcatenationClient() {
@Override
protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) {
builder.append("binder.bind(org.eclipse.xtext.ide.editor.contentassist.IProposalConflictHelper.class)");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can all be a multi line string

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — text block.

(comment written by Claude Code on João's behalf)

TypeReference _lexerSuperClass = naming.getLexerSuperClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar());
builder.append(_lexerSuperClass);
builder.append(".class)");
builder.newLineIfNotEmpty();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
builder.newLineIfNotEmpty();
builder.newLine();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — and every sibling newLineIfNotEmpty() whose line ends in a static literal is now a plain newLine() across all four files (the remaining ones guard dynamic values that may end in a newline or retract whitespace-only lines, so they are load-bearing).

(comment written by Claude Code on João's behalf)

builder.append("import ");
builder.append(this.getGrammarNaming().getInternalParserSuperClass(it).getName());
builder.append(';');
builder.newLineIfNotEmpty();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
builder.newLineIfNotEmpty();
builder.newLine();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — and the surrounding static import runs are coalesced into text blocks.

(comment written by Claude Code on João's behalf)

builder.append(_grammarAccessExtensions.ruleName(it));
builder.newLineIfNotEmpty();
if (this.annotations.hasNoBacktrackAnnotation(it)) {
builder.append(" ");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be just a multi line string

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — text block.

(comment written by Claude Code on João's behalf)

joaodinissf and others added 6 commits July 11, 2026 16:52
…r (parser group)

Migrate parser group (6 files) to hand-written Java; ANTLR StringConcatenation templates byte-verified vs xtend-gen; local gate green.

Split of com.avaloq.tools.ddk.xtext.generator: per-module Xtend infra cleanup deferred until both parser+builder PRs merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review feedback (dsldevkit#1429): convert the static and single-statement
interpolated grammar-fragment templates to Java text blocks with .formatted()
- compileLexerMembers, compileParserImports, compileParserMemberDeclarations,
compileParserSetTokenStreamMethod, compileRuleCatch.

Builders that compose sub-results via newLineIfNotEmpty (compileLexer), use the
two-argument append(value, indent) re-indentation, or contain FOR/IF control
flow are left as StringConcatenation, per the skill's "StringBuilder for control
flow only" rule - so the org.eclipse.xtend2.lib.StringConcatenation import stays.

Separate commit on top of the migration; output proven byte-identical to the
prior code for every converted method (verified against the real
StringConcatenation runtime).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…textAntlrGeneratorFragment2

Replace the single-line StringConcatenation in the keyword-condition map
lambda with String.formatted(); the result is byte-identical. The multi-line
condBuilder (which relies on newLine/newLineIfNotEmpty) is left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eAntlrGrammarGenerator

Collapse the pure-static append runs (parser imports block, the @members
comment, the constructor body, and the getFirstRuleName/getGrammarAccess
methods) into Java text blocks. Single-line two-arg appends (rule name,
grammar-access simple name) fold to %s as the re-indent is a no-op for
single-line values. Each run proven byte-identical via the
StringConcatenation-vs-text-block harness; loops/appendImmediate/dynamic
parts are left intact. Generated grammar unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold self-contained pure-static append/newLine runs across the AnnotationAware
ANTLR generators into Java text blocks: ContentAssist (java.util + super/DFA/
grammarAccess imports, getValueForTokenName body, @init/finally blocks),
AnnotationAwareAntlrGrammarGenerator (backtrack empty-action blocks, @after,
lexer-import comment), and Fragment2 (createParserContext Javadoc, NameMappings
get/set, isReparseSupported, getDefaultRuleName).

Dynamic grammar-element building, import-managed TypeReference appends, loop
bodies, and appendImmediate separators keep their StringConcatenation builders.
Generated output is byte-identical to the prior append runs (verified
append-for-append against xtend-gen).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses the third review round: every static append run across the
parser-group generators is now a text block or a single coalesced
append, simple interpolations use .formatted(), and four single-line
StringConcatenation builders are plain string concatenation.

newLineIfNotEmpty() is reduced to newLine() only where the last append
on the line is a static literal - provably identical, since the guard
is line-scoped and only behaves differently when the current line is
empty or whitespace-only. StringConcatenation remains where it is
load-bearing: appendImmediate loop separators, two-arg
append(value, indent) of multi-line values (continuation re-indent),
newLineIfNotEmpty() after dynamic values that may be empty or
newline-terminated, and import-registering append(TypeReference/Class)
calls.

Byte fidelity proven by an executable old-vs-new harness (39 cases
incl. appendImmediate loops at 0/1/3 elements, newline-terminated
dynamic values, and %-bearing names) and an independent adversarial
per-hunk review of the full diff (0 divergences).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ANGM7L3BaEGfGUmPVKRt4
@joaodinissf
joaodinissf force-pushed the migrate/xtend-to-java/xtext-generator-parser branch from 52001c4 to 30b0570 Compare July 11, 2026 15:58
@joaodinissf

Copy link
Copy Markdown
Collaborator Author

Done — swept all six files, not just the anchors: static append runs → text blocks, simple interpolations → .formatted(), four single-line builders → plain concatenation (net −375 lines). StringConcatenation remains only where it is load-bearing: appendImmediate loop separators, two-arg append(value, indent) of multi-line values, newLineIfNotEmpty() after dynamic values, and the import-registering append(TypeReference) calls. Byte fidelity proven by an old-vs-new harness plus an independent per-hunk review; full gate green.

(comment written by Claude Code on João's behalf)

joaodinissf added a commit that referenced this pull request Jul 13, 2026
…va skill

Adds rules/04-templates.md section 4.8 documenting the source-verified
StringConcatenation semantics that govern coalescing append-chains into
text blocks: append(String) newline normalization (the formal basis for
coalescing), the line-scoped whitespace-retracting behaviour of
newLineIfNotEmpty(), and the two-arg append(value, indent) continuation
re-indent. The conversion rule: newLineIfNotEmpty() may become newLine()
only when the last append on the current line is a static non-whitespace
literal; it must stay after dynamic line-tails and after indent +
conditional values; two-arg appends of potentially multi-line values are
never folded; appendImmediate loops stay untouched. Validation checklist
gains row 35 enforcing the rule.

Distilled from the #1429 round-3 review rework, where these distinctions
were byte-verified against an executable harness and an adversarial
per-hunk review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ANGM7L3BaEGfGUmPVKRt4
@joaodinissf
joaodinissf merged commit 9025f31 into dsldevkit:master Jul 13, 2026
4 checks passed
joaodinissf added a commit that referenced this pull request Jul 13, 2026
Address review feedback (#1429): convert the static and single-statement
interpolated grammar-fragment templates to Java text blocks with .formatted()
- compileLexerMembers, compileParserImports, compileParserMemberDeclarations,
compileParserSetTokenStreamMethod, compileRuleCatch.

Builders that compose sub-results via newLineIfNotEmpty (compileLexer), use the
two-argument append(value, indent) re-indentation, or contain FOR/IF control
flow are left as StringConcatenation, per the skill's "StringBuilder for control
flow only" rule - so the org.eclipse.xtend2.lib.StringConcatenation import stays.

Separate commit on top of the migration; output proven byte-identical to the
prior code for every converted method (verified against the real
StringConcatenation runtime).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joaodinissf
joaodinissf deleted the migrate/xtend-to-java/xtext-generator-parser branch July 13, 2026 08:17
joaodinissf added a commit that referenced this pull request Jul 13, 2026
Enabling CPD (#1397, pmd.cpd.min 100000->100) and migrating the
AnnotationAware ANTLR generators to Java (#1429) each passed CI in
isolation, but their combination on master surfaced 4 intra-file
duplications in the migrated generators that neither PR's build saw:
CPD was still effectively disabled when #1429's checks ran, and #1397's
baseline predated #1429's Java. The snapshot full scan caught it,
leaving master red at cpd-check.

These are faithfully migrated Xtend generators with parallel
rule-emission structure, baselined with file-scoped CPD-OFF markers
consistent with #1397's treatment of the analogous FormatJvmModelInferrer.

Verified locally: pmd:cpd-check / checkstyle:check / pmd:check all green
on com.avaloq.tools.ddk.xtext.generator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants