Skip to content

Commit 7fd5182

Browse files
committed
feat: add balance to jsdocLineWrappingStyle
#250
1 parent 6663384 commit 7fd5182

File tree

6 files changed

+319
-40
lines changed

6 files changed

+319
-40
lines changed

README.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Add `prettier-plugin-jsdoc` to your `plugins` list.
5454
```js
5555
export default {
5656
plugins: ["prettier-plugin-jsdoc"],
57-
}
57+
};
5858
```
5959

6060
If you want to ignore some types of files, use `overrides` with an empty `plugins`:
@@ -184,22 +184,22 @@ Like code tags (` ```js `), header tags like `# Header`, or other Markdown featu
184184

185185
## Options
186186

187-
| Key | Type | Default | Description |
188-
| :---------------------------------- | :-------------------------------- | :----------- | ----------------------------------------------------------------------------------------------- |
187+
| Key | Type | Default | Description |
188+
| :---------------------------------- | :-------------------------------- | :----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
189189
| `jsdocSpaces` | Number | 1 |
190190
| `jsdocDescriptionWithDot` | Boolean | false |
191191
| `jsdocDescriptionTag` | Boolean | false |
192192
| `jsdocVerticalAlignment` | Boolean | false |
193193
| `jsdocKeepUnParseAbleExampleIndent` | Boolean | false |
194194
| `jsdocCommentLineStrategy` | ("singleLine","multiline","keep") | "singleLine" |
195195
| `jsdocCapitalizeDescription` | Boolean | true |
196-
| `jsdocSeparateReturnsFromParam` | Boolean | false | Adds a space between last `@param` and `@returns` |
197-
| `jsdocSeparateTagGroups` | Boolean | false | Adds a space between tag groups |
198-
| `jsdocPreferCodeFences` | Boolean | false | Always fence code blocks (surround them by triple backticks) |
199-
| `tsdoc` | Boolean | false | See [TSDoc](#tsdoc) |
200-
| `jsdocPrintWidth` | Number | undefined | If you don't set the value to `jsdocPrintWidth`, `printWidth` will be used as `jsdocPrintWidth` |
201-
| `jsdocLineWrappingStyle` | String | "greedy" | "greedy": lines wrap as soon as they reach `printWidth` |
202-
| `jsdocTagsOrder` | String (object) | undefined | See [Custom Tags Order](doc/CUSTOM_TAGS_ORDER.md) |
196+
| `jsdocSeparateReturnsFromParam` | Boolean | false | Adds a space between last `@param` and `@returns` |
197+
| `jsdocSeparateTagGroups` | Boolean | false | Adds a space between tag groups |
198+
| `jsdocPreferCodeFences` | Boolean | false | Always fence code blocks (surround them by triple backticks) |
199+
| `tsdoc` | Boolean | false | See [TSDoc](#tsdoc) |
200+
| `jsdocPrintWidth` | Number | undefined | If you don't set the value to `jsdocPrintWidth`, `printWidth` will be used as `jsdocPrintWidth` |
201+
| `jsdocLineWrappingStyle` | String | "greedy" | "greedy": lines wrap as soon as they reach `printWidth`. "balance": preserve existing line breaks if lines are shorter than `printWidth`, otherwise use greedy wrapping |
202+
| `jsdocTagsOrder` | String (object) | undefined | See [Custom Tags Order](doc/CUSTOM_TAGS_ORDER.md) |
203203

204204
### TSDoc
205205

@@ -226,10 +226,11 @@ To enable, add:
226226
1. Fork and clone the repository
227227
2. [Install Yarn](https://yarnpkg.com/getting-started/install)
228228
3. Install project dependencies:
229-
229+
230230
```sh
231231
yarn install
232232
```
233+
233234
4. Make changes and make sure that tests pass:
234235
```js
235236
yarn run test

src/descriptionFormatter.ts

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ async function formatDescription(
7171

7272
const { printWidth } = options;
7373
const { tagStringLength = 0, beginningSpace } = formatOptions;
74+
const originalText = text; // Save original text for nowrap mode
7475

7576
/**
7677
* change list with dash to dot for example:
@@ -281,21 +282,102 @@ async function formatDescription(
281282
},
282283
);
283284
284-
_paragraph = _paragraph.replace(/\s+/g, " "); // Make single line
285-
286-
if (
287-
options.jsdocCapitalizeDescription &&
288-
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)
289-
)
290-
_paragraph = capitalizer(_paragraph);
291-
if (options.jsdocDescriptionWithDot)
292-
_paragraph = _paragraph.replace(/([\w\p{L}])$/u, "$1."); // Insert dot if needed
293-
294-
let result = breakDescriptionToLines(
295-
_paragraph,
296-
printWidth,
297-
intention,
298-
);
285+
// In balance mode, check if we should preserve original line breaks
286+
// Helper: Apply capitalization to first character if needed
287+
const applyCapitalization = (text: string): string => {
288+
const shouldCapitalize =
289+
options.jsdocCapitalizeDescription &&
290+
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag);
291+
return shouldCapitalize ? capitalizer(text) : text;
292+
};
293+
294+
// Helper: Add trailing dot if needed
295+
const applyTrailingDot = (text: string): string => {
296+
return options.jsdocDescriptionWithDot
297+
? text.replace(/([\w\p{L}])$/u, "$1.")
298+
: text;
299+
};
300+
301+
// Helper: Format text with standard transformations
302+
const applyStandardFormatting = (text: string): string => {
303+
return applyTrailingDot(applyCapitalization(text));
304+
};
305+
306+
// Helper: Join lines with proper indentation
307+
const joinWithIndentation = (lines: string[]): string => {
308+
const indentedLines = lines.map(
309+
(line) => `${intention}${line}`,
310+
);
311+
return indentedLines.join("\n");
312+
};
313+
314+
// Helper: Apply greedy wrapping
315+
const applyGreedyWrapping = (text: string): string => {
316+
const singleLine = text.replace(/\s+/g, " ");
317+
const formatted = applyStandardFormatting(singleLine);
318+
const jsdocPrintWidth =
319+
options.jsdocPrintWidth ?? printWidth;
320+
return breakDescriptionToLines(
321+
formatted,
322+
jsdocPrintWidth,
323+
intention,
324+
);
325+
};
326+
327+
// Main logic: Determine wrapping strategy
328+
const isBalanceMode =
329+
options.jsdocLineWrappingStyle === "balance";
330+
const originalHasLineBreaks = originalText.includes("\n");
331+
const shouldTryBalanceMode =
332+
isBalanceMode && originalHasLineBreaks;
333+
334+
let result: string;
335+
336+
if (shouldTryBalanceMode) {
337+
const originalLines = originalText
338+
.split("\n")
339+
.map((line) => line.trim())
340+
.filter((line) => line.length > 0);
341+
342+
const jsdocPrintWidth =
343+
options.jsdocPrintWidth ?? printWidth;
344+
const effectiveMaxWidth =
345+
tag === DESCRIPTION && tagStringLength > 0
346+
? jsdocPrintWidth - tagStringLength
347+
: jsdocPrintWidth - intention.length;
348+
349+
const allLinesFit = originalLines.every(
350+
(line) => line.length <= effectiveMaxWidth,
351+
);
352+
const hasMultipleLines = originalLines.length > 1;
353+
354+
if (allLinesFit && hasMultipleLines) {
355+
// Preserve original line breaks
356+
const formattedLines = originalLines.map(
357+
(line, index) => {
358+
const isFirstLine = index === 0;
359+
const isLastLine = index === originalLines.length - 1;
360+
361+
let formatted = line;
362+
if (isFirstLine) {
363+
formatted = applyCapitalization(formatted);
364+
}
365+
if (isLastLine) {
366+
formatted = applyTrailingDot(formatted);
367+
}
368+
return formatted;
369+
},
370+
);
371+
372+
result = joinWithIndentation(formattedLines);
373+
} else {
374+
// Fall back to greedy wrapping
375+
result = applyGreedyWrapping(_paragraph);
376+
}
377+
} else {
378+
// Default greedy wrapping mode
379+
result = applyGreedyWrapping(_paragraph);
380+
}
299381
300382
// Replace links
301383
result = result.replace(

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ const options = {
130130
value: "greedy",
131131
description: `Lines wrap as soon as they reach the print width`,
132132
},
133+
{
134+
value: "balance",
135+
description: `Preserve existing line breaks if lines are shorter than print width, otherwise use greedy wrapping`,
136+
},
133137
] as ChoiceSupportOption["choices"],
134138
category: "jsdoc",
135139
default: "greedy",

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface JsdocOptions {
2020
jsdocCapitalizeDescription: boolean;
2121
jsdocPreferCodeFences: boolean;
2222
tsdoc: boolean;
23-
jsdocLineWrappingStyle: "greedy";
23+
jsdocLineWrappingStyle: "greedy" | "balance";
2424
jsdocTagsOrder?: Record<string, number>;
2525
jsdocFormatImports: boolean;
2626
jsdocNamedImportPadding: boolean;

tests/__snapshots__/descriptions.test.ts.snap

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,82 @@ exports[`empty lines 1`] = `
821821
"
822822
`;
823823
824+
exports[`jsdocLineWrappingStyle balance 1`] = `
825+
"/**
826+
* This is a carefully formatted description
827+
* with multiple lines that are balanced
828+
* to avoid runts and improve readability.
829+
*
830+
* @param {string} name - The name parameter
831+
* @returns {boolean} Returns true if successful
832+
*/
833+
"
834+
`;
835+
836+
exports[`jsdocLineWrappingStyle balance 2`] = `
837+
"/**
838+
* This is a very long line that exceeds
839+
* the print width limit and should be
840+
* wrapped using greedy wrapping even in
841+
* balance mode because it's too long to
842+
* preserve as is.
843+
*
844+
* @param {string} name - The name
845+
* parameter
846+
* @returns {boolean} Returns true if
847+
* successful
848+
*/
849+
"
850+
`;
851+
852+
exports[`jsdocLineWrappingStyle balance 3`] = `
853+
"/**
854+
* This is a carefully formatted description with multiple lines that are
855+
* balanced to avoid runts and improve readability.
856+
*
857+
* @param {string} name - The name parameter
858+
* @returns {boolean} Returns true if successful
859+
*/
860+
"
861+
`;
862+
863+
exports[`jsdocLineWrappingStyle balance 4`] = `
864+
"/**
865+
* @param {string} name - This is a carefully formatted
866+
* parameter description with balanced lines
867+
* that should be preserved in balance mode
868+
* @param {number} age - Another parameter with
869+
* balanced formatting
870+
* @returns {boolean} Returns true
871+
*/
872+
"
873+
`;
874+
875+
exports[`jsdocLineWrappingStyle balance with link in description 1`] = `
876+
"/**
877+
* @param {string} name - This is a carefully formatted parameter description
878+
* with balanced lines that should be preserved in balance mode issue:
879+
* {@link https://github.com/hosseinmd/prettier-plugin-jsdoc/issues/98?prettier-plugin-jsdocprettier-plugin-jsdocprettier-plugin-jsdoc}
880+
* @param {number} age - Another parameter with
881+
* balanced formatting
882+
* @returns {boolean} Returns true
883+
*/
884+
"
885+
`;
886+
887+
exports[`jsdocLineWrappingStyle balance with paraghraph in description 1`] = `
888+
"/**
889+
* This is a carefully formatted that should be preserved in balance mode that
890+
* should be preserved in balance mode
891+
*
892+
* Paragraph description with balanced lines
893+
*
894+
* That should be preserved in balance mode that should be preserved in balance
895+
* mode that should be preserved in balance mode
896+
*/
897+
"
898+
`;
899+
824900
exports[`matches prettier markdown format 1`] = `
825901
"/**
826902
* # Header
@@ -970,15 +1046,13 @@ exports[`numbers and code in description 4`] = `
9701046
9711047
exports[`printWidth 1`] = `
9721048
"/**
973-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
974-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
975-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
976-
* A
1049+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
1050+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
1051+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
9771052
*
978-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
979-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
980-
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
981-
* A
1053+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
1054+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
1055+
* A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
9821056
*/
9831057
"
9841058
`;

0 commit comments

Comments
 (0)