@lewishowles/lint-config 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,10 @@ const tagOrder = ["param", "throws", "returns"];
13
13
  const preservedSectionTags = new Set(["example"]);
14
14
  // Fast lookup set built from tagOrder.
15
15
  const targetTags = new Set(tagOrder);
16
+ // Markdown headings start with one to six hashes and a space.
17
+ const markdownHeadingPattern = /^#{1,6}\s/;
18
+ // Markdown list items start with a bullet or numbered marker and a space.
19
+ const markdownListItemPattern = /^(?:[-*]|\d+[.)])\s/;
16
20
 
17
21
  /**
18
22
  * Return whether source text is a JSDoc-style block comment.
@@ -174,19 +178,19 @@ function parseTargetTags(tagLines) {
174
178
  * The aligned tag header.
175
179
  */
176
180
  function formatTagHeader(entry) {
177
- // Splits the tag's remainder into its type, name, and description.
178
- const typeMatch = entry.rest.match(/^(\{[^}]+\})(?:\s+(\S+))?(?:\s+(.*))?$/);
181
+ // The tag's type annotation, plus a name when the tag is @param.
182
+ const typeMatch = parseTargetTagRest(entry);
179
183
 
180
184
  if (!typeMatch) {
181
185
  return `@${entry.type} ${entry.rest}`.trimEnd();
182
186
  }
183
187
 
184
188
  // The bare {type} annotation.
185
- const type = typeMatch[1];
189
+ const type = typeMatch.groups.type;
186
190
  // The parameter name, when the tag has one.
187
- const name = typeMatch[2];
191
+ const name = typeMatch.groups.name;
188
192
 
