Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maven-plugin-plugin/src/it/help-basic-jdk11/expected-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ help-jdk11:test

Available parameters:

codeExample
A parameter with a preformatted example.
<binary>
<url>https://example.test/plugin.exe</url>
</binary>

defaultParam (Default: escape\backslash)
This parameter uses "quotation" marks and backslashes '\\' in its
description. Those characters must be escaped in Java string literals.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ public class MyMojo extends AbstractMojo {
@Parameter(defaultValue = "escape\\backslash")
private String defaultParam;

/**
* A parameter with a preformatted example.
*
* <pre>{@code
* <binary>
* <url>https://example.test/plugin.exe</url>
* </binary>
* }</pre>
*/
@Parameter
private String codeExample;

/**
* This parameter is deprecated.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,31 @@ private String getPlainText(Element element) {

// the formatting rules, implemented in a breadth-first DOM traverse
private static class FormattingVisitor implements NodeVisitor {
private static final char PRE_START = '\u0001';
private static final char PRE_END = '\u0002';

private StringBuilder accum = new StringBuilder(); // holds the accumulated text

// Nested <pre> elements are invalid HTML; Jsoup normalises them before traversal.
private int preformatted;

// hit when the node is first seen
public void head(Node node, int depth) {
String name = node.nodeName();
if (node instanceof TextNode) {
accum.append(((TextNode) node).text()); // TextNodes carry all user-readable text in the DOM.
TextNode textNode = (TextNode) node;
// TextNode#text() normalizes whitespace. Keep the original text inside preformatted blocks.
accum.append(preformatted > 0 ? textNode.getWholeText() : textNode.text());
} else if (name.equals("li")) {
accum.append("\n * ");
} else if (name.equals("dt")) {
accum.append(" ");
} else if (name.equals("pre")) {
if (accum.length() > 0 && accum.charAt(accum.length() - 1) != '\n') {
accum.append('\n');
}
accum.append(PRE_START);
preformatted++;
} else if (StringUtil.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr")) {
accum.append("\n");
}
Expand All @@ -78,8 +92,18 @@ public void head(Node node, int depth) {
// hit when all of the node's children (if any) have been visited
public void tail(Node node, int depth) {
String name = node.nodeName();
if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5")) {
accum.append("\n");
boolean preformattedEndsWithNewline = false;
if (name.equals("pre")) {
preformatted--;
preformattedEndsWithNewline = accum.length() > 0 && accum.charAt(accum.length() - 1) == '\n';
accum.append(PRE_END);
if (!preformattedEndsWithNewline) {
accum.append('\n');
}
Comment on lines +96 to +102

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Redundant second if (name.equals("pre")) block — the two consecutive if blocks can be merged. After the first block appends PRE_END, name is still "pre", so the second check is always true when the first was true. Merge them into a single block:

Suggested change
if (name.equals("pre")) {
preformatted--;
preformattedEndsWithNewline = accum.length() > 0 && accum.charAt(accum.length() - 1) == '\n';
accum.append(PRE_END);
}
if (name.equals("pre")) {
if (!preformattedEndsWithNewline) {
accum.append('\n');
}
if (name.equals("pre")) {
preformatted--;
preformattedEndsWithNewline = accum.length() > 0 && accum.charAt(accum.length() - 1) == '\n';
accum.append(PRE_END);
if (!preformattedEndsWithNewline) {
accum.append('\n');
}
```

} else if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5")) {
if (accum.length() == 0 || accum.charAt(accum.length() - 1) != '\n') {
accum.append("\n");
}
} else if (name.equals("a")) {
// link is empty if it cannot be made absolute
String link = node.absUrl("href");
Expand All @@ -91,8 +115,26 @@ public void tail(Node node, int depth) {

@Override
public String toString() {
// collate multiple consecutive spaces
return accum.toString().replaceAll(" +", " ").replace("\n ", "\n");
String text = accum.toString();
StringBuilder result = new StringBuilder(text.length());
int start = 0;
int preStart;
while ((preStart = text.indexOf(PRE_START, start)) >= 0) {
appendNormalText(result, text.substring(start, preStart));
int preEnd = text.indexOf(PRE_END, preStart);
if (preEnd < 0) {
appendNormalText(result, text.substring(preStart + 1));
return result.toString();
}
result.append(text, preStart + 1, preEnd);
start = preEnd + 1;
}
appendNormalText(result, text.substring(start));
return result.toString();
}

private static void appendNormalText(StringBuilder result, String text) {
result.append(text.replaceAll(" +", " ").replace("\n ", "\n"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,27 @@ public class HelpMojo

String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );

for ( String plainLine : plainLines )
for ( int i = 0; i < plainLines.length; i++ )
{
toLines( lines, ind + plainLine, indentSize, lineLength );
String plainLine = plainLines[i];
if ( plainLine.trim().isEmpty() )
{
boolean previousLineIsPreformatted = i > 0
&& !plainLines[i - 1].trim().isEmpty()
&& plainLines[i - 1].startsWith( " " );
boolean nextLineIsPreformatted = i + 1 < plainLines.length
&& !plainLines[i + 1].trim().isEmpty()
&& plainLines[i + 1].startsWith( " " );
// The whitespace-only line before a preformatted block is a boundary, not displayed content.
if ( !nextLineIsPreformatted || previousLineIsPreformatted )
{
lines.add( "" );
}
}
else
{
toLines( lines, ind + plainLine, indentSize, lineLength );
}
}

return lines;
Expand All @@ -400,6 +418,29 @@ public class HelpMojo
int lineIndent = getIndentLevel( line );
StringBuilder buf = new StringBuilder( 256 );

// Preserve leading spaces in preformatted descriptions instead of treating them as word separators.
if ( lineIndent < line.length() && line.charAt( lineIndent ) == ' ' )
{
for ( int j = 0; j < line.length(); j++ )
{
char c = line.charAt( j );
if ( c == '\t' )
{
buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
}
else if ( c == '\u00A0' )
{
buf.append( ' ' );
}
else
{
buf.append( c );
}
}
lines.add( buf.toString() );
return;
}

String[] tokens = line.split( " +" );

for ( String token : tokens )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,9 +369,27 @@ public class HelpMojo

String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );

for ( String plainLine : plainLines )
for ( int i = 0; i < plainLines.length; i++ )
{
toLines( lines, ind + plainLine, indentSize, lineLength );
String plainLine = plainLines[i];
if ( plainLine.trim().isEmpty() )
{
boolean previousLineIsPreformatted = i > 0
&& !plainLines[i - 1].trim().isEmpty()
&& plainLines[i - 1].startsWith( " " );
boolean nextLineIsPreformatted = i + 1 < plainLines.length
&& !plainLines[i + 1].trim().isEmpty()
&& plainLines[i + 1].startsWith( " " );
// The whitespace-only line before a preformatted block is a boundary, not displayed content.
if ( !nextLineIsPreformatted || previousLineIsPreformatted )
{
lines.add( "" );
}
}
else
{
toLines( lines, ind + plainLine, indentSize, lineLength );
}
}

return lines;
Expand All @@ -390,6 +408,29 @@ public class HelpMojo
int lineIndent = getIndentLevel( line );
StringBuilder buf = new StringBuilder( 256 );

// Preserve leading spaces in preformatted descriptions instead of treating them as word separators.
if ( lineIndent < line.length() && line.charAt( lineIndent ) == ' ' )
{
for ( int j = 0; j < line.length(); j++ )
{
char c = line.charAt( j );
if ( c == '\t' )
{
buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
}
else if ( c == '\u00A0' )
{
buf.append( ' ' );
}
else
{
buf.append( c );
}
}
lines.add( buf.toString() );
return;
}

String[] tokens = line.split( " +" );

for ( String token : tokens )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ void explicitNewline() {
converter.convert(test));
}

@Test
void preformattedTextPreservesWhitespace() {
String test =
"For example:<pre>\n &lt;binary&gt;\n &lt;url&gt;https://example.test/plugin.exe&lt;/url&gt;\n &lt;/binary&gt;\n</pre>";
assertEquals(
"For example:\n <binary>\n <url>https://example.test/plugin.exe</url>\n </binary>\n",
Comment on lines 86 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Consider adding edge-case tests for robustness:

  1. <pre> content that does not end with a newline (tests the preformattedEndsWithNewline branch)
  2. Multiple <pre> blocks in the same input
  3. Nested <pre> handling (if expected to be unsupported, a comment saying so would help)

The current test only covers the trailing-newline path.

converter.convert(test));
}

@Test
void preformattedTextWithoutTrailingNewlinePreservesWhitespace() {
assertEquals("before\n first\n second\nafter", converter.convert("before<pre> first\n second</pre>after"));
}

@Test
void multiplePreformattedBlocksPreserveTheirBoundaries() {
assertEquals(
"one\n first\nmiddle\nsecond \nend",
converter.convert("one<pre> first</pre>middle<pre>second </pre>end"));
}

@ParameterizedTest(name = "{0} to {1}") // With JUnit 6.0.0 the non-printable chars will be kept in display, see
// https://docs.junit.org/current/user-guide/#writing-tests-parameterized-tests-display-names-quoted-text
@MethodSource("provideConvertParamsBreakLines")
Expand Down