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 d138011f0..ccb1e99ef 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -1929,13 +1929,34 @@ public Void visitReturn(ReturnTree node, Void unused) { sync(node); token("return"); if (node.getExpression() != null) { - builder.space(); - scan(node.getExpression(), null); + ExpressionTree expression = node.getExpression(); + // Text blocks after return must start on their own line so the opening delimiter + // is indented like other expression contexts (Google Java Style 4.8.9; + // https://github.com/google/google-java-format/issues/1258). A fill-mode break is + // not enough: return plus the delimiter fits on one line and stays attached. + if (isTextBlockLiteral(expression)) { + builder.open(plusFour); + builder.breakOp(FillMode.FORCED, " ", ZERO); + scan(expression, null); + builder.close(); + } else { + builder.space(); + scan(expression, null); + } } token(";"); return null; } + /** True when {@code expression} is a text block literal. */ + private boolean isTextBlockLiteral(ExpressionTree expression) { + if (expression.getKind() != STRING_LITERAL) { + return false; + } + String source = getSourceForNode(expression, getCurrentPath()); + return source.startsWith("\"\"\""); + } + // TODO(cushon): is this worth special-casing? private boolean visitSingleMemberAnnotation(AnnotationTree node) { if (node.getArguments().size() != 1) { diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java index 7414e4e50..7d4f91e9d 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterTest.java @@ -862,4 +862,50 @@ public void maxLineLengthNonPositive() { IllegalArgumentException.class, () -> Style.builder().maxLineLength(-1).build()); assertThat(e).hasMessageThat().contains("maxLineLength must be positive, was: -1"); } + + @Test + public void returnTextBlockStartsOnOwnLine() throws Exception { + // https://github.com/google/google-java-format/issues/1258 + // CLI applies StringWrapper after formatSource; match that pipeline. + Formatter formatter = new Formatter(); + String input = + """ + class T { + String f() { + return \""" + this is sample text + \"""; + } + } + """; + String expected = + """ + class T { + String f() { + return + \""" + this is sample text + \"""; + } + } + """; + String formatted = StringWrapper.wrap(formatter.formatSource(input), formatter); + assertThat(formatted).isEqualTo(expected); + assertThat(StringWrapper.wrap(formatter.formatSource(formatted), formatter)) + .isEqualTo(expected); + } + + @Test + public void returnOrdinaryStringUnchanged() throws Exception { + String input = + """ + class T { + String f() { + return "hello"; + } + } + """; + assertThat(new Formatter().formatSource(input)).isEqualTo(input); + } + }