Skip to content

Conversation

@Jenson3210
Copy link
Contributor

What's changed?

Introduced all properties in the WrappingAndBraces style and partially support their wrapping.
Added additional support for a bunch of properties.
Refactored the existing support in the WrappingAndBracesVisitor to all do their logic through the same "visitSpace" concept rather than dedicated visitors for every type of J element.

What's your motivation?

Not all of the style properties are supported yet, but this will allow us to clean up the evaluate() pattern sooner for all properties as the parent loading will have all properties for all lst elements when we've add support for these wrappings. The unsupported ones are also ones that are less likely to be used in initial usages. This is already a big improvement compared to the before-state.

There is still a small need for the TabsAndIndentsVisitor currently, but I feel we can get rid of this one in a next iteration as only very little amount of indents are now not immediately correct after WrappingAndBraces -> planning to add the little missing support to that one and then deprecate TabsAndIndents to save a traversal (actually, make up for the one that got introduced by the MergeSpacesVisitor)

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

@Jenson3210 Jenson3210 self-assigned this Dec 12, 2025
@Jenson3210 Jenson3210 added the recipe Requested Recipe label Dec 12, 2025
@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Dec 12, 2025
Jenson3210 and others added 2 commits December 12, 2025 09:36
…gAndBracesVisitor.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@openrewrite openrewrite deleted a comment from github-actions bot Dec 12, 2025
@knutwannheden
Copy link
Contributor

This is a really impressive piece of work! I particularly like the architectural approach of routing formatting logic through visitSpace(), visitContainer(), and visitRightPadded() rather than having dedicated visitor methods for every AST element type. The use of Space.Location and JContainer.Location to dispatch formatting decisions keeps the code centralized and maintainable.

The addition of comprehensive WrappingAndBracesStyle properties (force braces, alignment options, open/close newline controls, etc.) fills important gaps in the formatter's capabilities. The test coverage is thorough and demonstrates the flexibility of the new approach.

Performance Concern

I do have a concern about the performance characteristics of the current implementation. The SourcePositionService.positionOf() method prints the source file from the root up to the target element each time it's called:

Cursor printCursor = cursor;
if (!(cursor.getValue() instanceof JavaSourceFile)) {
    printCursor = cursor.dropParentUntil(c -> c instanceof JavaSourceFile);
}
javaPrinter.visit(printCursor.getValue(), printLine, printCursor.getParent());

In WrappingAndBracesVisitor, this is called for:

  • alignWhenMultiline calculations (method parameters, arguments, implements lists, try-with-resources, etc.)
  • WrapIfTooLong / ChopIfTooLong checks against hardWrapAt
  • Text block and method chain alignment

With default IntelliJ settings where several alignWhenMultiline options are enabled, a typical source file with many methods could trigger hundreds of these calls. Each call traverses all AST nodes from the file start up to the target element, resulting in O(n^2) complexity in the worst case, where n is the file size.

Potential Solutions

A few ideas that might help:

1. Print from a local anchor point

Instead of printing from the file root, print from a nearby anchor (e.g., the enclosing statement or method). Column position within a statement is what matters for most wrapping decisions, and statements are typically short enough that printing them is cheap.

2. Track column position incrementally

Similar to how JavaPrinter tracks its output position as it traverses, a formatting visitor could maintain column position as state:

  • Reset column on newlines
  • Add to column count when visiting content
  • Use tracked position directly for alignment/wrapping decisions

This would give O(n) traversal instead of O(n^2).

3. Hybrid approach

