diff --git a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java index 1762b6b21..71e64b804 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java +++ b/core/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java @@ -438,6 +438,16 @@ private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(Li continue; } + if (seenWhitespace.contains("\n") + && !hasMultipleNewlines(seenWhitespace) + && isHyphenatedWordEnd(accumulated) + && tokens.peek() instanceof Literal literal + && isWordStart(literal.value()) + && !isSuspendedHyphenConjunction(literal.value())) { + accumulated.append(tokens.next().value()); + continue; + } + output.add(new Literal(accumulated.toString())); accumulated.setLength(0); @@ -448,13 +458,26 @@ private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(Li // We have another token coming. 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"); + } + private static String consumeAdjacentWhitespace(PeekingIterator tokens) { StringBuilder seenWhitespace = new StringBuilder(); while (tokens.peek() instanceof Whitespace) { diff --git a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java index 98185d283..f0014a211 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java @@ -2569,6 +2569,59 @@ class Test {} doFormatTest(input, expected); } + @Test + public 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 + public 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 + public void markdownHyphenatedLineBreakJoined() { + assume().that(MARKDOWN_JAVADOC_SUPPORTED).isTrue(); + 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); + } + // TODO: b/346668798 - Test the following Markdown constructs, and make the tests work as needed. // We can assume that the CommonMark parser correctly handles Markdown, so the question is whether // they are subsequently mishandled by our formatting logic. So for example the CommonMark parser