diff --git a/rewrite-core/src/main/java/org/openrewrite/style/LineWrapSetting.java b/rewrite-core/src/main/java/org/openrewrite/style/LineWrapSetting.java index 5c442f6a49..8d4ba23bfd 100644 --- a/rewrite-core/src/main/java/org/openrewrite/style/LineWrapSetting.java +++ b/rewrite-core/src/main/java/org/openrewrite/style/LineWrapSetting.java @@ -16,7 +16,6 @@ package org.openrewrite.style; public enum LineWrapSetting { - DoNotWrap, WrapAlways; - // Eventually we would add values like WrapIfTooLong or ChopIfTooLong - + DoNotWrap, WrapAlways, ChopIfTooLong; + // Eventually we would add values like WrapIfTooLong } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/WrappingAndBracesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/WrappingAndBracesTest.java index 8fda362b0c..0eec95c8fb 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/WrappingAndBracesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/WrappingAndBracesTest.java @@ -44,6 +44,7 @@ class WrappingAndBracesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder")), new WrappingAndBracesStyle.Annotations(WrapAlways), @@ -925,6 +926,7 @@ final String method4(){ void annotationWrappingWithNulls() { rewriteRun(spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(DoNotWrap, emptyList()), null, diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/LengthCalculator.java b/rewrite-java/src/main/java/org/openrewrite/java/format/LengthCalculator.java new file mode 100644 index 0000000000..8770e4187c --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/LengthCalculator.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.format; + +import org.openrewrite.Cursor; +import org.openrewrite.PrintOutputCapture; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.Statement; + +import static java.util.Collections.emptyList; + +class LengthCalculator { + + public static int computeTreeLineLength(J tree, Cursor cursor) { + Object cursorValue = cursor.getValue(); + if (cursorValue instanceof J) { + J j = (J) cursorValue; + boolean hasNewLine = j.getPrefix().getWhitespace().contains("\n") || j.getComments().stream().anyMatch(c -> c.getSuffix().contains("\n")); + Cursor parent = cursor.getParentTreeCursor(); + boolean isCompilationUnit = parent.getValue() instanceof J.CompilationUnit; + if (!hasNewLine && !isCompilationUnit) { + return computeTreeLineLength(parent.getValue(), parent); + } + } else { + throw new RuntimeException("Unable to calculate length due to unexpected cursor value: " + cursorValue.getClass()); + } + + TreeVisitor>> printer = tree.printer(cursor); + PrintOutputCapture> capture = new PrintOutputCapture<>(printer, PrintOutputCapture.MarkerPrinter.SANITIZED); + + printer.visit(trimPrefix(tree), capture, cursor.getParentOrThrow()); + + return capture.getOut().length() + getSuffixLength(tree); + } + + private static int getSuffixLength(J tree) { + if (tree instanceof Statement && needsSemicolon((Statement) tree)) { + return 1; + } + return 0; + } + + private static boolean needsSemicolon(Statement statement) { + return statement instanceof J.MethodInvocation || statement instanceof J.VariableDeclarations || statement instanceof J.Assignment || statement instanceof J.Package; + } + + private static J trimPrefix(J tree) { + Space prefix = tree.getPrefix(); + String whitespace = prefix.getLastWhitespace().replaceFirst("^.*\\n*", ""); + prefix = prefix.withComments(emptyList()).withWhitespace(whitespace); + return tree.withPrefix(prefix); + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/MinimumViableSpacingVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/MinimumViableSpacingVisitor.java index 9512111b6c..824e5d0f6a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/MinimumViableSpacingVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/MinimumViableSpacingVisitor.java @@ -97,6 +97,14 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P return c; } + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P p) { + if (method.getName().getComments().isEmpty() && method.getName().getPrefix().getWhitespace().contains("\n")) { + method = method.withName(method.getName().withPrefix(Space.EMPTY)); + } + return super.visitMethodInvocation(method, p); + } + @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P p) { J.MethodDeclaration m = super.visitMethodDeclaration(method, p); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index b48b60c03c..582375e86f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -399,7 +399,6 @@ private int computeFirstParameterColumn(J.MethodDeclaration method) { if (firstArg.getPrefix().getLastWhitespace().contains("\n")) { int declPrefixLength = getLengthOfWhitespace(method.getPrefix().getLastWhitespace()); int argPrefixLength = getLengthOfWhitespace(firstArg.getPrefix().getLastWhitespace()); - //noinspection ConstantConditions to be backwards compatible with older style versions if (declPrefixLength >= argPrefixLength) { return declPrefixLength + style.getContinuationIndent(); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/WrapMethodChains.java b/rewrite-java/src/main/java/org/openrewrite/java/format/WrapMethodChains.java index fc02b059ec..1604ae39b6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/WrapMethodChains.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/WrapMethodChains.java @@ -43,7 +43,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P ctx try { // styles are parent loaded, so the getters may or may not be present and they may or may not return null - if (style != null && style.getChainedMethodCalls() != null && style.getChainedMethodCalls().getWrap() == LineWrapSetting.WrapAlways) { + if (style != null && style.getChainedMethodCalls() != null && + (style.getChainedMethodCalls().getWrap() == LineWrapSetting.WrapAlways || style.getChainedMethodCalls().getWrap() == LineWrapSetting.ChopIfTooLong)) { List matchers = style.getChainedMethodCalls().getBuilderMethods().stream() .map(name -> String.format("*..* %s(..)", name)) .map(MethodMatcher::new) @@ -60,6 +61,11 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, P ctx return m; } + // Not long enough to wrap + if (style.getChainedMethodCalls().getWrap() == LineWrapSetting.ChopIfTooLong && LengthCalculator.computeTreeLineLength(method, getCursor()) <= style.getHardWrapAt()) { + return m; + } + //Only update the whitespace, preserving comments if (after.getComments().isEmpty()) { after = after.withWhitespace("\n"); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/IntelliJ.java b/rewrite-java/src/main/java/org/openrewrite/java/style/IntelliJ.java index 25ec3dc39b..1a6f81f2fe 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/IntelliJ.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/IntelliJ.java @@ -99,6 +99,7 @@ public static SpacesStyle spaces() { public static WrappingAndBracesStyle wrappingAndBraces() { return new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(DoNotWrap, emptyList()), new WrappingAndBracesStyle.Annotations(WrapAlways), diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/WrappingAndBracesStyle.java b/rewrite-java/src/main/java/org/openrewrite/java/style/WrappingAndBracesStyle.java index ae0d9eecf8..89278ceefd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/WrappingAndBracesStyle.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/WrappingAndBracesStyle.java @@ -29,6 +29,7 @@ @With public class WrappingAndBracesStyle implements JavaStyle { + int hardWrapAt; IfStatement ifStatement; ChainedMethodCalls chainedMethodCalls; @Nullable Annotations classAnnotations; diff --git a/rewrite-java/src/test/java/org/openrewrite/java/format/AutoFormatTest.java b/rewrite-java/src/test/java/org/openrewrite/java/format/AutoFormatTest.java index d04adbccd2..1e23d6b1ce 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/format/AutoFormatTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/format/AutoFormatTest.java @@ -59,6 +59,7 @@ public static class Builder { new NamedStyles(UUID.randomUUID(), "junit", "Unit Test style", "Only used in unit tests", emptySet(), List.of( new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, asList("builder", "newBuilder", "stream")), new WrappingAndBracesStyle.Annotations(WrapAlways), diff --git a/rewrite-java/src/test/java/org/openrewrite/java/format/LengthCalculatorTest.java b/rewrite-java/src/test/java/org/openrewrite/java/format/LengthCalculatorTest.java new file mode 100644 index 0000000000..206d8737a9 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/format/LengthCalculatorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.format; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.java; + +class LengthCalculatorTest implements RewriteTest { + + @DocumentExample + @Test + void correctlyCalculatesLineLength() { + rewriteRun( + spec -> spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { + + @Override + public J.Package visitPackage(J.Package pkg, ExecutionContext ctx) { + assertThat(LengthCalculator.computeTreeLineLength(pkg, getCursor())).isEqualTo(20); + return super.visitPackage(pkg, ctx); + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + if ("Test".equals(classDecl.getSimpleName())) { + assertThat(LengthCalculator.computeTreeLineLength(classDecl, getCursor())).isEqualTo(381); + } + if ("Inner".equals(classDecl.getSimpleName())) { + assertThat(LengthCalculator.computeTreeLineLength(classDecl, getCursor())).isEqualTo(72); + } + return super.visitClassDeclaration(classDecl, ctx); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + assertThat(LengthCalculator.computeTreeLineLength(method, getCursor())).isEqualTo(285); + return super.visitMethodDeclaration(method, ctx); + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + assertThat(LengthCalculator.computeTreeLineLength(multiVariable, getCursor())).isEqualTo(80); + return super.visitVariableDeclarations(multiVariable, ctx); + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + assertThat(LengthCalculator.computeTreeLineLength(method, getCursor())).isEqualTo(80); + return super.visitMethodInvocation(method, ctx); + } + })), + java( + """ + package com.example; + + // Comments should not affect line length calculation + public class Test { + public void /* multiline comments can impact though */ example() { + String text = "This is a sample string to test line length calculation"; + // Another comment that is not counted + String invocation = String.valueOf("Both lines share the same length."); + } + + class Inner { + // Inner class to test nested structures + } + } + """ + ) + ); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/format/MinimumViableSpacingVisitorTest.java b/rewrite-java/src/test/java/org/openrewrite/java/format/MinimumViableSpacingVisitorTest.java new file mode 100644 index 0000000000..1829af3a77 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/format/MinimumViableSpacingVisitorTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.format; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; + +class MinimumViableSpacingVisitorTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion().dependsOn(""" + package com.example; + + public class MyObject { + public static Builder builder() { return new Builder(); } + public static Builder newBuilder() { return new Builder(); } + public static class Builder { + Builder name(String n) { return this; } + Builder age(int a) { return this; } + Builder items(java.util.List items) { return this; } + Builder nested(MyObject nested) { return this; } + MyObject build() { return new MyObject(); } + } + } + """)) + .recipe(toRecipe(() -> new MinimumViableSpacingVisitor<>(null))); + } + + @DocumentExample + @Test + void reformatChainedMethodInvocationToSingleLine() { + rewriteRun( + java( + """ + package com.example; + class Test { + void test() { + MyObject myObject = MyObject.builder() + . + + name("John"); + } + } + """, + """ + package com.example; + class Test { + void test() { + MyObject myObject = MyObject.builder() + .name("John"); + } + } + """ + ) + ); + } + + @Test + void doNotReformatChainedMethodInvocationToSingleLineWhenCommentInPrefixOfName() { + rewriteRun( + java( + """ + package com.example; + class Test { + void test() { + MyObject myObject = MyObject.builder() + . //Some comment + name("John"); + } + } + """ + ) + ); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java b/rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java index 8b35791e4b..066b0df79f 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java @@ -16,6 +16,8 @@ package org.openrewrite.java.format; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.java.JavaParser; import org.openrewrite.java.style.WrappingAndBracesStyle; import org.openrewrite.test.RecipeSpec; @@ -24,8 +26,7 @@ import java.util.Arrays; import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.style.LineWrapSetting.DoNotWrap; -import static org.openrewrite.style.LineWrapSetting.WrapAlways; +import static org.openrewrite.style.LineWrapSetting.*; import static org.openrewrite.test.RewriteTest.toRecipe; class WrapMethodChainsTest implements RewriteTest { @@ -50,6 +51,7 @@ public static class Builder { """)) .recipe(toRecipe(() -> new WrappingAndBracesVisitor<>( new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "newBuilder")), new WrappingAndBracesStyle.Annotations(WrapAlways), @@ -473,6 +475,7 @@ void test() { void formatStreamChain() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -569,6 +572,7 @@ void test() { void formatStreamWithMultilineFilterLambda() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -641,6 +645,7 @@ static class Item { void formatStreamWithMultipleMultilineLambdas() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -715,6 +720,7 @@ boolean isValid() { void formatStreamWithMixedLambdaStyles() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -773,6 +779,7 @@ List process(List items) { void formatStreamWithComplexNestedLambda() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -848,6 +855,7 @@ static class Employee { void formatStreamWithMethodReferencesAndLambdas() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -909,6 +917,7 @@ String preprocess(String s) { void formatStreamWithPeekAndMultilineLambda() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -964,6 +973,7 @@ List process(List items) { void preserveAlreadyFormattedStreamWithMultilineLambda() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -1038,6 +1048,7 @@ static class Item { void formatStreamWithReduceMultilineLambda() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("stream")), null, @@ -1087,6 +1098,7 @@ Integer sum(List numbers) { void formatStreamInBuilderArgument() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 120, new WrappingAndBracesStyle.IfStatement(false), new WrappingAndBracesStyle.ChainedMethodCalls(WrapAlways, Arrays.asList("builder", "stream")), null, @@ -1129,4 +1141,71 @@ MyObject process(List items) { ) ); } + + @Test + void chopIfTooLong() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + 70, + new WrappingAndBracesStyle.IfStatement(false), + new WrappingAndBracesStyle.ChainedMethodCalls(ChopIfTooLong, Arrays.asList("builder", "stream")), + null, + null, + null, + null, + null, + null)))), + java( + """ + package com.example; + + class Test { + void test() { + MyObject obj = MyObject.builder().name("test").age(25).build(); + } + } + """, + """ + package com.example; + + class Test { + void test() { + MyObject obj = MyObject.builder() + .name("test") + .age(25) + .build(); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(ints = {71, 72}) + void doNotChopIfNotTooLong(int length) { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new WrappingAndBracesVisitor<>(new WrappingAndBracesStyle( + length, + new WrappingAndBracesStyle.IfStatement(false), + new WrappingAndBracesStyle.ChainedMethodCalls(ChopIfTooLong, Arrays.asList("builder", "stream")), + null, + null, + null, + null, + null, + null)))), + java( + """ + package com.example; + + class Test { + void test() { + MyObject obj = MyObject.builder().name("test").age(25).build(); + } + } + """ + ) + ); + } }