-
Notifications
You must be signed in to change notification settings - Fork 466
ChopIfTooLong for methodInvocation chains #6112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Jenson3210
wants to merge
9
commits into
main
Choose a base branch
from
chop-methodinvocations-if-too-long
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0e4652f
ChopIfTooLong for methodInvocation chains
Jenson3210 e4af05b
license header
Jenson3210 2efcaad
best practices
Jenson3210 c9611c5
Merge branch 'main' into chop-methodinvocations-if-too-long
Jenson3210 cf8de40
Update rewrite-java/src/main/java/org/openrewrite/java/format/WrapMet…
Jenson3210 37c8640
Removed unneeded comment
Jenson3210 29fb5ec
Use line length calculator
Jenson3210 300100a
license + eol
Jenson3210 f8fb518
comments
Jenson3210 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
rewrite-java/src/main/java/org/openrewrite/java/format/LengthCalculator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright 2025 the original author or authors. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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<?, PrintOutputCapture<TreeVisitor<?, ?>>> printer = tree.printer(cursor); | ||
PrintOutputCapture<TreeVisitor<?, ?>> 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); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
rewrite-java/src/test/java/org/openrewrite/java/format/LengthCalculatorTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright 2025 the original author or authors. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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 | ||
} | ||
} | ||
""" | ||
) | ||
); | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
rewrite-java/src/test/java/org/openrewrite/java/format/MinimumViableSpacingVisitorTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
* Copyright 2025 the original author or authors. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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<String> 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"); | ||
} | ||
} | ||
""" | ||
) | ||
); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a service mechanism for supplying language-specific implementations of concepts that appear in multiple languages. This strikes me as a good candidate for that pattern. See JavaSourceFile.service().
This is how a
JavaVisitor
can visit a kotlin source file and havemaybeAddImport()
use the kotlin version ofAddImport
to do it.I see this as the default
SourcePositionService
. While you don't need to do it right now, it could be a reasonable place to put agetLineNumber()
orgetStartColumn()
/getEndColumn()
methods which are sometimes requested. Methods like these could be relevant in any language or data format.There could be Groovy/Kotlin variations which, for example, return
false
more often inneedsSemicolon
as those languages only require them to separate multiple statements on the same line.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took this approach from
ColumnPositionCalculator
already present. Is that then also a good candidate?This method though does not
getLineNumber
but gets the length of the current element (including its newlines) starting from the first element that starts on it's line.Counting the prefix (indentation only) of that element + the length. I struggled to find an explanatory name for that. Any suggestions?