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 c34b4f583..6205f36c2 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 @@ -447,6 +447,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); @@ -457,13 +467,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 1fc5d0b74..a5f7dae36 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/JavadocFormattingTest.java @@ -2571,6 +2571,58 @@ 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() { + 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