189
- if (entry.type === "param" && name) {
193
+ if (name) {
190
194
  return `@param ${type} ${name}`;
191
195
  }
192
196
 
@@ -194,23 +198,204 @@ function formatTagHeader(entry) {
194
198
  }
195
199
 
196
200
  /**
197
- * Return the inline description from a target tag.
201
+ * Return a target tag's inline description without a separator hyphen. Once the
202
+ * description moves onto its own line, a leading hyphen would read as a
203
+ * Markdown bullet.
198
204
  *
199
205
  * @param {object} entry
200
206
  * The parsed tag entry.
201
207
  *
202
208
  * @returns {string}
203
- * The inline description, when present.
209
+ * The inline description, or an empty string when there is none.
204
210
  */
205
211
  function getInlineTagDescription(entry) {
206
- // Splits the tag's remainder into its type, name, and description.
207
- const typeMatch = entry.rest.match(/^(\{[^}]+\})(?:\s+(\S+))?(?:\s+(.*))?$/);
212
+ // The parsed type and description, with a name only for parameters.
213
+ const tagParts = parseTargetTagRest(entry);
208
214
 
209
- return typeMatch?.[3] ?? "";
215
+ return (tagParts?.groups.description ?? "").replace(/^-(?:\s+|$)/, "");
216
+ }
217
+
218
+ /**
219
+ * Parse a target tag's type and inline description, including a parameter name
220
+ * only when the tag is @param.
221
+ *
222
+ * @param {object} entry
223
+ * The parsed tag entry.
224
+ *
225
+ * @returns {RegExpMatchArray|null}
226
+ * The type, name and description, or null when there is no type.
227
+ */
228
+ function parseTargetTagRest(entry) {
229
+ if (entry.type === "param") {
230
+ return entry.rest.match(/^(?<type>\{[^}]+\})(?:\s+(?<name>\S+))?(?:\s+(?<description>.*))?$/);
231
+ }
232
+
233
+ return entry.rest.match(/^(?<type>\{[^}]+\})(?:\s+(?<description>.*))?$/);
234
+ }
235
+
236
+ /**
237
+ * Find the lines that belong to a Markdown heading, list, table or fenced code
238
+ * block, so formatting can leave them exactly as written.
239
+ *
240
+ * @param {string[]} lines
241
+ * The undecorated JSDoc lines.
242
+ *
243
+ * @returns {boolean[]}
244
+ * True for each line that belongs to a Markdown block, in line order.
245
+ */
246
+ function getMarkdownStructure(lines) {
247
+ // Whether each line read so far belongs to a Markdown block.
248
+ const structure = [];
249
+
250
+ // Whether the current line is inside a fenced code block.
251
+ let inFence = false;
252
+ // The indentation of the open list item; deeper lines continue it.
253
+ let listIndent = null;
254
+
255
+ for (const line of lines) {
256
+ // The line without surrounding whitespace.
257
+ const trimmed = line.trim();
258
+ // The number of whitespace characters before the line's text.
259
+ const indent = line.length - line.trimStart().length;
260
+
261
+ // A list or table can start on the first line, after a blank line, or
262
+ // straight after another Markdown block.
263
+ const blockBoundary =
264
+ structure.length === 0 || lines[structure.length - 1].trim() === "" || structure.at(-1);
265
+
266
+ if (inFence) {
267
+ structure.push(true);
268
+
269
+ inFence = !trimmed.startsWith("```");
270
+
271
+ continue;
272
+ }
273
+
274
+ if (trimmed.startsWith("```")) {
275
+ structure.push(true);
276
+
277
+ inFence = true;
278
+ listIndent = null;
279
+
280
+ continue;
281
+ }
282
+
283
+ // A bullet or a list numbered from 1 can follow prose directly. Other
284
+ // numbers need a block boundary, so prose such as "2) the second case"
285
+ // isn't read as a list.
286
+ if (
287
+ markdownListItemPattern.test(trimmed) &&
288
+ (blockBoundary || /^1[.)]\s/.test(trimmed) || /^[-*]\s/.test(trimmed))
289
+ ) {
290
+ structure.push(true);
291
+
292
+ listIndent = indent;
293
+
294
+ continue;
295
+ }
296
+
297
+ // An indented line continues the list item above it.
298
+ if (trimmed !== "" && listIndent !== null && indent > listIndent) {
299
+ structure.push(true);
300
+
301
+ continue;
302
+ }
303
+
304
+ listIndent = null;
305
+
306
+ // A heading can start anywhere, but a table must start at a boundary.
307
+ structure.push(
308
+ markdownHeadingPattern.test(trimmed) || (blockBoundary && trimmed.startsWith("|")),
309
+ );
310
+ }
311
+
312
+ return structure;
313
+ }
314
+
315
+ /**
316
+ * Format the prose in a run of JSDoc lines and copy Markdown block lines
317
+ * through unchanged.
318
+ *
319
+ * @param {string[]} lines
320
+ * The undecorated JSDoc lines.
321
+ * @param {function} formatPlainLines
322
+ * Formats one run of prose lines and returns the formatted lines.
323
+ * @param {string[]} structureLines
324
+ * The lines to check for Markdown blocks, when they differ from the lines
325
+ * being formatted. Tag descriptions pass a blank line in place of the
326
+ * inline description so it is never read as Markdown.
327
+ *
328
+ * @returns {string[]}
329
+ * The formatted prose and unchanged Markdown lines.
330
+ */
331
+ function splitMarkdownProse(lines, formatPlainLines, structureLines = lines) {
332
+ // Whether each line belongs to a Markdown block.
333
+ const structure = getMarkdownStructure(structureLines);
334
+ // The formatted lines, including unchanged Markdown blocks.
335
+ const result = [];
336
+
337
+ // The prose waiting to be formatted.
338
+ let prose = [];
339
+
340
+ /**
341
+ * Format the waiting prose, keeping the blank line that separated it from
342
+ * the next Markdown block.
343
+ *
344
+ * @param {boolean} keepBlank
345
+ * Whether a Markdown block follows, so the blank line before it stays.
346
+ */
347
+ function flushProse(keepBlank = false) {
348
+ if (prose.length > 0) {
349
+ // Whether the source separates the prose from the next block.
350
+ const endsWithBlank = prose.at(-1)?.trim() === "";
351
+
352
+ result.push(...formatPlainLines(prose));
353
+
354
+ if (keepBlank && endsWithBlank && result.at(-1) !== "") {
355
+ result.push("");
356
+ }
357
+
358
+ prose = [];
359
+ }
360
+ }
361
+
362
+ for (let index = 0; index < lines.length; index += 1) {
363
+ if (structure[index]) {
364
+ flushProse(true);
365
+ result.push(lines[index]);
366
+ } else {
367
+ prose.push(lines[index]);
368
+ }
369
+ }
370
+
371
+ flushProse();
372
+
373
+ return result;
374
+ }
375
+
376
+ /**
377
+ * Wrap prose without making a new line look like a Markdown block.
378
+ *
379
+ * @param {string} text
380
+ * The prose to wrap.
381
+ * @param {number} width
382
+ * The available content width.
383
+ *
384
+ * @returns {string[]}
385
+ * The wrapped prose lines.
386
+ */
387
+ function wrapProse(text, width) {
388
+ // Joins each word that looks like a Markdown marker to the word before it
389
+ // with a word joiner (U+2060), so wrapping never starts a line with it. The
390
+ // joiner turns back into a space before the lines are returned.
391
+ const joined = text.replace(/(\S+)\s+([-*|]|\d+[.)]|#{1,6})(?=\s)/g, "$1\u2060$2");
392
+
393
+ return wrapWords(joined, width).map((line) => line.replaceAll("\u2060", " "));
210
394
  }
211
395
 
212
396
  /**
213
397
  * Format and refill JSDoc prose while preserving paragraph and list boundaries.
398
+ * Markdown blocks are left as written.
214
399
  *
215
400
  * @param {string[]} lines
216
401
  * The prose content lines.
@@ -223,6 +408,25 @@ function getInlineTagDescription(entry) {
223
408
  * The formatted prose lines.
224
409
  */
225
410
  function formatUnwrappedProse(lines, addPunctuation, indentation) {
411
+ return splitMarkdownProse(lines, (prose) =>
412
+ formatPlainUnwrappedProse(prose, addPunctuation, indentation),
413
+ );
414
+ }
415
+
416
+ /**
417
+ * Refill and format JSDoc prose that contains no Markdown blocks.
418
+ *
419
+ * @param {string[]} lines
420
+ * The prose lines to refill.
421
+ * @param {boolean} addPunctuation
422
+ * Whether to format each paragraph as a sentence.
423
+ * @param {string} indentation
424
+ * The indentation used by the comment.
425
+ *
426
+ * @returns {string[]}
427
+ * The refilled prose lines.
428
+ */
429
+ function formatPlainUnwrappedProse(lines, addPunctuation, indentation) {
226
430
  // The prose lines after refilling, then formatted in place below.
227
431
  const result = refillCommentLines(
228
432
  lines.map((line) => ({ prefix: `${indentation} * `, text: line })),
@@ -259,7 +463,8 @@ function formatUnwrappedProse(lines, addPunctuation, indentation) {
259
463
  }
260
464
 
261
465
  /**
262
- * Format prose paragraphs to the block-comment width.
466
+ * Format prose paragraphs to the block-comment width, leaving Markdown blocks
467
+ * as written.
263
468
  *
264
469
  * @param {string[]} lines
265
470
  * The prose content lines.
@@ -272,7 +477,26 @@ function formatUnwrappedProse(lines, addPunctuation, indentation) {
272
477
  * Formatted prose content lines.
273
478
  */
274
479
  function formatProse(lines, width, addPunctuation) {
275
- // The formatted lines, built up in place.
480
+ return splitMarkdownProse(lines, (proseLines) =>
481
+ formatPlainProse(proseLines, width, addPunctuation),
482
+ );
483
+ }
484
+
485
+ /**
486
+ * Format prose paragraphs that contain no Markdown blocks.
487
+ *
488
+ * @param {string[]} lines
489
+ * The prose lines.
490
+ * @param {number} width
491
+ * The available content width.
492
+ * @param {boolean} addPunctuation
493
+ * Whether to format each paragraph as a sentence.
494
+ *
495
+ * @returns {string[]}
496
+ * The formatted prose lines.
497
+ */
498
+ function formatPlainProse(lines, width, addPunctuation) {
499
+ // The formatted prose lines, built up in place.
276
500
  const result = [];
277
501
 
278
502
  // The prose lines collected for the paragraph in progress.
@@ -293,12 +517,15 @@ function formatProse(lines, width, addPunctuation) {
293
517
  text = formatSentence(text);
294
518
  }
295
519
 
296
- result.push(...wrapWords(text, width));
520
+ result.push(...wrapProse(text, width));
297
521
 
298
522
  paragraph = [];
299
523
  }
300
524
 
301
- for (const line of lines) {
525
+ for (let index = 0; index < lines.length; index += 1) {
526
+ // The current undecorated JSDoc line.
527
+ const line = lines[index];
528
+
302
529
  if (line.trim() === "") {
303
530
  flushParagraph();
304
531
 
@@ -325,7 +552,8 @@ function formatProse(lines, width, addPunctuation) {
325
552
  * @param {string[]} tagLines
326
553
  * The undecorated tag content.
327
554
  * @param {number} width
328
- * The available description width.
555
+ * The width of the comment text. @param, @returns and @throws descriptions
556
+ * wrap four characters narrower to fit their indent.
329
557
  * @param {boolean} addPunctuation
330
558
  * Whether to format tag descriptions as sentences.
331
559
  * @param {boolean} normaliseTags
@@ -364,22 +592,7 @@ function formatTags(tagLines, width, addPunctuation, normaliseTags) {
364
592
  }
365
593
 
366
594
  result.push(formatTagHeader(entry));
367
-
368
- // The entry's inline and multi-line description text, combined.
369
- const description = [getInlineTagDescription(entry), ...entry.description]
370
- .filter((line) => line.trim() !== "")
371
- .map((line) => line.trim());
372
-
373
- // The description text, punctuated as a sentence when requested.
374
- let descriptionText = description.join(" ");
375
-
376
- if (addPunctuation && descriptionText !== "") {
377
- descriptionText = formatSentence(descriptionText);
378
- }
379
-
380
- if (descriptionText !== "") {
381
- result.push(...wrapWords(descriptionText, width).map((line) => ` ${line}`));
382
- }
595
+ result.push(...formatTargetDescription(entry, width, addPunctuation));
383
596
 
384
597
  lastType = entry.type;
385
598
  }
@@ -393,7 +606,8 @@ function formatTags(tagLines, width, addPunctuation, normaliseTags) {
393
606
  * @param {string[]} lines
394
607
  * The undecorated tag content lines.
395
608
  * @param {number} width
396
- * The available description width.
609
+ * The width of the comment text. @param, @returns and @throws descriptions
610
+ * wrap four characters narrower to fit their indent.
397
611
  * @param {boolean} addPunctuation
398
612
  * Whether to format descriptions as sentences.
399
613
  *
@@ -401,6 +615,9 @@ function formatTags(tagLines, width, addPunctuation, normaliseTags) {
401
615
  * Formatted mixed tag content lines.
402
616
  */
403
617
  function formatMixedTags(lines, width, addPunctuation) {
618
+ // Whether each line belongs to a Markdown block. Tag lines count as blank
619
+ // so a list ends at the next tag.
620
+ const structure = getMarkdownStructure(lines.map((line) => (getJSDocTagName(line) ? "" : line)));
404
621
  // The formatted lines, built up in place.
405
622
  const result = [];
406
623
 
@@ -409,46 +626,61 @@ function formatMixedTags(lines, width, addPunctuation) {
409
626
  // Whether the current tag's content is copied through unchanged.
410
627
  let preserveSection = false;
411
628
 
412
- for (const line of lines) {
629
+ for (let index = 0; index < lines.length; index += 1) {
630
+ // The current undecorated JSDoc line.
631
+ const line = lines[index];
413
632
  // The tag name, or null when the line isn't a tag at all.
414
633
  const tagName = getJSDocTagName(line);
415
634
  // Matches a new @param/@throws/@returns tag line.
416
635
  const match = line.trim().match(/^@(param|throws|returns)\b(.*)$/);
417
636
 
637
+ if (tagName !== null && currentEntry) {
638
+ // Whether the author left a blank line between this description and
639
+ // the next tag.
640
+ const hasTagSeparator = currentEntry.description.at(-1)?.trim() === "";
641
+
642
+ result.push(...formatTargetDescription(currentEntry, width, addPunctuation));
643
+
644
+ if (hasTagSeparator && result.at(-1) !== "") {
645
+ result.push("");
646
+ }
647
+
648
+ currentEntry = null;
649
+ }
650
+
418
651
  if (match) {
419
652
  currentEntry = {
420
653
  description: [],
421
654
  rest: match[2].trim(),
422
655
  type: match[1],
423
656
  };
657
+
424
658
  preserveSection = false;
425
659
 
426
660
  result.push(formatTagHeader(currentEntry));
427
661
  } else if (tagName !== null) {
428
- currentEntry = null;
429
662
  preserveSection = isPreservedSectionTag(line);
430
663
 
431
664
  result.push(line.trim());
665
+ } else if (currentEntry) {
666
+ currentEntry.description.push(line);
432
667
  } else if (preserveSection) {
433
668
  result.push(line);
669
+ } else if (structure[index]) {
670
+ result.push(line);
434
671
  } else if (line.trim() === "") {
435
- currentEntry = null;
436
-
437
672
  if (result.at(-1) !== "") {
438
673
  result.push("");
439
674
  }
440
675
  } else {
441
- // The description text, punctuated as a sentence when requested.
442
- let text = line.trim();
443
-
444
- if (addPunctuation && currentEntry) {
445
- text = formatSentence(text);
446
- }
447
-
448
- result.push(...wrapWords(text, width).map((wrappedLine) => ` ${wrappedLine}`));
676
+ result.push(...wrapProse(line.trim(), width));
449
677
  }
450
678
  }
451
679
 
680
+ if (currentEntry) {
681
+ result.push(...formatTargetDescription(currentEntry, width, addPunctuation));
682
+ }
683
+
452
684
  while (result.at(-1) === "") {
453
685
  result.pop();
454
686
  }
@@ -456,13 +688,60 @@ function formatMixedTags(lines, width, addPunctuation) {
456
688
  return result;
457
689
  }
458
690
 
691
+ /**
692
+ * Format the description of a @param, @returns or @throws tag, joining the text
693
+ * on the tag line with the lines below it.
694
+ *
695
+ * @param {object} entry
696
+ * The parsed tag, with its tag-line text and the description lines
697
+ * collected beneath it.
698
+ * @param {number} width
699
+ * The width of the comment text. The description wraps four characters
700
+ * narrower to fit its indent.
701
+ * @param {boolean} addPunctuation
702
+ * Whether to format prose paragraphs as sentences.
703
+ *
704
+ * @returns {string[]}
705
+ * The formatted description lines beneath the tag header.
706
+ */
707
+ function formatTargetDescription(entry, width, addPunctuation) {
708
+ // The description written on the tag line, without a leading hyphen.
709
+ const inlineDescription = getInlineTagDescription(entry);
710
+ // The full tag description, before wrapping.
711
+ const description = [inlineDescription, ...entry.description];
712
+
713
+ // Drop blank lines around the description.
714
+ while (description[0]?.trim() === "") {
715
+ description.shift();
716
+ }
717
+
718
+ while (description.at(-1)?.trim() === "") {
719
+ description.pop();
720
+ }
721
+
722
+ // Ignore the inline description when finding Markdown blocks, so a
723
+ // description that starts with something like "1." or "#" stays prose.
724
+ const structuralDescription = inlineDescription ? ["", ...description.slice(1)] : description;
725
+
726
+ return splitMarkdownProse(
727
+ description,
728
+ (prose) => {
729
+ return formatPlainProse(prose, Math.max(1, width - 4), addPunctuation).map((line) => {
730
+ return line === "" ? "" : ` ${line}`;
731
+ });
732
+ },
733
+ structuralDescription,
734
+ );
735
+ }
736
+
459
737
  /**
460
738
  * Format descriptions while retaining non-target tag lines.
461
739
  *
462
740
  * @param {string[]} lines
463
741
  * The undecorated tag content lines.
464
742
  * @param {number} width
465
- * The available description width.
743
+ * The width of the comment text. @param, @returns and @throws descriptions
744
+ * wrap four characters narrower to fit their indent.
466
745
  * @param {boolean} addPunctuation
467
746
  * Whether to format descriptions as sentences.
468
747
  *
@@ -470,11 +749,17 @@ function formatMixedTags(lines, width, addPunctuation) {
470
749
  * Formatted tag content lines.
471
750
  */
472
751
  function formatTagDescriptions(lines, width, addPunctuation) {
752
+ // Whether each line belongs to a Markdown block. Tag lines count as blank
753
+ // so a list ends at the next tag.
754
+ const structure = getMarkdownStructure(lines.map((line) => (getJSDocTagName(line) ? "" : line)));
473
755
  // The formatted lines, built up in place.
474
756
  const result = [];
475
757
 
476
758
  // The description lines collected for the tag in progress.
477
759
  let description = [];
760
+ // Whether the current tag is @param, @returns or @throws, whose description
761
+ // keeps a four-space indent.
762
+ let indentDescription = false;
478
763
  // Whether the current tag's content is copied through unchanged.
479
764
  let preserveSection = false;
480
765
 
@@ -493,12 +778,20 @@ function formatTagDescriptions(lines, width, addPunctuation) {
493
778
  text = formatSentence(text);
494
779
  }
495
780
 
496
- result.push(...wrapWords(text, width).map((line) => ` ${line}`));
781
+ // The wrapping width, four characters narrower for @param, @returns and
782
+ // @throws so their indent still fits.
783
+ const descriptionWidth = indentDescription ? Math.max(1, width - 4) : width;
784
+ // The indent applied to each wrapped description line.
785
+ const indent = indentDescription ? " " : "";
786
+
787
+ result.push(...wrapProse(text, descriptionWidth).map((line) => `${indent}${line}`));
497
788
 
498
789
  description = [];
499
790
  }
500
791
 
501
- for (const line of lines) {
792
+ for (let index = 0; index < lines.length; index += 1) {
793
+ // The current undecorated JSDoc line.
794
+ const line = lines[index];
502
795
  // The tag name, or null when the line isn't a tag at all.
503
796
  const tagName = getJSDocTagName(line);
504
797
 
@@ -506,9 +799,13 @@ function formatTagDescriptions(lines, width, addPunctuation) {
506
799
  flushDescription();
507
800
  result.push(line.trim());
508
801
 
802
+ indentDescription = isTargetTag(line);
509
803
  preserveSection = isPreservedSectionTag(line);
510
804
  } else if (preserveSection) {
511
805
  result.push(line);
806
+ } else if (structure[index]) {
807
+ flushDescription();
808
+ result.push(line);
512
809
  } else if (line.trim() === "") {
513
810
  flushDescription();
514
811
 
@@ -644,15 +941,8 @@ export function formatJSDocBlockStructure(commentText, formattingOptions) {
644
941
  const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
645
942
  // The prose lines, refilled before tags are appended.
646
943
  const prose = formatUnwrappedProse(formattingContext.proseLines, false, formattingContext.indent);
647
-
648
944
  // The tag lines, without spacing or grouping normalisation.
649
- const tags = formatTags(
650
- formattingContext.tagLines,
651
- Math.max(1, formattingContext.width - 4),
652
- false,
653
- false,
654
- );
655
-
945
+ const tags = formatTags(formattingContext.tagLines, formattingContext.width, false, false);
656
946
  // The formatted comment content, before tags are appended.
657
947
  const outputLines = [...prose];
658
948
 
@@ -691,15 +981,8 @@ export function formatJSDocTagFormatting(commentText, formattingOptions) {
691
981
  const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
692
982
  // The prose, rewrapped to the comment's available width.
693
983
  const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
694
-
695
984
  // The tag lines, with spacing and grouping normalised.
696
- const tags = formatTags(
697
- formattingContext.tagLines,
698
- Math.max(1, formattingContext.width - 4),
699
- false,
700
- true,
701
- );
702
-
985
+ const tags = formatTags(formattingContext.tagLines, formattingContext.width, false, true);
703
986
  // The formatted comment content, before tags are appended.
704
987
  const outputLines = [...prose];
705
988
 
@@ -725,15 +1008,8 @@ export function formatJSDocPunctuation(commentText, formattingOptions) {
725
1008
  const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
726
1009
  // The prose, capitalised and punctuated as sentences.
727
1010
  const prose = formatUnwrappedProse(formattingContext.proseLines, true, formattingContext.indent);
728
-
729
1011
  // The tag lines, with descriptions punctuated as sentences.
730
- const tags = formatTags(
731
- formattingContext.tagLines,
732
- Math.max(1, formattingContext.width - 4),
733
- true,
734
- false,
735
- );
736
-
1012
+ const tags = formatTags(formattingContext.tagLines, formattingContext.width, true, false);
737
1013
  // The formatted comment content, before tags are appended.
738
1014
  const outputLines = [...prose];
739
1015
 
@@ -759,15 +1035,8 @@ export function formatJSDocWrapping(commentText, formattingOptions) {
759
1035
  const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
760
1036
  // The prose, rewrapped to the comment's available width.
761
1037
  const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
762
-
763
1038
  // The tag lines, without spacing or grouping normalisation.
764
- const tags = formatTags(
765
- formattingContext.tagLines,
766
- Math.max(1, formattingContext.width - 4),
767
- false,
768
- false,
769
- );
770
-
1039
+ const tags = formatTags(formattingContext.tagLines, formattingContext.width, false, false);
771
1040
  // The formatted comment content, before tags are appended.
772
1041
  const outputLines = [...prose];
773
1042