Keep the centralized visitSpace()/visitContainer() architecture (avoiding per-element overrides), but combine it with:

  • Incremental indentation tracking (like TabsAndIndentsVisitor's lastIndent message)
  • Local printing only when column position is needed for alignment

This would preserve the PR's architectural elegance while avoiding the performance pitfall.


Overall this is heading in a great direction - I just want to flag the performance aspect before it becomes an issue on larger codebases.

@Jenson3210
Copy link
Contributor Author

Jenson3210 commented Dec 12, 2025

Thanks @knutwannheden for this!
That is indeed the reason for this one being a draft.

I am looking at the TabsAndIndentsVisitor right now to see how I could reuse patterns from over there to not have to print entire file all the time.

The other approach of anchor-printing also popped up in my mind. Printing as of the first element that starts on a new line would already prevent the entire file of being printed all the time.
That would have to export another kind of Span object as it won't have accurate line numbers (which we do not need for formatting)

I also considered just adding the new line only and leaving indentation to the TabsAndIndentsVisitor, but that will give issues later as the WrapIfLong needs to know the indent of other container elements before it can decide if it is long or now.

@Jenson3210 Jenson3210 marked this pull request as ready for review December 18, 2025 17:59
@timtebeek
Copy link
Member

There's one thread still unresolved it seems, but other than that what I've seen looks good to me; thanks for the massive effort here!

Copy link
Member

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

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

Approved from my side already. Might need a coordinated merge & release given the packages changed.

Comment on lines -39 to -49
@Value
@With
public static class MethodDeclarationParameters {
Boolean alignWhenMultiple;
}

@Value
@With
public static class RecordComponents {
Boolean alignWhenMultiple;
}
Copy link
Member

Choose a reason for hiding this comment

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

Since these classes are in a style package, I think we'd need a coordinated release right? Given that if ("tree".equals(part) || "marker".equals(part) || "style".equals(part)) we spotted earlier today

@github-project-automation github-project-automation bot moved this from In Progress to Ready to Review in OpenRewrite Jan 5, 2026
@Jenson3210
Copy link
Contributor Author

We should be good i guess. The evaluate method handles the package changes in cli and it also handled it for previous changes. But better safe than sorry 😊

@timtebeek timtebeek requested a review from sambsnyd January 8, 2026 18:02
# Conflicts:
#	rewrite-java/src/test/java/org/openrewrite/java/format/WrapMethodChainsTest.java
Copy link
Contributor

@knutwannheden knutwannheden left a comment

Choose a reason for hiding this comment

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

I haven't reviewed the visitor logic. I think the best would be to run the formatter over some repos and see if we detect any regressions (e.g. by comparing patches) or if we find some more cases we can fix.

new WrappingAndBracesStyle.Annotations(DoNotWrap),
new WrappingAndBracesStyle.Annotations(DoNotWrap),
new WrappingAndBracesStyle.Annotations(DoNotWrap)
new WrappingAndBracesStyle.KeepWhenFormatting(true, true, true, true, false, false, false, false),
Copy link
Contributor

Choose a reason for hiding this comment

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

Please verify:

Suggested change
new WrappingAndBracesStyle.KeepWhenFormatting(true, true, true, true, false, false, false, false),
new WrappingAndBracesStyle.KeepWhenFormatting(true, true, true, false, false, false, false, false),

new WrappingAndBracesStyle.MethodParentheses(false, false),
new WrappingAndBracesStyle.ChainedMethodCalls(LineWrapSetting.DoNotWrap, false, false, emptyList(), false, false),
new WrappingAndBracesStyle.IfStatement(WrappingAndBracesStyle.ForceBraces.DoNotForce, false, true),
new WrappingAndBracesStyle.ForStatement(LineWrapSetting.DoNotWrap, false, false, false, WrappingAndBracesStyle.ForceBraces.DoNotForce),
Copy link
Contributor

Choose a reason for hiding this comment

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

Please verify:

Suggested change
new WrappingAndBracesStyle.ForStatement(LineWrapSetting.DoNotWrap, false, false, false, WrappingAndBracesStyle.ForceBraces.DoNotForce),
new WrappingAndBracesStyle.ForStatement(LineWrapSetting.DoNotWrap, true, false, false, WrappingAndBracesStyle.ForceBraces.DoNotForce),

Comment on lines -49 to -53
@Option(displayName = "Remove custom line breaks",
description = "Do you want to remove custom line breaks? (default false)",
required = false)
@Nullable
Boolean removeCustomLineBreaks;
Copy link
Member

Choose a reason for hiding this comment

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

I am glad to see this go 👍

Comment on lines 121 to 124

@ToBeRemoved(after = "30-11-2025", reason = "Replace me with org.openrewrite.style.StyleHelper.addStyleMarker now available in parent runtime")
@ToBeRemoved(after = "30-01-2025", reason = "Replace me with org.openrewrite.style.StyleHelper.addStyleMarker now available in parent runtime")
private static <T extends SourceFile> T addStyleMarker(T t, List<NamedStyles> styles) {
if (!styles.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

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

Why did this date go back in time? Probably safe to remove this now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I missed the year. I would not like to introduce the risk of NoSuchMethodErrors in this PR.
Subsequent PR will follow that will remove this one. Updated the dates of these annotations so we can remove all of them at once.

Comment on lines +41 to +42
int endColumn;
int maxColumn;
Copy link
Member

Choose a reason for hiding this comment

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

What is the difference between "end column" and "max column" ? I would have assumed these were the same... for that matter why do we need this as a separate concept from Span ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are 2 question in this comment afaics.

  • Difference between endColumn and maxColumn:
    Some elements will span multiple lines. The endColumn is the column on which the entire lst element printed is ending. The maxColumn is the width of the printed block. Whichever line is the longest will have its length in the maxColumn.

  • Difference between Span and ColSpan:
    For Span (which has line information), we have to print the file as of the SourceFile's beginning. For Colspan, we only have to print the lst element which starts on a new line (as we do not care about the lines).
    During calculations in WrappingAndBraces / Indents ... we only need column information. This saves a lot of printing during the calculation and then also reduces the runtime of these visitors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

recipe Requested Recipe

Projects

Status: Ready to Review

Development

Successfully merging this pull request may close these issues.

5 participants