diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java index dce148572..735601817 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -3157,7 +3157,10 @@ private void visitRegularDot(List items, boolean needDot) { dotExpressionUpToArgs(e, Optional.of(tyargTag)); Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); dotExpressionArgsAndParen( - e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO); + e, + tyargIndent, + (trailingDereferences || needDot) ? plusFour : ZERO, + (trailingDereferences && !needDot) ? plusFour : ZERO); } length += getLength(e, getCurrentPath()); needDot = true; @@ -3248,7 +3251,9 @@ private void visitDotWithPrefix( Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); Indent argsIndent = Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO); - dotExpressionArgsAndParen(e, tyargIndent, argsIndent); + Indent huggedArgsIndent = + Indent.If.make(nameTag, ZERO, trailingDereferences ? plusFour : ZERO); + dotExpressionArgsAndParen(e, tyargIndent, argsIndent, huggedArgsIndent); needDot = true; } @@ -3321,14 +3326,14 @@ private static ExpressionTree getArrayBase(ExpressionTree node) { } private void dotExpressionArgsAndParen( - ExpressionTree expression, Indent tyargIndent, Indent indent) { + ExpressionTree expression, Indent tyargIndent, Indent indent, Indent huggedIndent) { Deque indices = getArrayIndices(expression); expression = getArrayBase(expression); switch (expression.getKind()) { case METHOD_INVOCATION -> { builder.open(tyargIndent); MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; - addArguments(methodInvocation.getArguments(), indent); + addArguments(methodInvocation.getArguments(), indent, huggedIndent); builder.close(); } default -> {} @@ -3382,17 +3387,27 @@ private void addTypeArguments(List typeArguments, Indent plusInd token(">"); } + private void addArguments(List arguments, Indent plusIndent) { + addArguments(arguments, plusIndent, ZERO); + } + /// Add arguments to a method invocation, etc. The arguments indented `plusFour`, filled, from the /// current indent. The arguments may be output two at a time if they seem to be arguments to a /// map constructor, etc. /// /// @param arguments the arguments /// @param plusIndent the extra indent for the arguments - private void addArguments(List arguments, Indent plusIndent) { + /// @param huggedPlusIndent the extra indent when a single expression lambda argument is hugged + private void addArguments( + List arguments, Indent plusIndent, Indent huggedPlusIndent) { boolean tabular = arguments.size() % 2 == 0 && argumentsAreTabular(arguments) == 2; boolean hug = canHugOnlyArgument(arguments); if (hug) { - builder.openHuggable(plusIndent, hugPoint(getOnlyElement(arguments))); + ExpressionTree argument = getOnlyElement(arguments); + builder.openHuggable( + plusIndent, + hasHuggedBlockLambda(arguments) ? ZERO : huggedPlusIndent, + hugPoint(argument)); } else { builder.open(plusIndent); } @@ -3473,6 +3488,12 @@ private static boolean canHugOnlyArgument(List argumen return arguments.size() == 1 && isHuggable(getOnlyElement(arguments)); } + private static boolean hasHuggedBlockLambda(List arguments) { + return arguments.size() == 1 + && getOnlyElement(arguments) instanceof LambdaExpressionTree lambda + && lambda.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT; + } + private void argList(List arguments) { argList(arguments, /* hug= */ false); } diff --git a/core/src/main/java/com/google/googlejavaformat/layout/LayoutBuilder.java b/core/src/main/java/com/google/googlejavaformat/layout/LayoutBuilder.java index 4f90924ca..c2bfe98db 100644 --- a/core/src/main/java/com/google/googlejavaformat/layout/LayoutBuilder.java +++ b/core/src/main/java/com/google/googlejavaformat/layout/LayoutBuilder.java @@ -128,12 +128,13 @@ public void drain() { } public void open(Indent plusIndent) { - open(plusIndent, HugRole.NONE, null); + open(plusIndent, ZERO, HugRole.NONE, null); } - private void open(Indent plusIndent, HugRole hugRole, HugPoint hugPoint) { + private void open( + Indent plusIndent, Indent huggedPlusIndent, HugRole hugRole, HugPoint hugPoint) { depth++; - events.add(new Open(plusIndent, hugRole, hugPoint)); + events.add(new Open(plusIndent, huggedPlusIndent, hugRole, hugPoint)); } /// The start of the body of a construct that can be hugged, i.e. laid out starting on the same @@ -160,15 +161,15 @@ public void hugPoint(HugPoint hugPoint) { /// [#openHuggedArguments], are the ones that are not taken when hugging, and everything up to /// `hugPoint` must then fit on the current line. /// - /// When hugged, the hugged content is indented relative to the line containing the call, instead - /// of by the level's extra indent. - public void openHuggable(Indent plusIndent, HugPoint hugPoint) { - open(plusIndent, HugRole.HUGGABLE, hugPoint); + /// When hugged, the hugged content is indented relative to the line containing the call plus + /// `huggedPlusIndent`, instead of by the enclosing levels' extra indents plus `plusIndent`. + public void openHuggable(Indent plusIndent, Indent huggedPlusIndent, HugPoint hugPoint) { + open(plusIndent, huggedPlusIndent, HugRole.HUGGABLE, hugPoint); } /// Opens the level holding the arguments of an enclosing [#openHuggable] level. public void openHuggedArguments() { - open(ZERO, HugRole.ARGUMENTS, null); + open(ZERO, ZERO, HugRole.ARGUMENTS, null); } public void close() { @@ -596,14 +597,16 @@ private static boolean isSpaceLike(Event ev) { /// closed group) into the preceding group, so it's accounted for when deciding whether that group /// fits on the line. private static Doc buildTree(List events) { - LevelBuilder base = new LevelBuilder(ZERO, HugRole.NONE, null); + LevelBuilder base = new LevelBuilder(ZERO, ZERO, HugRole.NONE, null); ArrayDeque stack = new ArrayDeque<>(); stack.addLast(base); LevelBuilder appendLevel = base; for (Event ev : events) { switch (ev) { case Open open -> - stack.addLast(new LevelBuilder(open.plusIndent(), open.hugRole(), open.hugPoint())); + stack.addLast( + new LevelBuilder( + open.plusIndent(), open.huggedPlusIndent(), open.hugRole(), open.hugPoint())); case Close close -> { LevelBuilder top = stack.removeLast(); stack.getLast().add(top); @@ -643,12 +646,14 @@ private static LineBreak forcedLineBreak(Indent plusIndent) { /// A mutable group under construction; see [#buildTree]. private static final class LevelBuilder { final Indent plusIndent; + final Indent huggedPlusIndent; final HugRole hugRole; final HugPoint hugPoint; final List items = new ArrayList<>(); - LevelBuilder(Indent plusIndent, HugRole hugRole, HugPoint hugPoint) { + LevelBuilder(Indent plusIndent, Indent huggedPlusIndent, HugRole hugRole, HugPoint hugPoint) { this.plusIndent = plusIndent; + this.huggedPlusIndent = huggedPlusIndent; this.hugRole = hugRole; this.hugPoint = hugPoint; } @@ -675,7 +680,8 @@ ImmutableList buildItems() { /// /// The hugged layout has the same content as the regular one, but the breaks of this level and /// of its [HugRole#ARGUMENTS] level (if any) are replaced by their flat text, and this level's - /// extra indent is dropped. Everything up to the hug point must fit on the current line. + /// extra indent is replaced by [#huggedPlusIndent]. Everything up to the hug point must fit on + /// the current line. private Doc buildHuggable() { ImmutableList.Builder fallback = ImmutableList.builder(); ImmutableList.Builder preferred = ImmutableList.builder(); @@ -709,7 +715,7 @@ private Doc buildHuggable() { } } Doc.Group fallbackGroup = new Doc.Group(plusIndent, fallback.build()); - Doc.Group preferredGroup = new Doc.Group(ZERO, preferred.build()); + Doc.Group preferredGroup = new Doc.Group(huggedPlusIndent, preferred.build()); int prefixWidth = canHug ? widthBefore(preferredGroup, hugPoint.marker) : -1; if (prefixWidth < 0) { return fallbackGroup; @@ -754,7 +760,9 @@ private static int widthBefore(Doc doc, Doc marker) { private sealed interface Event permits Open, Close, LineBreak, TokenOp, TokOp, SpaceOp, HugPointOp {} - private record Open(Indent plusIndent, HugRole hugRole, HugPoint hugPoint) implements Event {} + private record Open( + Indent plusIndent, Indent huggedPlusIndent, HugRole hugRole, HugPoint hugPoint) + implements Event {} private record HugPointOp(HugPoint hugPoint) implements Event {} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.input index 03a77ce5f..0d62e18d1 100644 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.input +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.input @@ -36,6 +36,7 @@ class LambdaHug { .forEach(System.out::println); CompletableFuture.supplyAsync(() -> computeSomethingExpensive(input, parameters)).thenAccept(result -> { consume(result); consumeAgain(result); }); someObject.someModerateLengthFunctionName(foo -> doOtherModerateLengthName(foo, bar, baz, qux, quux)); + SpliteratorTester.of(() -> Streams.concat(Stream.of("a"), Stream.of("b"), Stream.empty(), Stream.of("c", "d")).spliterator()).expect("a", "b", "c", "d"); } void nested() { diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.output index 85c2e7ef7..a3fcc6cba 100644 --- a/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.output +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/LambdaHug.output @@ -126,6 +126,10 @@ class LambdaHug { }); someObject.someModerateLengthFunctionName( foo -> doOtherModerateLengthName(foo, bar, baz, qux, quux)); + SpliteratorTester.of(() -> + Streams.concat(Stream.of("a"), Stream.of("b"), Stream.empty(), Stream.of("c", "d")) + .spliterator()) + .expect("a", "b", "c", "d"); } void nested() {