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
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -916,3 +916,44 @@ Which generates this:
```

Use `$T` when referencing types in Javadoc to get automatic imports.

### Markdown in Javadoc

To output Markdown styled Javadoc (see [JEP 467](https://openjdk.org/jeps/467)),
th method `useMarkdownJavadoc()` of the `JavaFile` builder has to be called:
Comment on lines +922 to +923
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
To output Markdown styled Javadoc (see [JEP 467](https://openjdk.org/jeps/467)),
th method `useMarkdownJavadoc()` of the `JavaFile` builder has to be called:
To emit Markdown Javadoc (see [JEP 467](https://openjdk.org/jeps/467)),
use `JavaFile.Builder.useMarkdownJavadoc()`:


```java
TypeSpec emptyClass = TypeSpec.classBuilder("EmptyClass")
.addJavadoc("# Empty Class\n"
+ "\n"
+ "A representation of nothing: /* empty */\n")
.addJavadoc("\n")
.addJavadoc("This is not to be confused with the [$T] datatype which\n"
+ "is an uninstantiable placeholder class to hold a reference\n"
+ "to the *Class* object representing the Java keyword `void`.\n", Void.class)
.build();

JavaFile javaFile = JavaFile.builder("com.example.empty", emptyClass)
.useMarkdownJavadoc() // sets the Markdown output flag
.build();

javaFile.writeTo(System.out);
```

which outputs:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: consistent wording

Suggested change
which outputs:
Which generates this:


```java
package com.example.empty;

import java.lang.Void;

