[Java. Formatting] Move semicolon to the new line with long chain calls implementation

IDEA-291765

GitOrigin-RevId: b67a9be7fd9d57febe95f2d65fc5fae3dfb4151c
This commit is contained in:
Georgii Ustinov
2023-11-29 11:35:43 +00:00
committed by intellij-monorepo-bot
parent d657d89ed1
commit 9cf690d4e1
4 changed files with 111 additions and 39 deletions
@@ -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<ASTNode> 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<? super ASTNode> 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();
@@ -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<Block> buildBlocksFrom(List<? extends ASTNode> nodes) {
List<ChainedCallChunk> methodCall = splitMethodCallOnChunksByDots(nodes);
List<ChainedCallChunk> 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<ChainedCallChunk> 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<? extends ChainedCallChunk> methodCall) {
@@ -170,12 +172,12 @@ class ChainMethodCallsBlockBuilder {
}
@NotNull
private List<ChainedCallChunk> splitMethodCallOnChunksByDots(@NotNull List<? extends ASTNode> nodes) {
private static List<ChainedCallChunk> splitMethodCallOnChunksByDots(@NotNull List<? extends ASTNode> nodes, CommonCodeStyleSettings settings) {
List<ChainedCallChunk> result = new ArrayList<>();
List<ASTNode> 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<ASTNode> nodes, CommonCodeStyleSettings settings) {
List<ChainedCallChunk> 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<ASTNode> nodes) {
boolean isEmpty() {
@@ -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<? super ASTNode> 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();
}
}
}
@@ -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<ASTNode> 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);