diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java index 22f2160e1c21..c7b06e5b956c 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/AbstractJavaBlock.java @@ -823,7 +823,7 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo @NotNull private Block createMethodCallExpressionBlock(@NotNull ASTNode node, Wrap blockWrap, Alignment alignment, Indent indent) { final ArrayList nodes = new ArrayList<>(); - collectNodes(nodes, node); + JavaFormatterUtil.collectCallExpressionNodes(nodes, node); if (Registry.is(LegacyChainedMethodCallsBlockBuilder.COMPATIBILITY_KEY)) { return new LegacyChainedMethodCallsBlockBuilder(alignment, blockWrap, indent, mySettings, myJavaSettings, myFormattingMode).build(nodes); @@ -831,23 +831,6 @@ public abstract class AbstractJavaBlock extends AbstractBlock implements JavaBlo return new ChainMethodCallsBlockBuilder(alignment, blockWrap, indent, mySettings, myJavaSettings, myFormattingMode).build(nodes); } - private static void collectNodes(@NotNull List nodes, @NotNull ASTNode node) { - ASTNode child = node.getFirstChildNode(); - while (child != null) { - if (!FormatterUtil.containsWhiteSpacesOnly(child)) { - IElementType type = child.getElementType(); - if (type == JavaElementType.METHOD_CALL_EXPRESSION || - type == JavaElementType.REFERENCE_EXPRESSION) { - collectNodes(nodes, child); - } - else { - nodes.add(child); - } - } - child = child.getTreeNext(); - } - } - private boolean shouldAlignChild(@NotNull final ASTNode child) { int role = getChildRole(child); final IElementType nodeType = myNode.getElementType(); diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/ChainMethodCallsBlockBuilder.java b/java/java-impl/src/com/intellij/psi/formatter/java/ChainMethodCallsBlockBuilder.java index 594df45ad1f4..f2e6efe13e7e 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/ChainMethodCallsBlockBuilder.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/ChainMethodCallsBlockBuilder.java @@ -46,6 +46,8 @@ class ChainMethodCallsBlockBuilder { private final FormattingMode myFormattingMode; + private static final int MANY_METHOD_CALLS_FACTOR = 3; + ChainMethodCallsBlockBuilder(Alignment alignment, Wrap wrap, Indent indent, @@ -70,7 +72,7 @@ class ChainMethodCallsBlockBuilder { } private List buildBlocksFrom(List nodes) { - List methodCall = splitMethodCallOnChunksByDots(nodes); + List methodCall = splitMethodCallOnChunksByDots(nodes, mySettings); Wrap wrap = Wrap.createWrap(getWrapType(mySettings.METHOD_CALL_CHAIN_WRAP), true); Wrap builderMethodWrap = Wrap.createWrap(WrapType.ALWAYS, true); @@ -83,7 +85,7 @@ class ChainMethodCallsBlockBuilder { CallChunkBlockBuilder builder = new CallChunkBlockBuilder(mySettings, myJavaSettings, myFormattingMode); for (int i = 0; i < methodCall.size(); i++) { ChainedCallChunk currentCallChunk = methodCall.get(i); - if (isMethodCall(currentCallChunk) && !isBuilderMethod(currentCallChunk) || isComment(currentCallChunk)) { + if (isMethodCall(currentCallChunk) && !isBuilderMethod(currentCallChunk, mySettings) || isComment(currentCallChunk)) { if (chainedCallsAlignment == null) { chainedCallsAlignment = createCallChunkAlignment(i, methodCall); } @@ -93,7 +95,7 @@ class ChainMethodCallsBlockBuilder { } Wrap currWrap = isMethodCall(currentCallChunk) && canWrap(i, methodCall) - ? isBuilderMethod(currentCallChunk) ? builderMethodWrap : wrap + ? isBuilderMethod(currentCallChunk, mySettings) ? builderMethodWrap : wrap : null; blocks.add(builder.create(currentCallChunk.nodes, @@ -107,7 +109,7 @@ class ChainMethodCallsBlockBuilder { private int getCommonIndentSize(@NotNull List chunks) { String commonIndent = null; for (ChainedCallChunk chunk : chunks) { - if (isMethodCall(chunk) && isBuilderMethod(chunk)) { + if (isMethodCall(chunk) && isBuilderMethod(chunk, mySettings)) { String currIndent = chunk.getIndentString(); if (currIndent != null) { if (commonIndent == null) { @@ -140,9 +142,9 @@ class ChainMethodCallsBlockBuilder { return false; } - private boolean isBuilderMethod(@NotNull ChainedCallChunk chunk) { + private static boolean isBuilderMethod(@NotNull ChainedCallChunk chunk, CommonCodeStyleSettings settings) { String identifier = chunk.getIdentifier(); - return identifier != null && mySettings.isBuilderMethod(identifier); + return identifier != null && settings.isBuilderMethod(identifier); } private boolean canWrap(int chunkIndex, @NotNull List methodCall) { @@ -170,12 +172,12 @@ class ChainMethodCallsBlockBuilder { } @NotNull - private List splitMethodCallOnChunksByDots(@NotNull List nodes) { + private static List splitMethodCallOnChunksByDots(@NotNull List nodes, CommonCodeStyleSettings settings) { List result = new ArrayList<>(); List current = new ArrayList<>(); for (ASTNode node : nodes) { - if (JavaFormatterUtil.isStartOfCallChunk(mySettings, node) || node.getPsi() instanceof PsiComment) { + if (JavaFormatterUtil.isStartOfCallChunk(settings, node) || node.getPsi() instanceof PsiComment) { if (!current.isEmpty()) { result.add(new ChainedCallChunk(current)); } @@ -209,6 +211,19 @@ class ChainMethodCallsBlockBuilder { return false; } + public static boolean isLongCallChain(List nodes, CommonCodeStyleSettings settings) { + List chunks = splitMethodCallOnChunksByDots(nodes, settings); + + int methodCallCount = 0; + + for (ChainedCallChunk chunk : chunks) { + if (isMethodCall(chunk) && !isBuilderMethod(chunk, settings) && !isComment(chunk)) { + methodCallCount++; + } + } + return methodCallCount >= MANY_METHOD_CALLS_FACTOR; + } + private record ChainedCallChunk(@NotNull List nodes) { boolean isEmpty() { diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java index 072cbc492464..2c76dcfd1d70 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/JavaFormatterUtil.java @@ -23,6 +23,8 @@ import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + import static com.intellij.psi.impl.PsiImplUtil.isTypeAnnotation; public final class JavaFormatterUtil { @@ -514,4 +516,27 @@ public final class JavaFormatterUtil { return CommonCodeStyleSettings.DO_NOT_WRAP; } + + /** + * Traverses the children of the node and collects nodes with type method calls or reference expressions to the list + * @param nodes List in which the method add nodes + * @param node Node to traverse + * + */ + public static void collectCallExpressionNodes(@NotNull List nodes, @NotNull ASTNode node) { + ASTNode child = node.getFirstChildNode(); + while (child != null) { + if (!FormatterUtil.containsWhiteSpacesOnly(child)) { + IElementType type = child.getElementType(); + if (type == JavaElementType.METHOD_CALL_EXPRESSION || + type == JavaElementType.REFERENCE_EXPRESSION) { + collectCallExpressionNodes(nodes, child); + } + else { + nodes.add(child); + } + } + child = child.getTreeNext(); + } + } } diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/JavaSpacePropertyProcessor.java b/java/java-impl/src/com/intellij/psi/formatter/java/JavaSpacePropertyProcessor.java index 3152edaf4468..438fa3ebc009 100644 --- a/java/java-impl/src/com/intellij/psi/formatter/java/JavaSpacePropertyProcessor.java +++ b/java/java-impl/src/com/intellij/psi/formatter/java/JavaSpacePropertyProcessor.java @@ -12,6 +12,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; @@ -457,23 +458,27 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { else if (myRole1 == ChildRole.CLASS_INITIALIZER) { if (myRole2 == ChildRole.RBRACE) { int minLineFeeds = getMinLineFeedsBetweenRBraces(myChild1); - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, minLineFeeds, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, minLineFeeds, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); } else if (myRole2 == ChildRole.CLASS) { setAroundClassSpacing(); } else { int blankLines = myJavaSettings.BLANK_LINES_AROUND_INITIALIZER + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } else if (myRole1 == ChildRole.CLASS) { if (myRole2 == ChildRole.RBRACE) { - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, getLineBreaksBeforeClassEnd(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); + myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, getLineBreaksBeforeClassEnd(), mySettings.KEEP_LINE_BREAKS, + mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); } else { int blankLines = mySettings.BLANK_LINES_AROUND_CLASS + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } else if (myRole2 == ChildRole.CLASS) { @@ -482,7 +487,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { } else { int blankLines = mySettings.BLANK_LINES_AROUND_CLASS + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } @@ -495,7 +501,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { } else { int blankLines = getLinesAroundField() + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } @@ -516,7 +523,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { } else { int blankLines = getLinesAroundField() + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } else if (myRole2 == ChildRole.COMMA || myType2 == JavaTokenType.SEMICOLON) { @@ -570,7 +578,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { } else if (myRole1 == ChildRole.CLASS_INITIALIZER) { int blankLines = myJavaSettings.BLANK_LINES_AROUND_INITIALIZER + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE); } else { int blankLines = getLinesAroundMethod() + 1; @@ -591,7 +600,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { } else { int blankLines = getLinesAroundMethod() + 1; - myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); + myResult = + Spacing.createSpacing(0, Integer.MAX_VALUE, blankLines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS); } } return myResult != null; @@ -695,7 +705,7 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { int keepLines = myType1 == JavaTokenType.C_STYLE_COMMENT ? mySettings.KEEP_BLANK_LINES_BETWEEN_PACKAGE_DECLARATION_AND_HEADER : mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS; - myResult = Spacing.createSpacing(0, 0, lf, mySettings.KEEP_LINE_BREAKS,keepLines); + myResult = Spacing.createSpacing(0, 0, lf, mySettings.KEEP_LINE_BREAKS, keepLines); } else if (myType1 == JavaElementType.IMPORT_LIST) { @@ -758,7 +768,8 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { myResult = Spacing.createSpacing(1, 1, 1, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); } else { - myResult = Spacing.createDependentLFSpacing(0, 1, myParent.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); + myResult = + Spacing.createDependentLFSpacing(0, 1, myParent.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); } } else { @@ -1108,6 +1119,10 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { else if (myRole1 == ChildRole.TYPE || myRole2 == ChildRole.TYPE) { createSpaceInCode(true); } + else if (myType2 == JavaTokenType.SEMICOLON && isAllowedToMoveSemicolonInLongCallChain()) { + myResult = + Spacing.createDependentLFSpacing(0, 0, myChild1.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); + } else if (myType2 == JavaTokenType.SEMICOLON) { createSpaceProperty(false, false, 0); } @@ -1131,6 +1146,11 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { || myRole1 == ChildRole.TYPE) { createSpaceInCode(true); } + else if (myType2 == JavaTokenType.SEMICOLON && + isAllowedToMoveSemicolonInLongCallChain()) { + myResult = + Spacing.createDependentLFSpacing(0, 0, myChild1.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); + } else if (myType2 == JavaTokenType.SEMICOLON) { PsiElement pp = myParent.getParent(); if (pp instanceof PsiDeclarationStatement) { @@ -1140,11 +1160,21 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { return; } } - createSpaceProperty(false, false, 0); } } + private boolean isInsidePsiForStatement() { + PsiElement pp = myParent.getParent(); + if (pp instanceof PsiDeclarationStatement) { + PsiElement ppp = pp.getParent(); + if (ppp instanceof PsiForStatement) { + return true; + } + } + return false; + } + private static boolean isFinalKeywordBefore(ASTNode node) { ASTNode prevLeaf = TreeUtil.prevLeaf(node); if (prevLeaf != null && prevLeaf.getElementType() == TokenType.WHITE_SPACE) { @@ -1662,7 +1692,11 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { @Override public void visitStatement(@NotNull PsiStatement statement) { - if (myRole2 == ChildRole.CLOSING_SEMICOLON) { + if (myRole2 == ChildRole.CLOSING_SEMICOLON && + isAllowedToMoveSemicolonInLongCallChain()) { + myResult = + Spacing.createDependentLFSpacing(0, 0, myChild1.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE); + } else if (myRole2 == ChildRole.CLOSING_SEMICOLON) { createSpaceInCode(false); } @@ -1987,6 +2021,21 @@ public final class JavaSpacePropertyProcessor extends JavaElementVisitor { return text; } + private boolean isAllowedToMoveSemicolonInLongCallChain() { + if (!myJavaSettings.WRAP_SEMICOLON_AFTER_CALL_CHAIN) return false; + + if (Registry.is(LegacyChainedMethodCallsBlockBuilder.COMPATIBILITY_KEY)) return false; + + if(!(myChild1 instanceof PsiMethodCallExpression) || myType1 != JavaElementType.METHOD_CALL_EXPRESSION) return false; + + if (isInsidePsiForStatement()) return false; + + List nodes = new ArrayList<>(); + JavaFormatterUtil.collectCallExpressionNodes(nodes, myChild1); + + return ChainMethodCallsBlockBuilder.isLongCallChain(nodes, mySettings); + } + private static boolean sameTokens(IElementType type, String text, IElementType reparsedType, String reparsedText) { return reparsedType == type || reparsedType == JavaTokenType.IDENTIFIER && ElementType.KEYWORD_BIT_SET.contains(type) && Objects.equals(text, reparsedText);