Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update tsp-openapi3 automatic doc newlines logic #3839

Merged
merged 2 commits into from
Jul 15, 2024
Merged
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
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/openapi3"
---

Updates tsp-openapi3 doc line wrapping to only automatically create newlines when they are present in the original documentation.
47 changes: 10 additions & 37 deletions packages/openapi3/src/cli/actions/convert/utils/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,30 @@ export function generateDocs(doc: string | string[]): string {
return ``;
}

const wrapped = lineWrap(doc);
const split = splitNewlines(doc);

for (let i = 0; i < wrapped.length; i++) {
if (wrapped[i].includes("@") || wrapped[i].includes("*/")) {
if (wrapped.length === 1) {
return `@doc("${wrapped[0].replace(/\\/g, "\\\\").replace(/"/g, '\\"')}")`;
for (let i = 0; i < split.length; i++) {
if (split[i].includes("@") || split[i].includes("*/")) {
if (split.length === 1) {
return `@doc("${split[0].replace(/\\/g, "\\\\").replace(/"/g, '\\"')}")`;
}
return `@doc("""\n${wrapped.join("\n").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}\n""")`;
return `@doc("""\n${split.join("\n").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}\n""")`;
}
}

return `/**\n* ${wrapped.join("\n* ")}\n*/`;
return `/**\n* ${split.join("\n* ")}\n*/`;
}

export function generateDocsContent(doc: string | string[]): string {
if (isEmptyDoc(doc)) {
return ``;
}

const wrapped = lineWrap(doc);
return wrapped.length === 1 ? `${wrapped[0]}` : `""\n${wrapped.join("\n")}\n""`;
}

function lineWrap(doc: string | string[]): string[] {
const maxLength = 80;

function splitNewlines(doc: string | string[]): string[] {
let docString = Array.isArray(doc) ? doc.join("\n") : doc;
docString = docString.replace(/\r\n/g, "\n");
docString = docString.replace(/\r/g, "\n");

if (docString.length <= maxLength && !docString.includes("\n")) {
if (!docString.includes("\n")) {
return [docString];
}

const oriLines = docString.split("\n");
const lines: string[] = [];
for (const oriLine of oriLines) {
const words = oriLine.split(" ");
let line = ``;
for (const word of words) {
if (word.length + 1 > maxLength - line.length) {
lines.push(line.substring(0, line.length - 1));
line = `${word} `;
} else {
line = `${line}${word} `;
}
}
lines.push(`${line.substring(0, line.length - 1)}`);
}

return lines;
return docString.split("\n");
chrisradek marked this conversation as resolved.
Show resolved Hide resolved
}

function isEmptyDoc(doc?: string | string[]): doc is undefined {
Expand Down
84 changes: 84 additions & 0 deletions packages/openapi3/test/tsp-openapi3/docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { strictEqual } from "assert";
import { it } from "vitest";
import { generateDocs } from "../../src/cli/actions/convert/utils/docs.js";

it("returns empty string for empty docs", () => {
strictEqual(generateDocs(""), "");
strictEqual(generateDocs([]), "");
});

it("returns single line doc", () => {
strictEqual(
generateDocs("Hello, World!"),
`
/**
* Hello, World!
*/
`.trim()
);
});

it("returns multi-line doc", () => {
strictEqual(
generateDocs(["Hello,", "World!"]),
`
/**
* Hello,
* World!
*/`.trim()
);
});

it("returns multi-line docs when they contain newline characters", () => {
strictEqual(
generateDocs("Hello,\nWorld!"),
`
/**
* Hello,
* World!
*/`.trim()
);
});

it("does not automatically apply line-wrapping for very long lines", () => {
const longLine = "This is a long line".repeat(20); // 380 characters
strictEqual(
generateDocs(longLine),
`
/**
* ${longLine}
*/`.trim()
);
});

it("handles different newline breaks", () => {
const scenarios = ["Hello,\nWorld!", "Hello,\rWorld!", "Hello,\r\nWorld!"];

for (const scenario of scenarios) {
strictEqual(
generateDocs(scenario),
`
/**
* Hello,
* World!
*/`.trim()
);
}
});

it("uses doc generator for @ and */", () => {
strictEqual(generateDocs("Hello, @World!"), `@doc("Hello, @World!")`);

strictEqual(generateDocs("Hello, */World!"), `@doc("Hello, */World!")`);
});

it("supports multi-line with decorator", () => {
strictEqual(
generateDocs(["Hello,", "@World!"]),
`
@doc("""
Hello,
@World!
""")`.trim()
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ using Http;
using OpenAPI;

/**
* This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You
* can find out more about
* Swagger at [http://swagger.io](http://swagger.io). In the third iteration of
* the pet store, we've switched to the design first approach!
* You can now help us improve the API whether it's by making changes to the
* definition itself or to the code.
* That way, with time, we can improve the API in general, and expose some of the
* new features in OAS3.
* This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about
* Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
* You can now help us improve the API whether it's by making changes to the definition itself or to the code.
* That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
*
* Some useful links:
* - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
* - [The source API definition for the Pet
* Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
* - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
*/
@service({
title: "Swagger Petstore - OpenAPI 3.0",
Expand Down Expand Up @@ -548,8 +543,7 @@ op findPetsByStatus(
): findPetsByStatus200ApplicationXmlResponse | findPetsByStatus200ApplicationJsonResponse | findPetsByStatus400Response;

/**
* Multiple tags can be provided with comma separated strings. Use tag1, tag2,
* tag3 for testing.
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*/
@tag("pet")
@route("/pet/findByTags")
Expand Down Expand Up @@ -653,8 +647,7 @@ op placeOrder(
): placeOrder200ApplicationJsonResponse | placeOrder405Response;

/**
* For valid response try integer IDs with value < 1000. Anything above 1000 or
* nonintegers will generate API errors
* For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
*/
@tag("store")
@extension("x-swagger-router-controller", "OrderController")
Expand All @@ -668,8 +661,7 @@ op deleteOrder(
): deleteOrder400Response | deleteOrder404Response;

/**
* For valid response try integer IDs with value <= 5 or > 10. Other values will
* generate exceptions.
* For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
*/
@tag("store")
@extension("x-swagger-router-controller", "OrderController")
Expand Down
Loading