Skip to content

Commit 66f9f77

Browse files
authored
Merge pull request #1094 from BitGo/DX-2385-preserve-line-breaks-tag
feat: add preserve line breaks tag
2 parents 1f7075c + e060d15 commit 66f9f77

File tree

3 files changed

+139
-34
lines changed

3 files changed

+139
-34
lines changed

packages/openapi-generator/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ These are some tags that you can use in your schema JSDocs are custom to this ge
472472
will have `x-internal: true` for schemas with the `@private` tag.
473473
- `@deprecated` allows to mark any field in any schema as deprecated. The final spec
474474
will include `deprecated: true` in the final specificaiton.
475+
- `@preserveLineBreaks` preserves line breaks in descriptions. By default, multiline
476+
descriptions are collapsed into a single line. Use this tag when you need to preserve
477+
formatting, such as for markdown lists or structured text.
475478

476479
```typescript
477480
import * as t from 'io-ts';
@@ -482,5 +485,15 @@ const Schema = t.type({
482485
/** @deprecated */
483486
deprecatedField: t.string,
484487
publicNonDeprecatedField: t.string,
488+
/**
489+
* @preserveLineBreaks
490+
* This description spans multiple lines
491+
* and will preserve its line breaks.
492+
*
493+
* Available options:
494+
* - `option1` - First option
495+
* - `option2` - Second option
496+
*/
497+
fieldWithFormattedDescription: t.string,
485498
});
486499
```

packages/openapi-generator/src/comments.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,17 @@ export function leadingComment(
4040
commentString = commentString + endingSubstring;
4141
}
4242

43-
const parsedComment = parseComment(commentString, { spacing: 'preserve' });
43+
const shouldPreserveLineBreaks = commentString.includes('@preserveLineBreaks');
44+
if (shouldPreserveLineBreaks) {
45+
// This handles both inline and separate line cases
46+
commentString = commentString.replace(/^\s*\*\s*@preserveLineBreaks\s*$/gm, '');
47+
commentString = commentString.replace(/@preserveLineBreaks\s*/g, '');
48+
}
49+
50+
const parsedComment = parseComment(
51+
commentString,
52+
shouldPreserveLineBreaks ? { spacing: 'preserve' } : undefined,
53+
);
4454

4555
for (const block of parsedComment) {
4656
block.description = block.description.trim();

packages/openapi-generator/test/openapi/comments.test.ts

Lines changed: 115 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,7 +1658,7 @@ testCase(
16581658
},
16591659
);
16601660

