Skip to content

Commit c0589e4

Browse files
committed
Add record component wrapping and method call argument alignment (#6601)
* Add record component wrapping and method call argument alignment This adds support for IntelliJ-style formatting options requested: 1. Record Components (issue #1556): - Added WrappingAndBracesStyle.RecordComponents with wrap, alignWhenMultiline, openNewLine, and closeNewLine options - Added WrapRecordComponents visitor to apply wrapping rules - Integrated into WrappingAndBracesVisitor for class declarations - Added comprehensive tests 2. Method Call Arguments Alignment (issue #1558): - Added METHOD_INVOCATION_ARGUMENT and NEW_CLASS_ARGUMENTS handling in TabsAndIndentsVisitor.alignWhenMultiple() - This enables alignWhenMultiline for method call arguments Note: Issues #1555 (Chained Method Calls) and #1557 (Method Declaration Parameters) were already implemented - the style options for ChopIfTooLong, alignWhenMultiline, openNewLine, and closeNewLine already existed. Fixes moderneinc/customer-requests#1555 Fixes moderneinc/customer-requests#1556 Fixes moderneinc/customer-requests#1557 Fixes moderneinc/customer-requests#1558 * Fix WrappingAndBracesTest constructor calls Add missing RecordComponents parameter to constructor calls in rewrite-java-test module.
1 parent ce325fc commit c0589e4

10 files changed

Lines changed: 529 additions & 0 deletions

File tree

rewrite-java-test/src/test/java/org/openrewrite/java/format/WrappingAndBracesTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public void defaults(RecipeSpec spec) {
5151
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder"), false),
5252
new WrappingAndBracesStyle.MethodDeclarationParameters(WrapAlways, false, false, false),
5353
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
54+
null,
5455
new WrappingAndBracesStyle.Annotations(WrapAlways),
5556
new WrappingAndBracesStyle.Annotations(WrapAlways),
5657
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -938,6 +939,7 @@ void annotationWrappingWithNulls() {
938939
null,
939940
null,
940941
null,
942+
null,
941943
null),
942944
null))),
943945
java(

rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,12 @@ private boolean alignWhenMultiple(JRightPadded.Location loc) {
563563
isAlignedWhenMultipleFromStyle = () -> wrappingStyle.getChainedMethodCalls() != null && wrappingStyle.getChainedMethodCalls().getAlignWhenMultiline();
564564
intelliJDefault = false;
565565
break;
566+
case METHOD_INVOCATION_ARGUMENT:
567+
case NEW_CLASS_ARGUMENTS:
568+
//noinspection ConstantConditions
569+
isAlignedWhenMultipleFromStyle = () -> wrappingStyle.getMethodCallArguments() != null && wrappingStyle.getMethodCallArguments().getAlignWhenMultiline();
570+
intelliJDefault = false;
571+
break;
566572
default:
567573
isAlignedWhenMultipleFromStyle = () -> null;
568574
intelliJDefault = false;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.format;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.internal.ListUtils;
21+
import org.openrewrite.java.JavaIsoVisitor;
22+
import org.openrewrite.java.service.SourcePositionService;
23+
import org.openrewrite.java.style.WrappingAndBracesStyle;
24+
import org.openrewrite.java.tree.J;
25+
import org.openrewrite.java.tree.JContainer;
26+
import org.openrewrite.java.tree.JavaSourceFile;
27+
import org.openrewrite.java.tree.Space;
28+
import org.openrewrite.java.tree.Statement;
29+
import org.openrewrite.style.LineWrapSetting;
30+
31+
import java.util.List;
32+
33+
@Value
34+
@EqualsAndHashCode(callSuper = false)
35+
public class WrapRecordComponents<P> extends JavaIsoVisitor<P> {
36+
37+
WrappingAndBracesStyle style;
38+
39+
@Override
40+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P ctx) {
41+
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx);
42+
43+
try {
44+
// styles are parent loaded, so the getters may or may not be present and they may or may not return null
45+
if (style != null && style.getRecordComponents() != null && style.getRecordComponents().getWrap() != LineWrapSetting.DoNotWrap) {
46+
JContainer<Statement> primaryConstructor = c.getPadding().getPrimaryConstructor();
47+
if (primaryConstructor == null || primaryConstructor.getElements().size() <= 1) {
48+
return c;
49+
}
50+
List<Statement> components = primaryConstructor.getElements();
51+
JavaSourceFile sourceFile = getCursor().firstEnclosing(JavaSourceFile.class);
52+
if (style.getRecordComponents().getWrap() == LineWrapSetting.ChopIfTooLong) {
53+
if (sourceFile == null) {
54+
return c;
55+
}
56+
if (sourceFile.service(SourcePositionService.class).positionOf(getCursor(), primaryConstructor).getMaxColumn() <= style.getHardWrapAt()) {
57+
return c;
58+
}
59+
}
60+
61+
if (style.getRecordComponents().getCloseNewLine()) {
62+
c = c.getPadding().withPrimaryConstructor(primaryConstructor.getPadding().withElements(ListUtils.mapLast(primaryConstructor.getPadding().getElements(), rightPaddedParam -> {
63+
Space after = rightPaddedParam.getAfter();
64+
if (after.getLastWhitespace().contains("\n")) {
65+
return rightPaddedParam;
66+
}
67+
if (after.getComments().isEmpty()) {
68+
after = after.withWhitespace("\n");
69+
} else {
70+
after = after.withComments(ListUtils.mapLast(after.getComments(), comment -> comment.withSuffix("\n")));
71+
}
72+
return rightPaddedParam.withAfter(after);
73+
})));
74+
}
75+
76+
c = c.withPrimaryConstructor(ListUtils.map(components, (paramIndex, param) -> {
77+
if (param instanceof J.Empty) {
78+
return param;
79+
}
80+
Space prefix = param.getPrefix();
81+
if (prefix.getComments().isEmpty()) {
82+
if ((paramIndex != 0 || style.getRecordComponents().getOpenNewLine()) && !prefix.getWhitespace().contains("\n")) {
83+
prefix = prefix.withWhitespace("\n");
84+
}
85+
} else {
86+
int index = -1;
87+
for (int i = prefix.getComments().size() - 1; i >= 0; i--) {
88+
if (prefix.getComments().get(i).getSuffix().contains("\n")) {
89+
index = i;
90+
break;
91+
}
92+
}
93+
if (index == -1 && (paramIndex != 0 || style.getRecordComponents().getOpenNewLine())) {
94+
if (prefix.getComments().isEmpty()) {
95+
if (!prefix.getWhitespace().contains("\n")) {
96+
prefix = prefix.withWhitespace("\n");
97+
}
98+
} else {
99+
prefix = prefix.withComments(ListUtils.mapLast(prefix.getComments(),
100+
comment -> comment.withSuffix("\n")));
101+
}
102+
}
103+
}
104+
return param.withPrefix(prefix);
105+
}));
106+
}
107+
} catch (NoSuchMethodError | NoSuchFieldError ignore) {
108+
// Styles are parent-first loaded and this can happen if the style is from a older version of the runtime. Can be removed in future releases.
109+
}
110+
111+
return c;
112+
}
113+
}

rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P
196196
}
197197
}
198198
}
199+
// Apply record component wrapping for records
200+
if (j.getPadding().getPrimaryConstructor() != null) {
201+
j = (J.ClassDeclaration) new WrapRecordComponents<>(style).visit(j, p, getCursor().getParentTreeCursor());
202+
}
199203
return j;
200204
}
201205

rewrite-java/src/main/java/org/openrewrite/java/style/IntelliJ.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public static WrappingAndBracesStyle wrappingAndBraces() {
104104
new WrappingAndBracesStyle.ChainedMethodCalls(DoNotWrap, emptyList(), false),
105105
new WrappingAndBracesStyle.MethodDeclarationParameters(DoNotWrap, false, false, false),
106106
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
107+
new WrappingAndBracesStyle.RecordComponents(DoNotWrap, false, false, false),
107108
new WrappingAndBracesStyle.Annotations(WrapAlways),
108109
new WrappingAndBracesStyle.Annotations(WrapAlways),
109110
new WrappingAndBracesStyle.Annotations(WrapAlways),

rewrite-java/src/main/java/org/openrewrite/java/style/WrappingAndBracesStyle.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class WrappingAndBracesStyle implements JavaStyle {
3434
ChainedMethodCalls chainedMethodCalls;
3535
MethodDeclarationParameters methodDeclarationParameters;
3636
MethodCallArguments methodCallArguments;
37+
@Nullable RecordComponents recordComponents;
3738
@Nullable Annotations classAnnotations;
3839
@Nullable Annotations methodAnnotations;
3940
@Nullable Annotations fieldAnnotations;
@@ -54,6 +55,15 @@ public ChainedMethodCalls getChainedMethodCalls() {
5455
false) : chainedMethodCalls;
5556
}
5657

58+
public RecordComponents getRecordComponents() {
59+
//noinspection ConstantConditions
60+
return recordComponents == null ? new RecordComponents(
61+
LineWrapSetting.DoNotWrap,
62+
false,
63+
false,
64+
false) : recordComponents;
65+
}
66+
5767
@Value
5868
@With
5969
public static class IfStatement {
@@ -146,4 +156,33 @@ public Boolean getCloseNewLine() {
146156
return closeNewLine == null ? false : closeNewLine;
147157
}
148158
}
159+
160+
@Value
161+
@With
162+
public static class RecordComponents {
163+
LineWrapSetting wrap;
164+
Boolean alignWhenMultiline;
165+
Boolean openNewLine;
166+
Boolean closeNewLine;
167+
168+
public LineWrapSetting getWrap() {
169+
//noinspection ConstantConditions
170+
return wrap == null ? LineWrapSetting.DoNotWrap : wrap;
171+
}
172+
173+
public Boolean getAlignWhenMultiline() {
174+
//noinspection ConstantConditions
175+
return alignWhenMultiline == null ? false : alignWhenMultiline;
176+
}
177+
178+
public Boolean getOpenNewLine() {
179+
//noinspection ConstantConditions
180+
return openNewLine == null ? false : openNewLine;
181+
}
182+
183+
public Boolean getCloseNewLine() {
184+
//noinspection ConstantConditions
185+
return closeNewLine == null ? false : closeNewLine;
186+
}
187+
}
149188
}

rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static class Builder {
6262
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder"), false),
6363
new WrappingAndBracesStyle.MethodDeclarationParameters(WrapAlways, false, false, false),
6464
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
65+
null,
6566
new WrappingAndBracesStyle.Annotations(WrapAlways),
6667
new WrappingAndBracesStyle.Annotations(WrapAlways),
6768
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -449,6 +450,7 @@ void formatStreamChain() {
449450
null,
450451
null,
451452
null,
453+
null,
452454
null))),
453455
null))),
454456
java(
@@ -544,6 +546,7 @@ void formatStreamWithMultilineFilterLambda() {
544546
null,
545547
null,
546548
null,
549+
null,
547550
null))),
548551
null))),
549552
java(
@@ -619,6 +622,7 @@ void formatStreamWithMultipleMultilineLambdas() {
619622
null,
620623
null,
621624
null,
625+
null,
622626
null))),
623627
null))),
624628
java(
@@ -696,6 +700,7 @@ void formatStreamWithMixedLambdaStyles() {
696700
null,
697701
null,
698702
null,
703+
null,
699704
null))),
700705
null))),
701706
java(
@@ -757,6 +762,7 @@ void formatStreamWithComplexNestedLambda() {
757762
null,
758763
null,
759764
null,
765+
null,
760766
null))),
761767
null))),
762768
java(
@@ -835,6 +841,7 @@ void formatStreamWithMethodReferencesAndLambdas() {
835841
null,
836842
null,
837843
null,
844+
null,
838845
null))),
839846
null))),
840847
java(
@@ -899,6 +906,7 @@ void formatStreamWithPeekAndMultilineLambda() {
899906
null,
900907
null,
901908
null,
909+
null,
902910
null))),
903911
null))),
904912
java(
@@ -957,6 +965,7 @@ void preserveAlreadyFormattedStreamWithMultilineLambda() {
957965
null,
958966
null,
959967
null,
968+
null,
960969
null))),
961970
null))),
962971
java(
@@ -1034,6 +1043,7 @@ void formatStreamWithReduceMultilineLambda() {
10341043
null,
10351044
null,
10361045
null,
1046+
null,
10371047
null))),
10381048
null))),
10391049
java(
@@ -1086,6 +1096,7 @@ void formatStreamInBuilderArgument() {
10861096
null,
10871097
null,
10881098
null,
1099+
null,
10891100
null))),
10901101
null))),
10911102
java(
@@ -1139,6 +1150,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon
11391150
null,
11401151
null,
11411152
null,
1153+
null,
11421154
null))),
11431155
null).visit(formatted, p, getCursor().getParent());
11441156
return (J.CompilationUnit) new MergeSpacesVisitor().visit(cu, formatted);
@@ -1188,6 +1200,7 @@ void doNotChopIfNotTooLong(int length) {
11881200
null,
11891201
null,
11901202
null,
1203+
null,
11911204
null))),
11921205
null))),
11931206
java(

rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodDeclarationParametersTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public void defaults(RecipeSpec spec) {
4343
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder"), false),
4444
new WrappingAndBracesStyle.MethodDeclarationParameters(WrapAlways, false, true, false),
4545
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
46+
null,
4647
new WrappingAndBracesStyle.Annotations(WrapAlways),
4748
new WrappingAndBracesStyle.Annotations(WrapAlways),
4849
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -603,6 +604,7 @@ void formatLongLinesOnly() {
603604
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
604605
new WrappingAndBracesStyle.MethodDeclarationParameters(ChopIfTooLong, false, true, false),
605606
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
607+
null,
606608
new WrappingAndBracesStyle.Annotations(WrapAlways),
607609
new WrappingAndBracesStyle.Annotations(WrapAlways),
608610
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -649,6 +651,7 @@ void preserveMethodWithMaxLengthBelowThreshold() {
649651
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
650652
new WrappingAndBracesStyle.MethodDeclarationParameters(ChopIfTooLong, false, false, false),
651653
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
654+
null,
652655
new WrappingAndBracesStyle.Annotations(WrapAlways),
653656
new WrappingAndBracesStyle.Annotations(WrapAlways),
654657
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -680,6 +683,7 @@ void openCloseNewLine() {
680683
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
681684
new WrappingAndBracesStyle.MethodDeclarationParameters(WrapAlways, false, false, true),
682685
new WrappingAndBracesStyle.MethodCallArguments(DoNotWrap, false, false, false),
686+
null,
683687
new WrappingAndBracesStyle.Annotations(WrapAlways),
684688
new WrappingAndBracesStyle.Annotations(WrapAlways),
685689
new WrappingAndBracesStyle.Annotations(WrapAlways),

rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodInvocationArgumentsTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public void defaults(RecipeSpec spec) {
4040
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder"), false),
4141
new WrappingAndBracesStyle.MethodDeclarationParameters(DoNotWrap, false, false, false),
4242
new WrappingAndBracesStyle.MethodCallArguments(WrapAlways, false, true, false),
43+
null,
4344
new WrappingAndBracesStyle.Annotations(WrapAlways),
4445
new WrappingAndBracesStyle.Annotations(WrapAlways),
4546
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -506,6 +507,7 @@ void formatLongLinesOnly() {
506507
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
507508
new WrappingAndBracesStyle.MethodDeclarationParameters(DoNotWrap, false, false, false),
508509
new WrappingAndBracesStyle.MethodCallArguments(ChopIfTooLong, false, false, true),
510+
null,
509511
new WrappingAndBracesStyle.Annotations(WrapAlways),
510512
new WrappingAndBracesStyle.Annotations(WrapAlways),
511513
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -558,6 +560,7 @@ void preserveMethodCallWithLengthBelowThreshold() {
558560
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
559561
new WrappingAndBracesStyle.MethodDeclarationParameters(DoNotWrap, false, false, false),
560562
new WrappingAndBracesStyle.MethodCallArguments(ChopIfTooLong, false, false, false),
563+
null,
561564
new WrappingAndBracesStyle.Annotations(WrapAlways),
562565
new WrappingAndBracesStyle.Annotations(WrapAlways),
563566
new WrappingAndBracesStyle.Annotations(WrapAlways),
@@ -593,6 +596,7 @@ void openCloseNewLine() {
593596
new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, emptyList(), false),
594597
new WrappingAndBracesStyle.MethodDeclarationParameters(DoNotWrap, false, false, false),
595598
new WrappingAndBracesStyle.MethodCallArguments(WrapAlways, false, false, true),
599+
null,
596600
new WrappingAndBracesStyle.Annotations(WrapAlways),
597601
new WrappingAndBracesStyle.Annotations(WrapAlways),
598602
new WrappingAndBracesStyle.Annotations(WrapAlways),

0 commit comments

Comments
 (0)