/// # Empty Class
///
/// A representation of nothing: /* empty */
///
/// This is not to be confused with the [Void] datatype which
/// is an uninstantiable placeholder class to hold a reference
/// to the *Class* object representing the Java keyword `void`.
class EmptyClass {
}
```
Comment on lines +925 to +959
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we update this to use the same example (or as similar as possible) as in the non-Markdown example?

57 changes: 41 additions & 16 deletions javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
* honors imports, indentation, and deferred variable names.
*/
final class CodeWriter {

private enum CommentType {
LINE,
JAVADOC,
MARKDOWN_JAVADOC
}

/** Sentinel value that indicates that no user-provided package has been set. */
private static final String NO_PACKAGE = new String();

Expand All @@ -51,13 +58,13 @@ final class CodeWriter {
private final LineWrapper out;
private int indentLevel;

private boolean javadoc = false;
private boolean comment = false;
private CommentType comment = null;
private String packageName = NO_PACKAGE;
private final List<TypeSpec> typeSpecStack = new ArrayList<>();
private final Set<String> staticImportClassNames;
private final Set<String> staticImports;
private final Set<String> alwaysQualify;
private final boolean useMarkdownJavadoc;
private final Map<String, ClassName> importedTypes;
private final Map<String, ClassName> importableTypes = new LinkedHashMap<>();
private final Set<String> referencedNames = new LinkedHashSet<>();
Expand All @@ -77,20 +84,22 @@ final class CodeWriter {
}

CodeWriter(Appendable out, String indent, Set<String> staticImports, Set<String> alwaysQualify) {
this(out, indent, Collections.emptyMap(), staticImports, alwaysQualify);
this(out, indent, Collections.emptyMap(), staticImports, alwaysQualify, false);
}

CodeWriter(
Appendable out,
String indent,
Map<String, ClassName> importedTypes,
Set<String> staticImports,
Set<String> alwaysQualify) {
Set<String> alwaysQualify,
boolean useMarkdownJavadoc) {
this.out = new LineWrapper(out, indent, 100);
this.indent = checkNotNull(indent, "indent == null");
this.importedTypes = checkNotNull(importedTypes, "importedTypes == null");
this.staticImports = checkNotNull(staticImports, "staticImports == null");
this.alwaysQualify = checkNotNull(alwaysQualify, "alwaysQualify == null");
this.useMarkdownJavadoc = useMarkdownJavadoc;
this.staticImportClassNames = new LinkedHashSet<>();
for (String signature : staticImports) {
staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.')));
Expand Down Expand Up @@ -144,12 +153,12 @@ public CodeWriter popType() {

public void emitComment(CodeBlock codeBlock) throws IOException {
trailingNewline = true; // Force the '//' prefix for the comment.
comment = true;
comment = CommentType.LINE;
try {
emit(codeBlock);
emit("\n");
} finally {
comment = false;
comment = null;
}
}

Expand All @@ -158,14 +167,22 @@ public void emitJavadoc(CodeBlock javadocCodeBlock) throws IOException {
return;
}

emit("/**\n");
javadoc = true;
if (useMarkdownJavadoc) {
emit("/// ");
comment = CommentType.MARKDOWN_JAVADOC;
} else {
emit("/**\n");
comment = CommentType.JAVADOC;
}
try {
emit(javadocCodeBlock, true);
} finally {
javadoc = false;
comment = null;
}

if (!useMarkdownJavadoc) {
emit(" */\n");
}
emit(" */\n");
}

public void emitAnnotations(List<AnnotationSpec> annotations, boolean inline) throws IOException {
Expand Down Expand Up @@ -497,9 +514,14 @@ CodeWriter emitAndIndent(String s) throws IOException {
for (String line : LINE_BREAKING_PATTERN.split(s, -1)) {
// Emit a newline character. Make sure blank lines in Javadoc & comments look good.
if (!first) {
if ((javadoc || comment) && trailingNewline) {
if ((comment != null) && trailingNewline) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if ((comment != null) && trailingNewline) {
if (comment != null && trailingNewline) {

emitIndentation();
out.append(javadoc ? " *" : "//");
out.append(
switch (comment) {
case LINE -> "//";
case JAVADOC -> " *";
case MARKDOWN_JAVADOC -> "///";
});
}
out.append("\n");
trailingNewline = true;
Expand All @@ -519,10 +541,13 @@ CodeWriter emitAndIndent(String s) throws IOException {
// Emit indentation and comment prefix if necessary.
if (trailingNewline) {
emitIndentation();
if (javadoc) {
out.append(" * ");
} else if (comment) {
out.append("// ");
if (comment != null) {
out.append(
switch (comment) {
case LINE -> "// ";
case JAVADOC -> " * ";
case MARKDOWN_JAVADOC -> "/// ";
});
}
}

Expand Down
12 changes: 11 additions & 1 deletion javapoet/src/main/java/com/palantir/javapoet/JavaFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public Appendable append(char _c) {
private final Set<String> staticImports;
private final Set<String> alwaysQualify;
private final String indent;
private final boolean useMarkdownJavadoc;

private JavaFile(Builder builder) {
this.fileComment = builder.fileComment.build();
Expand All @@ -77,6 +78,7 @@ private JavaFile(Builder builder) {
this.skipJavaLangImports = builder.skipJavaLangImports;
this.staticImports = Util.immutableSet(builder.staticImports);
this.indent = builder.indent;
this.useMarkdownJavadoc = builder.useMarkdownJavadoc;

Set<String> alwaysQualifiedNames = new LinkedHashSet<>();
fillAlwaysQualifiedNames(builder.typeSpec, alwaysQualifiedNames);
Expand Down Expand Up @@ -105,7 +107,8 @@ public void writeTo(Appendable out) throws IOException {
Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();

// Second pass: write the code, taking advantage of the imports.
CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports, alwaysQualify);
CodeWriter codeWriter =
new CodeWriter(out, indent, suggestedImports, staticImports, alwaysQualify, useMarkdownJavadoc);
emit(codeWriter);
}

Expand Down Expand Up @@ -292,6 +295,7 @@ public Builder toBuilder() {
builder.fileComment.add(fileComment);
builder.skipJavaLangImports = skipJavaLangImports;
builder.indent = indent;
builder.useMarkdownJavadoc = useMarkdownJavadoc;
return builder;
}

Expand All @@ -301,6 +305,7 @@ public static final class Builder {
private final CodeBlock.Builder fileComment = CodeBlock.builder();
private boolean skipJavaLangImports;
private String indent = " ";
private boolean useMarkdownJavadoc;

private final Set<String> staticImports = new TreeSet<>();

Expand Down Expand Up @@ -351,6 +356,11 @@ public Builder indent(String indent) {
return this;
}

public Builder useMarkdownJavadoc() {
this.useMarkdownJavadoc = true;
return this;
}

public JavaFile build() {
return new JavaFile(this);
}
Expand Down
14 changes: 14 additions & 0 deletions javapoet/src/test/java/com/palantir/javapoet/CodeWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.util.Collections;
import org.junit.jupiter.api.Test;

public class CodeWriterTest {
Expand All @@ -35,4 +36,17 @@ public void emptyLineInJavaDocDosEndings() throws IOException {
*/
""");
}

@Test
public void markdownJavadocStyle() throws IOException {
CodeBlock javadocCodeBlock = CodeBlock.of("A\r\n\r\nB\r\n");
StringBuilder out = new StringBuilder();
new CodeWriter(out, " ", Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), true)
.emitJavadoc(javadocCodeBlock);
assertThat(out.toString()).isEqualTo("""
/// A
///
/// B
""");
}
}
23 changes: 23 additions & 0 deletions javapoet/src/test/java/com/palantir/javapoet/MethodSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -354,6 +355,28 @@ void getTaco(double money, int count) {
""");
}

@Test
public void withParameterMarkdownJavaDoc() throws IOException {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test feels somewhat arbitrary - we don't have similar tests for the other types which can contain Javadoc (ex FieldSpec or ParameterSpec). It also requires this awkward TestUtil.codeWriterWithMarkdownJavadoc, which I'd prefer to avoid introducing.

I think we can just remove this test. It's sufficient to test CodeWriter and JavaFile.

MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
.addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money")
.addJavadoc("the amount required to buy the taco.\n")
.build())
.addParameter(ParameterSpec.builder(TypeName.INT, "count")
.addJavadoc("the number of Tacos to buy.\n")
.build())
.build();

StringBuilder out = new StringBuilder();
methodSpec.emit(TestUtil.codeWriterWithMarkdownJavadoc(out), "Constructor", Collections.emptySet());

assertThat(out.toString()).isEqualTo("""
/// @param money the amount required to buy the taco.
/// @param count the number of Tacos to buy.
void getTaco(double money, int count) {
}
""");
}

@Test
public void duplicateExceptionsIgnored() {
ClassName ioException = ClassName.get(IOException.class);
Expand Down
5 changes: 5 additions & 0 deletions javapoet/src/test/java/com/palantir/javapoet/TestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.palantir.javapoet;

import java.util.Collection;
import java.util.Collections;
import javax.lang.model.element.Element;

final class TestUtil {
Expand All @@ -30,4 +31,8 @@ static <E extends Element> E findFirst(Collection<E> elements, String name) {
}
throw new IllegalArgumentException(name + " not found in " + elements);
}

static CodeWriter codeWriterWithMarkdownJavadoc(Appendable out) {
return new CodeWriter(out, " ", Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), true);
}
}
Loading