1661-
const ROUTE_WITH_MARKDOWN_LIST = `
1661+
const ROUTE_WITH_MARKDOWN_LIST_AND_PRESERVE_LINE_BREAKS = `
16621662
import * as t from 'io-ts';
16631663
import * as h from '@api-ts/io-ts-http';
16641664
@@ -1674,6 +1674,7 @@ export const route = h.httpRoute({
16741674
request: h.httpRequest({
16751675
query: {
16761676
/**
1677+
* @preserveLineBreaks
16771678
* The permissions granted by this access token.
16781679
*
16791680
* - \`all\` - Access all actions in the test environment.
@@ -1688,46 +1689,127 @@ export const route = h.httpRoute({
16881689
});
16891690
`;
16901691

1691-
testCase('route with markdown list in comment', ROUTE_WITH_MARKDOWN_LIST, {
1692-
openapi: '3.0.3',
1693-
info: {
1694-
title: 'Test',
1695-
version: '1.0.0',
1696-
},
1697-
paths: {
1698-
'/list': {
1699-
get: {
1700-
summary: 'A route with a list in the comment',
1701-
operationId: 'api.v1.list',
1702-
tags: ['Test Routes'],
1703-
parameters: [
1704-
{
1705-
name: 'permissions',
1706-
in: 'query',
1707-
required: true,
1708-
schema: {
1709-
type: 'string',
1692+
testCase(
1693+
'route with markdown list in comment and @preserveLineBreaks tag',
1694+
ROUTE_WITH_MARKDOWN_LIST_AND_PRESERVE_LINE_BREAKS,
1695+
{
1696+
openapi: '3.0.3',
1697+
info: {
1698+
title: 'Test',
1699+
version: '1.0.0',
1700+
},
1701+
paths: {
1702+
'/list': {
1703+
get: {
1704+
summary: 'A route with a list in the comment',
1705+
operationId: 'api.v1.list',
1706+
tags: ['Test Routes'],
1707+
parameters: [
1708+
{
1709+
name: 'permissions',
1710+
in: 'query',
1711+
required: true,
1712+
schema: {
1713+
type: 'string',
1714+
},
1715+
description:
1716+
'The permissions granted by this access token.\n\n- `all` - Access all actions in the test environment.\n- `crypto_compare` - Call CryptoCompare API.',
17101717
},
1711-
description:
1712-
'The permissions granted by this access token.\n\n- `all` - Access all actions in the test environment.\n- `crypto_compare` - Call CryptoCompare API.',
1713-
},
1714-
],
1715-
responses: {
1716-
200: {
1717-
description: 'OK',
1718-
content: {
1719-
'application/json': {
1720-
schema: {
1721-
type: 'string',
1718+
],
1719+
responses: {
1720+
200: {
1721+
description: 'OK',
1722+
content: {
1723+
'application/json': {
1724+
schema: {
1725+
type: 'string',
1726+
},
17221727
},
17231728
},
17241729
},
17251730
},
17261731
},
17271732
},
17281733
},
1734+
components: {
1735+
schemas: {},
1736+
},
17291737
},
1730-
components: {
1731-
schemas: {},
1738+
);
1739+
1740+
const ROUTE_WITH_INLINE_PRESERVE_LINE_BREAKS = `
1741+
import * as t from 'io-ts';
1742+
import * as h from '@api-ts/io-ts-http';
1743+
1744+
/**
1745+
* A route with inline preserveLineBreaks tag
1746+
*
1747+
* @operationId api.v1.inline
1748+
* @tag Test Routes
1749+
*/
1750+
export const route = h.httpRoute({
1751+
path: '/inline',
1752+
method: 'GET',
1753+
request: h.httpRequest({
1754+
query: {
1755+
/**
1756+
* @preserveLineBreaks This tag is inline with other text
1757+
* This is a long description that
1758+
* spans multiple lines in the source.
1759+
*/
1760+
field1: t.string,
1761+
},
1762+
}),
1763+
response: {
1764+
200: t.string
17321765
},
17331766
});
1767+
`;
1768+
1769+
testCase(
1770+
'route with inline @preserveLineBreaks tag',
1771+
ROUTE_WITH_INLINE_PRESERVE_LINE_BREAKS,
1772+
{
1773+
openapi: '3.0.3',
1774+
info: {
1775+
title: 'Test',
1776+
version: '1.0.0',
1777+
},
1778+
paths: {
1779+
'/inline': {
1780+
get: {
1781+
summary: 'A route with inline preserveLineBreaks tag',
1782+
operationId: 'api.v1.inline',
1783+
tags: ['Test Routes'],
1784+
parameters: [
1785+
{
1786+
name: 'field1',
1787+
in: 'query',
1788+
required: true,
1789+
schema: {
1790+
type: 'string',
1791+
},
1792+
description:
1793+
'This tag is inline with other text\nThis is a long description that\nspans multiple lines in the source.',
1794+
},
1795+
],
1796+
responses: {
1797+
200: {
1798+
description: 'OK',
1799+
content: {
1800+
'application/json': {
1801+
schema: {
1802+
type: 'string',
1803+
},
1804+
},
1805+
},
1806+
},
1807+
},
1808+
},
1809+
},
1810+
},
1811+
components: {
1812+
schemas: {},
1813+
},
1814+
},
1815+
);

0 commit comments

Comments
 (0)