@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.
- package/CHANGELOG.md +22 -0
- package/README.md +46 -24
- package/base.json +20 -2
- package/comments/rules/class-documentation.js +5 -2
- package/comments/rules/configured-api-calls.js +2 -2
- package/comments/rules/formatting.js +9 -12
- package/comments/rules/function-documentation.js +22 -3
- package/comments/rules/variable-declarations.js +2 -2
- package/comments/rules/vue-component-documentation.js +1 -1
- package/comments/rules/vue-emit-documentation.js +6 -2
- package/comments/rules/vue-prop-documentation.js +6 -2
- package/comments/utils/documentation.js +36 -29
- package/comments/utils/jsdoc.js +348 -79
- package/comments/utils/source.js +59 -26
- package/comments/utils/wrap.js +48 -14
- package/comments.json +1 -0
- package/imports.json +13 -0
- package/layers.js +60 -0
- package/package.json +11 -4
package/comments/utils/jsdoc.js
CHANGED
|
@@ -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
|
-
//
|
|
178
|
-
const typeMatch = entry
|
|
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
|
|
189
|
+
const type = typeMatch.groups.type;
|
|
186
190
|
// The parameter name, when the tag has one.
|
|
187
|
-
const name = typeMatch
|
|
191
|
+
const name = typeMatch.groups.name;
|
|
188
192
|
|
|
189
|
-
if (
|
|
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
|
|
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
|
|
209
|
+
* The inline description, or an empty string when there is none.
|
|
204
210
|
*/
|
|
205
211
|
function getInlineTagDescription(entry) {
|
|
206
|
-
//
|
|
207
|
-
const
|
|
212
|
+
// The parsed type and description, with a name only for parameters.
|
|
213
|
+
const tagParts = parseTargetTagRest(entry);
|
|
208
214
|
|
|
209
|
-
return
|
|
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
|
-
|
|
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(...
|
|
520
|
+
result.push(...wrapProse(text, width));
|
|
297
521
|
|
|
298
522
|
paragraph = [];
|
|
299
523
|
}
|
|
300
524
|
|
|
301
|
-
for (
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|