diff --git a/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocLexer.java b/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocLexer.java index 846b7a37e..76e424ef1 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocLexer.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocLexer.java @@ -388,6 +388,16 @@ private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(Li continue; } + if (seenWhitespace.indexOf("\n") >= 0 + && !hasMultipleNewlines(seenWhitespace.toString()) + && isHyphenatedWordEnd(accumulated) + && tokens.peek().getType() == LITERAL + && isWordStart(tokens.peek().getValue()) + && !isSuspendedHyphenConjunction(tokens.peek().getValue())) { + accumulated.append(tokens.next().getValue()); + continue; + } + output.add(new Token(LITERAL, accumulated.toString())); accumulated.setLength(0); @@ -399,13 +409,26 @@ private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(Li // We have another token coming, possibly of type OTHER. Leave it for the next iteration. } - /* - * TODO(cpovirk): Another case where we could try to join tokens is if a line ends with - * /[^ -]-/, as in "non-\nblocking." - */ return output.build(); } + private static boolean isHyphenatedWordEnd(CharSequence cs) { + int length = cs.length(); + return length >= 2 && cs.charAt(length - 1) == '-' && isWordChar(cs.charAt(length - 2)); + } + + private static boolean isWordChar(char c) { + return Character.isLetterOrDigit(c) || c == '_'; + } + + private static boolean isWordStart(String s) { + return !s.isEmpty() && isWordChar(s.charAt(0)); + } + + private static boolean isSuspendedHyphenConjunction(String s) { + return s.equals("and") || s.equals("or"); + } + /** * Where the input has two consecutive line breaks between literals, insert a {@code

} tag between the literals. * diff --git a/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocWriter.java b/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocWriter.java index e86855742..4e299f8a8 100644 --- a/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocWriter.java +++ b/open-java-format/src/main/java/com/palantir/javaformat/java/javadoc/JavadocWriter.java @@ -114,9 +114,7 @@ void writeFooterJavadocTagStart(Token token) { */ postWriteModifiedContinuingListCount.reset(); - if (!wroteAnythingSignificant) { - // Javadoc consists solely of tags. This is frowned upon in general but OK for @Overrides. - } else if (!continuingFooterTag) { + if (!continuingFooterTag) { // First footer tag after a body tag. requestBlankLine(); } else { @@ -328,6 +326,12 @@ private void writeToken(Token token) { requestNewline(); } + if (!wroteAnythingSignificant) { + // Nothing precedes the first token but the opening ∕✱✱ and its newline, so no requested + // whitespace (e.g., the blank line a

 or 
    asks for) belongs before it. + requestedWhitespace = NONE; + } + if (requestedWhitespace == BLANK_LINE && (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) { /* diff --git a/open-java-format/src/test/java/com/palantir/javaformat/java/JavadocFormattingTest.java b/open-java-format/src/test/java/com/palantir/javaformat/java/JavadocFormattingTest.java index 679e0b137..23e0645ce 100644 --- a/open-java-format/src/test/java/com/palantir/javaformat/java/JavadocFormattingTest.java +++ b/open-java-format/src/test/java/com/palantir/javaformat/java/JavadocFormattingTest.java @@ -1625,4 +1625,90 @@ void preCodeMixedStarAndBareLinesKeepRelativeIndent() { }; doFormatTest(input, expected); } + + @Test + void hyphenatedLineBreakJoined() { + String[] input = { + "/**", // + " * This calculation requires an infinite-", + " * precision number.", + " */", + "class Test {}", + }; + String[] expected = { + "/** This calculation requires an infinite-precision number. */", // + "class Test {}", + }; + doFormatTest(input, expected); + } + + @Test + void suspendedHyphenPreserved() { + String[] input = { + "/**", // + " * Both pre-", + " * and post-processing steps.", + " */", + "class Test {}", + }; + String[] expected = { + "/** Both pre- and post-processing steps. */", // + "class Test {}", + }; + doFormatTest(input, expected); + } + + @Test + void hyphenBeforeParagraphBreakNotJoined() { + String[] input = { + "/**", // + " * Ends with a non-", + " *", + " * blocking paragraph.", + " */", + "class Test {}", + }; + String[] expected = { + "/**", // + " * Ends with a non-", + " *", + " *

    blocking paragraph.", + " */", + "class Test {}", + }; + doFormatTest(input, expected); + } + + @Test + void preBlockAsFirstContentGetsNoBlankLines() { + String[] input = { + "/**", // + " *

    {@code",
    +            " * class Demo {",
    +            " *   int x;",
    +            " * }",
    +            " * }
    ", + " */", + "class Test {}", + }; + doFormatTest(input, input); + } + + @Test + void listAsFirstContentGetsNoBlankLines() { + String[] input = { + "/**
    • one
    • two
    */", // + "class Test {}", + }; + String[] expected = { + "/**", // + " *
      ", + " *
    • one", + " *
    • two", + " *
    ", + " */", + "class Test {}", + }; + doFormatTest(input, expected); + } }