Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ public class JavaJavadocComment {
// escape the "@" in Javadoc description, if it is not used in inline tag like {@link }
private static final Pattern ESCAPE_AT = Pattern.compile("(?<!\\{)@");

// Markdown formatting patterns
// Match ***text*** (bold italic), **text** (bold), or *text* (italic)
// Uses non-greedy matching and requires the closing marker to not be followed by the same marker
private static final Pattern BOLD_ITALIC_PATTERN = Pattern.compile("\\*\\*\\*(.+?)\\*\\*\\*");
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*");
private static final Pattern ITALIC_PATTERN = Pattern.compile("(?<!\\*)\\*(?!\\*)(.+?)\\*(?!\\*)");

public JavaJavadocComment(JavaFileContents contents) {
this.contents = contents;
}
Expand All @@ -25,13 +32,131 @@ private static String ensurePeriod(String value) {
return value == null || value.isEmpty() || value.endsWith(".") ? value : value + '.';
}

/**
* Converts Markdown formatting to JavaDoc HTML tags.
* Converts:
* - ***text*** to &lt;b&gt;&lt;i&gt;text&lt;/i&gt;&lt;/b&gt; (bold italic)
* - **text** to &lt;b&gt;text&lt;/b&gt; (bold)
* - *text* to &lt;i&gt;text&lt;/i&gt; (italic)
* - Lines starting with "- " to &lt;ul&gt;&lt;li&gt; (bullet points)
* - Lines starting with "N. " to &lt;ol&gt;&lt;li&gt; (numbered lists)
*
* @param text the text with Markdown formatting
* @return the text with JavaDoc HTML formatting
*/
private static String convertMarkdownToJavadoc(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// Process line by line to handle bullet points and numbered lists
String[] lines = text.split("\n");
StringBuilder result = new StringBuilder();
boolean inUnorderedList = false;
boolean inOrderedList = false;

for (int i = 0; i < lines.length; i++) {
String line = lines[i];
String trimmedLine = line.trim();

// Check for bullet points (lines starting with "- ")
if (trimmedLine.startsWith("- ")) {
if (!inUnorderedList) {
if (inOrderedList) {
result.append("</ol>\n");
inOrderedList = false;
}
result.append("<ul>\n");
inUnorderedList = true;
}
// Extract the content after "- " and convert inline formatting
String content = convertInlineFormatting(trimmedLine.substring(2).trim());
result.append("<li>").append(content).append("</li>\n");
}
// Check for numbered lists (lines starting with "N. " where N is a digit)
else if (trimmedLine.matches("^\\d+\\.\\s+.*")) {
if (!inOrderedList) {
if (inUnorderedList) {
result.append("</ul>\n");
inUnorderedList = false;
}
result.append("<ol>\n");
inOrderedList = true;
}
// Extract the content after "N. " and convert inline formatting
String content = convertInlineFormatting(trimmedLine.replaceFirst("^\\d+\\.\\s+", ""));
result.append("<li>").append(content).append("</li>\n");
}
// Regular line
else {
// Close any open lists
if (inUnorderedList) {
result.append("</ul>\n");
inUnorderedList = false;
}
if (inOrderedList) {
result.append("</ol>\n");
inOrderedList = false;
}

if (!trimmedLine.isEmpty()) {
result.append(convertInlineFormatting(line));
}

// Add newline if not the last line
if (i < lines.length - 1) {
result.append("\n");
}
}
}

// Close any remaining open lists
if (inUnorderedList) {
result.append("</ul>");
}
if (inOrderedList) {
result.append("</ol>");
}

return result.toString();
}

/**
* Converts inline Markdown formatting (bold, italic) to JavaDoc HTML tags.
*
* @param text the text with inline Markdown formatting
* @return the text with JavaDoc HTML formatting
*/
private static String convertInlineFormatting(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// Convert ***bold italic*** first (must come before ** and *)
text = BOLD_ITALIC_PATTERN.matcher(text).replaceAll("<b><i>$1</i></b>");

// Convert **bold**
text = BOLD_PATTERN.matcher(text).replaceAll("<b>$1</b>");

// Convert *italic*
text = ITALIC_PATTERN.matcher(text).replaceAll("<i>$1</i>");

return text;
}

private static String processText(String value) {
String text = CodeNamer.escapeXmlComment(ensurePeriod(trim(value)));
if (text != null) {
String text = trim(value);
if (text != null && !text.isEmpty()) {
// Ensure period at the end before any processing
text = ensurePeriod(text);
// Escape XML special characters FIRST (before markdown conversion)
text = CodeNamer.escapeXmlComment(text);
// escape "@" that isn't prefixed with "{"
text = ESCAPE_AT.matcher(text).replaceAll("&#064;");
// escape tab
text = text.replace("\t", " ");
// Convert Markdown formatting to JavaDoc HTML tags AFTER escaping
text = convertMarkdownToJavadoc(text);
}
return CodeNamer.escapeIllegalUnicodeEscape(CodeNamer.escapeComment(text));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import "@typespec/http";
import "@typespec/spector";

using Http;
using Spector;

@scenarioService("/documentation")
@doc("Illustrates documentation generation and formatting features")
namespace Documentation;

@route("/lists")
namespace Lists {
/**
* This tests:
* - Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.
* - Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.
* - Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.
* - Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.
* - **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.
* - *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.
*/
@scenario
@scenarioDoc("""
Test simple bullet points in documentation.
Expected behavior: Should render properly formatted bullet lists.
""")
@get
@route("/bullet-points/op")
op bulletPointsOp(): NoContentResponse;

/**
* This tests:
* - Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.
* - Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.
* - Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.
* - Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.
* - **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.
* - *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.
*/
model BulletPointsModel {
/**
* This property uses an enum with bullet point documentation. The enum documentation includes various formatting styles to test rendering. The styles are:
* - Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.
* - Bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple
* - Bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.
* - Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.
* - **Bold bullet point**
* - *Italic bullet point*
*/
prop: BulletPointsEnum;
}

/**
* This tests really long bullet points in enum documentation to see how wrapping and formatting are handled. This should wrap around correctly and maintain proper indentation for each line.
* - Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.
* - Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.
* - Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.
* - Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.
* - **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.
* - *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.
*/
enum BulletPointsEnum {
/**
* Simple bullet point. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - One: one. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - Two: two. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
*/
Simple: "Simple",

/**
* Bullet point with **bold text**. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - **One**: one. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - **Two**: two. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
*/
Bold: "Bold",

/**
* Bullet point with *italic text*. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - *One*: one. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
* - *Two*: two. This line is intentionally long to test text wrapping in bullet points within enum documentation comments. It should properly indent the wrapped lines.
*/
Italic: "Italic",
}

@scenarioDoc("""
Test bullet points in model and enum documentation.
Expected input:
```json
{
"prop": "Simple"
}
```
""")
@post
@route("/bullet-points/model")
op bulletPointsModel(input: BulletPointsModel): NoContentResponse;

/**
* Steps to follow:
* 1. First step with **important** note
* 2. Second step with *emphasis*
* 3. Third step combining **bold** and *italic*
* 4. **Final step**: Review all steps for *accuracy*.
*/
@scenario
@scenarioDoc("""
Test numbered lists.
Expected behavior: Should render numbered list properly.
""")
@route("/numbered")
@get
op numbered(): NoContentResponse;
}

@route("/text-formatting")
namespace TextFormatting {
/**
* This is **bold text** in the middle of a sentence.
* This is a sentence with **multiple bold** sections and **another bold** section.
* **This entire sentence is bold.**
*/
@scenario
@scenarioDoc("""
Expected behavior: Text between ** should render as bold.
""")
@route("/bold")
@get
op boldText(): NoContentResponse;

/**
* This is *italic text* in the middle of a sentence.
* This is a sentence with *multiple italic* sections and *another italic* section.
* *This entire sentence is italic.*
* */
@scenario
@scenarioDoc("""
Test italic text formatting using *single asterisks*.
Expected behavior: Text between * should render as italic.
""")
@route("/italic")
@get
op italicText(): NoContentResponse;

/**
* This sentence has **bold**, *italic*, and ***bold italic*** text.
* You can also combine them like **bold with *italic inside* bold**.
* Or *italic with **bold inside** italic*.
* This is a sentence with **bold**, *italic*, and ***bold italic*** text.
*/
@scenario
@scenarioDoc("""
Test combined bold and italic formatting.
Expected behavior: Should handle nested and combined formatting.
""")
@route("/combined")
@get
op combinedFormatting(): NoContentResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api";

export const Scenarios: Record<string, ScenarioMockApi> = {};

function createGetServerTests(uri: string) {
return passOnSuccess({
uri,
method: "get",
request: {},
response: {
status: 204,
},
kind: "MockApiDefinition",
});
}

function createPostServerTests(uri: string, requestBody: unknown, responseBody?: unknown) {
return passOnSuccess({
uri,
method: "post",
request: {
body: json(requestBody),
},
response: {
status: 200,
body: responseBody ? json(responseBody) : undefined,
},
kind: "MockApiDefinition",
});
}

// Lists namespace tests
Scenarios.Documentation_Lists_bulletPointsOp = createGetServerTests(
"/documentation/lists/bullet-points/op",
);

Scenarios.Documentation_Lists_bulletPointsModel = createPostServerTests(
"/documentation/lists/bullet-points/model",
{
prop: "Simple",
},
);

Scenarios.Documentation_Lists_numbered = createGetServerTests("/documentation/lists/numbered");

// TextFormatting namespace tests
Scenarios.Documentation_TextFormatting_boldText = createGetServerTests(
"/documentation/text-formatting/bold",
);

Scenarios.Documentation_TextFormatting_italicText = createGetServerTests(
"/documentation/text-formatting/italic",
);

Scenarios.Documentation_TextFormatting_combinedFormatting = createGetServerTests(
"/documentation/text-formatting/combined",
);
Loading