@lewishowles/lint-config 0.5.0 → 0.6.1
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 +19 -0
- package/README.md +2 -2
- package/base.json +23 -4
- package/comments/plugin.js +2 -12
- package/comments/rules/configured-api-calls.js +2 -4
- package/comments/rules/formatting.js +868 -0
- package/comments/rules/variable-declarations.js +15 -5
- package/comments/rules/vue-component-documentation.js +5 -6
- package/comments/utils/documentation.js +31 -11
- package/comments/utils/jsdoc.js +78 -59
- package/comments/utils/source.js +0 -2
- package/comments/utils/wrap.js +123 -2
- package/comments.json +1 -6
- package/package.json +1 -1
- package/comments/rules/block-comments.js +0 -71
- package/comments/rules/jsdoc-tag-formatting.js +0 -71
- package/comments/rules/line-comments.js +0 -86
- package/comments/rules/max-line-length.js +0 -169
- package/comments/rules/placement.js +0 -293
- package/comments/rules/sentence-punctuation.js +0 -281
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDocumentationNode } from "../utils/documentation.js";
|
|
1
|
+
import { getDocumentationNode, isFunctionValue } from "../utils/documentation.js";
|
|
2
2
|
import { hasImmediateLineComment } from "../utils/source.js";
|
|
3
3
|
|
|
4
4
|
// Declaration kinds that require an immediately preceding line comment.
|
|
@@ -79,21 +79,31 @@ export default {
|
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// Whether every name in this const holds a function. The
|
|
83
|
+
// function-documentation rule already requires a JSDoc block on
|
|
84
|
+
// those, so asking for a line comment as well would make the
|
|
85
|
+
// two rules impossible to satisfy together.
|
|
86
|
+
const isFunctionValuedConst =
|
|
87
|
+
node.kind === "const" &&
|
|
88
|
+
node.declarations.every(
|
|
89
|
+
({ id, init }) => id.type === "Identifier" && isFunctionValue(init),
|
|
90
|
+
);
|
|
91
|
+
|
|
82
92
|
// The rule's resolved options for the file currently being
|
|
83
93
|
// visited.
|
|
84
94
|
const options = context.options?.[0];
|
|
85
|
-
|
|
86
95
|
// Resolves any export wrapper before checking for
|
|
87
96
|
// documentation.
|
|
88
97
|
const documentationNode = getDocumentationNode(node);
|
|
89
98
|
|
|
90
|
-
// With rootOnly, only declarations directly under the
|
|
91
|
-
//
|
|
92
|
-
//
|
|
99
|
+
// With rootOnly, only declarations directly under the Program
|
|
100
|
+
// need a comment, so a nested variable inside a function is
|
|
101
|
+
// left alone.
|
|
93
102
|
const shouldCheckDocumentation =
|
|
94
103
|
!options?.rootOnly || documentationNode.parent?.type === "Program";
|
|
95
104
|
|
|
96
105
|
if (
|
|
106
|
+
!isFunctionValuedConst &&
|
|
97
107
|
shouldCheckDocumentation &&
|
|
98
108
|
!hasImmediateLineComment(context.sourceCode, documentationNode)
|
|
99
109
|
) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { isDirectiveComment } from "../utils/source.js";
|
|
3
3
|
|
|
4
|
-
// Captures the opening tag's attributes separately, so the setup attribute
|
|
5
|
-
//
|
|
4
|
+
// Captures the opening tag's attributes separately, so the setup attribute can
|
|
5
|
+
// be tested without the tag being available from Oxlint's extracted AST.
|
|
6
6
|
const scriptBlockPattern =
|
|
7
7
|
/(?<openingTag><script\b(?<attributes>[^>]*)>)(?<content>[\s\S]*?)<\/script\s*>/gi;
|
|
8
8
|
|
|
@@ -69,8 +69,8 @@ function getScriptBlocks(context) {
|
|
|
69
69
|
* The matching raw script block, when one is found.
|
|
70
70
|
*/
|
|
71
71
|
function findScriptBlock(context) {
|
|
72
|
-
// Re-read on every call rather than caching, since this file's other
|
|
73
|
-
//
|
|
72
|
+
// Re-read on every call rather than caching, since this file's other script
|
|
73
|
+
// blocks may not have been visited yet or ever in this run.
|
|
74
74
|
const scriptBlocks = getScriptBlocks(context);
|
|
75
75
|
|
|
76
76
|
return scriptBlocks.find(
|
|
@@ -153,8 +153,7 @@ export default {
|
|
|
153
153
|
// createOnce builds this visitor once for the whole run, and a
|
|
154
154
|
// single file's script blocks are not necessarily visited
|
|
155
155
|
// consecutively, so the matching block is looked up fresh on
|
|
156
|
-
// each
|
|
157
|
-
// call rather than tracked with shared state.
|
|
156
|
+
// each call rather than tracked with shared state.
|
|
158
157
|
const scriptBlock = findScriptBlock(context);
|
|
159
158
|
|
|
160
159
|
if (hasComponentDocumentation(context, scriptBlock)) {
|
|
@@ -157,11 +157,14 @@ function getObjectPatternPaths(sourceCode, node, parentPath) {
|
|
|
157
157
|
* The Oxlint source code object.
|
|
158
158
|
* @param {object} node
|
|
159
159
|
* The parameter node.
|
|
160
|
+
* @param {string} [rootPath]
|
|
161
|
+
* The documented name that starts each path for a destructured object
|
|
162
|
+
* parameter. Defaults to options.
|
|
160
163
|
*
|
|
161
164
|
* @returns {string[]}
|
|
162
165
|
* The required JSDoc parameter paths.
|
|
163
166
|
*/
|
|
164
|
-
function getParameterPaths(sourceCode, node) {
|
|
167
|
+
function getParameterPaths(sourceCode, node, rootPath = "options") {
|
|
165
168
|
if (node.type === "Identifier") {
|
|
166
169
|
return [node.name];
|
|
167
170
|
}
|
|
@@ -171,11 +174,11 @@ function getParameterPaths(sourceCode, node) {
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
if (node.type === "ObjectPattern") {
|
|
174
|
-
return getObjectPatternPaths(sourceCode, node,
|
|
177
|
+
return getObjectPatternPaths(sourceCode, node, rootPath);
|
|
175
178
|
}
|
|
176
179
|
|
|
177
180
|
if (node.type === "AssignmentPattern") {
|
|
178
|
-
return getParameterPaths(sourceCode, node.left);
|
|
181
|
+
return getParameterPaths(sourceCode, node.left, rootPath);
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
return [];
|
|
@@ -189,25 +192,40 @@ function getParameterPaths(sourceCode, node) {
|
|
|
189
192
|
* @param {object} comment
|
|
190
193
|
* The JSDoc comment token.
|
|
191
194
|
*
|
|
192
|
-
* @returns {
|
|
193
|
-
*
|
|
195
|
+
* @returns {object}
|
|
196
|
+
* An object with names, the set of every documented parameter path, and
|
|
197
|
+
* topLevelNames, the top-level names in the order they are documented.
|
|
194
198
|
*/
|
|
195
199
|
function getDocumentedParameters(sourceCode, comment) {
|
|
196
200
|
// Splits the JSDoc block into its individual lines.
|
|
197
201
|
const content = getJSDocContent(getCommentText(sourceCode, comment));
|
|
198
202
|
// Collects the parameter paths documented by @param tags.
|
|
199
203
|
const names = new Set();
|
|
204
|
+
// Collects top-level parameter names in their documented order.
|
|
205
|
+
const topLevelNames = [];
|
|
200
206
|
|
|
201
207
|
for (const line of content) {
|
|
202
208
|
// Matches an @param tag and captures its documented path.
|
|
203
209
|
const match = line.trim().match(/^@param(?:\s+\{[^}]+\})?\s+(\[[^\]]+\]|\S+)/);
|
|
204
210
|
|
|
205
211
|
if (match) {
|
|
206
|
-
|
|
212
|
+
// The documented name as written, including optional brackets and
|
|
213
|
+
// any default value.
|
|
214
|
+
const name = match[1];
|
|
215
|
+
|
|
216
|
+
names.add(name);
|
|
217
|
+
|
|
218
|
+
// Removes optional and default-value syntax before checking for a
|
|
219
|
+
// nested path.
|
|
220
|
+
const topLevelName = name.replace(/^\[|\]$/g, "").split("=")[0];
|
|
221
|
+
|
|
222
|
+
if (!topLevelName.includes(".")) {
|
|
223
|
+
topLevelNames.push(topLevelName);
|
|
224
|
+
}
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
227
|
|
|
210
|
-
return names;
|
|
228
|
+
return { names, topLevelNames };
|
|
211
229
|
}
|
|
212
230
|
|
|
213
231
|
/**
|
|
@@ -293,7 +311,6 @@ function hasValueReturn(node) {
|
|
|
293
311
|
export function reportFunctionDocumentation(context, node, functionNode, options = {}) {
|
|
294
312
|
// Defaults to "Functions" when the caller names no declaration kind.
|
|
295
313
|
const subject = options.subject ?? "Functions";
|
|
296
|
-
|
|
297
314
|
// Finds the JSDoc block documenting this function, when present.
|
|
298
315
|
const comment = getDocumentationComment(context.sourceCode, node);
|
|
299
316
|
|
|
@@ -307,11 +324,14 @@ export function reportFunctionDocumentation(context, node, functionNode, options
|
|
|
307
324
|
}
|
|
308
325
|
|
|
309
326
|
// Reads the parameter paths already documented by @param tags.
|
|
310
|
-
const documentedParameters = getDocumentedParameters(
|
|
327
|
+
const { names: documentedParameters, topLevelNames } = getDocumentedParameters(
|
|
328
|
+
context.sourceCode,
|
|
329
|
+
comment,
|
|
330
|
+
);
|
|
311
331
|
|
|
312
332
|
// Derives the parameter paths the function actually requires.
|
|
313
|
-
const parameterPaths = functionNode.params.flatMap((parameter) =>
|
|
314
|
-
getParameterPaths(context.sourceCode, parameter),
|
|
333
|
+
const parameterPaths = functionNode.params.flatMap((parameter, index) =>
|
|
334
|
+
getParameterPaths(context.sourceCode, parameter, topLevelNames[index]),
|
|
315
335
|
);
|
|
316
336
|
|
|
317
337
|
for (const path of parameterPaths) {
|
package/comments/utils/jsdoc.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getDisplayWidth } from "./source.js";
|
|
2
|
+
import {
|
|
3
|
+
addTerminalPunctuation,
|
|
4
|
+
capitaliseSentence,
|
|
5
|
+
formatSentence,
|
|
6
|
+
refillCommentLines,
|
|
7
|
+
wrapWords,
|
|
8
|
+
} from "./wrap.js";
|
|
3
9
|
|
|
4
10
|
// The JSDoc tags this package formats, in their required output order.
|
|
5
11
|
const tagOrder = ["param", "throws", "returns"];
|
|
@@ -148,6 +154,7 @@ function parseTargetTags(tagLines) {
|
|
|
148
154
|
rest: match[2].trim(),
|
|
149
155
|
type: match[1],
|
|
150
156
|
};
|
|
157
|
+
|
|
151
158
|
entries.push(currentEntry);
|
|
152
159
|
} else if (currentEntry) {
|
|
153
160
|
currentEntry.description.push(line);
|
|
@@ -203,19 +210,24 @@ function getInlineTagDescription(entry) {
|
|
|
203
210
|
}
|
|
204
211
|
|
|
205
212
|
/**
|
|
206
|
-
* Format prose
|
|
213
|
+
* Format and refill JSDoc prose while preserving paragraph and list boundaries.
|
|
207
214
|
*
|
|
208
215
|
* @param {string[]} lines
|
|
209
216
|
* The prose content lines.
|
|
210
217
|
* @param {boolean} addPunctuation
|
|
211
218
|
* Whether to format each paragraph as a sentence.
|
|
219
|
+
* @param {string} indentation
|
|
220
|
+
* The indentation used by the comment.
|
|
212
221
|
*
|
|
213
222
|
* @returns {string[]}
|
|
214
223
|
* The formatted prose lines.
|
|
215
224
|
*/
|
|
216
|
-
function formatUnwrappedProse(lines, addPunctuation) {
|
|
217
|
-
// The
|
|
218
|
-
const result =
|
|
225
|
+
function formatUnwrappedProse(lines, addPunctuation, indentation) {
|
|
226
|
+
// The prose lines after refilling, then formatted in place below.
|
|
227
|
+
const result = refillCommentLines(
|
|
228
|
+
lines.map((line) => ({ prefix: `${indentation} * `, text: line })),
|
|
229
|
+
80,
|
|
230
|
+
).map(({ text }) => text);
|
|
219
231
|
|
|
220
232
|
// The index of the current paragraph's first line, or null between
|
|
221
233
|
// paragraphs.
|
|
@@ -282,12 +294,14 @@ function formatProse(lines, width, addPunctuation) {
|
|
|
282
294
|
}
|
|
283
295
|
|
|
284
296
|
result.push(...wrapWords(text, width));
|
|
297
|
+
|
|
285
298
|
paragraph = [];
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
for (const line of lines) {
|
|
289
302
|
if (line.trim() === "") {
|
|
290
303
|
flushParagraph();
|
|
304
|
+
|
|
291
305
|
if (result.at(-1) !== "") {
|
|
292
306
|
result.push("");
|
|
293
307
|
}
|
|
@@ -408,15 +422,18 @@ function formatMixedTags(lines, width, addPunctuation) {
|
|
|
408
422
|
type: match[1],
|
|
409
423
|
};
|
|
410
424
|
preserveSection = false;
|
|
425
|
+
|
|
411
426
|
result.push(formatTagHeader(currentEntry));
|
|
412
427
|
} else if (tagName !== null) {
|
|
413
428
|
currentEntry = null;
|
|
414
429
|
preserveSection = isPreservedSectionTag(line);
|
|
430
|
+
|
|
415
431
|
result.push(line.trim());
|
|
416
432
|
} else if (preserveSection) {
|
|
417
433
|
result.push(line);
|
|
418
434
|
} else if (line.trim() === "") {
|
|
419
435
|
currentEntry = null;
|
|
436
|
+
|
|
420
437
|
if (result.at(-1) !== "") {
|
|
421
438
|
result.push("");
|
|
422
439
|
}
|
|
@@ -477,6 +494,7 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
477
494
|
}
|
|
478
495
|
|
|
479
496
|
result.push(...wrapWords(text, width).map((line) => ` ${line}`));
|
|
497
|
+
|
|
480
498
|
description = [];
|
|
481
499
|
}
|
|
482
500
|
|
|
@@ -487,11 +505,13 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
487
505
|
if (tagName !== null) {
|
|
488
506
|
flushDescription();
|
|
489
507
|
result.push(line.trim());
|
|
508
|
+
|
|
490
509
|
preserveSection = isPreservedSectionTag(line);
|
|
491
510
|
} else if (preserveSection) {
|
|
492
511
|
result.push(line);
|
|
493
512
|
} else if (line.trim() === "") {
|
|
494
513
|
flushDescription();
|
|
514
|
+
|
|
495
515
|
if (result.at(-1) !== "") {
|
|
496
516
|
result.push("");
|
|
497
517
|
}
|
|
@@ -512,21 +532,18 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
512
532
|
/**
|
|
513
533
|
* Return the details needed to format a JSDoc block comment.
|
|
514
534
|
*
|
|
515
|
-
* @param {
|
|
516
|
-
* The
|
|
517
|
-
* @param {object}
|
|
518
|
-
* The
|
|
535
|
+
* @param {string} commentText
|
|
536
|
+
* The JSDoc comment text.
|
|
537
|
+
* @param {object} formattingOptions
|
|
538
|
+
* The indentation (`indent`) and newline style (`newline`) of the source
|
|
539
|
+
* around the comment.
|
|
519
540
|
*
|
|
520
541
|
* @returns {object}
|
|
521
542
|
* The JSDoc content and layout details.
|
|
522
543
|
*/
|
|
523
|
-
function getJSDocFormattingContext(
|
|
524
|
-
// The
|
|
525
|
-
const
|
|
526
|
-
// The indentation the comment's lines are aligned to.
|
|
527
|
-
const indent = getLineIndent(sourceCode, comment.range[0]) ?? "";
|
|
528
|
-
// The newline style used by the surrounding source.
|
|
529
|
-
const newline = getNewline(sourceCode.text);
|
|
544
|
+
function getJSDocFormattingContext(commentText, formattingOptions) {
|
|
545
|
+
// The indentation and newline style used by the surrounding source.
|
|
546
|
+
const { indent, newline } = formattingOptions;
|
|
530
547
|
// The available content width, allowing for the indent and " * " prefix.
|
|
531
548
|
const width = Math.max(1, 80 - getDisplayWidth(indent) - 3);
|
|
532
549
|
// The undecorated comment content lines.
|
|
@@ -613,19 +630,20 @@ function renderJSDocComment(formattingContext, outputLines) {
|
|
|
613
630
|
/**
|
|
614
631
|
* Format JSDoc block structure and delimiters.
|
|
615
632
|
*
|
|
616
|
-
* @param {
|
|
617
|
-
* The
|
|
618
|
-
* @param {object}
|
|
619
|
-
* The
|
|
633
|
+
* @param {string} commentText
|
|
634
|
+
* The JSDoc comment text.
|
|
635
|
+
* @param {object} formattingOptions
|
|
636
|
+
* The indentation (`indent`) and newline style (`newline`) of the source
|
|
637
|
+
* around the comment.
|
|
620
638
|
*
|
|
621
639
|
* @returns {string}
|
|
622
640
|
* The formatted comment text.
|
|
623
641
|
*/
|
|
624
|
-
export function formatJSDocBlockStructure(
|
|
642
|
+
export function formatJSDocBlockStructure(commentText, formattingOptions) {
|
|
625
643
|
// The comment's parsed content and layout details.
|
|
626
|
-
const formattingContext = getJSDocFormattingContext(
|
|
627
|
-
// The prose lines,
|
|
628
|
-
const prose = formatUnwrappedProse(formattingContext.proseLines, false);
|
|
644
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
645
|
+
// The prose lines, refilled before tags are appended.
|
|
646
|
+
const prose = formatUnwrappedProse(formattingContext.proseLines, false, formattingContext.indent);
|
|
629
647
|
|
|
630
648
|
// The tag lines, without spacing or grouping normalisation.
|
|
631
649
|
const tags = formatTags(
|
|
@@ -643,20 +661,34 @@ export function formatJSDocBlockStructure(sourceCode, comment) {
|
|
|
643
661
|
return renderJSDocComment(formattingContext, outputLines);
|
|
644
662
|
}
|
|
645
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Return whether a JSDoc comment contains a @param, @throws, or @returns tag.
|
|
666
|
+
*
|
|
667
|
+
* @param {string} commentText
|
|
668
|
+
* The JSDoc comment text.
|
|
669
|
+
*
|
|
670
|
+
* @returns {boolean}
|
|
671
|
+
* Whether one of those tags is present.
|
|
672
|
+
*/
|
|
673
|
+
export function hasTargetJSDocTag(commentText) {
|
|
674
|
+
return getJSDocContent(commentText).some(isTargetTag);
|
|
675
|
+
}
|
|
676
|
+
|
|
646
677
|
/**
|
|
647
678
|
* Format JSDoc tag spacing, order, and grouping.
|
|
648
679
|
*
|
|
649
|
-
* @param {
|
|
650
|
-
* The
|
|
651
|
-
* @param {object}
|
|
652
|
-
* The
|
|
680
|
+
* @param {string} commentText
|
|
681
|
+
* The JSDoc comment text.
|
|
682
|
+
* @param {object} formattingOptions
|
|
683
|
+
* The indentation (`indent`) and newline style (`newline`) of the source
|
|
684
|
+
* around the comment.
|
|
653
685
|
*
|
|
654
686
|
* @returns {string}
|
|
655
687
|
* The formatted comment text.
|
|
656
688
|
*/
|
|
657
|
-
export function formatJSDocTagFormatting(
|
|
689
|
+
export function formatJSDocTagFormatting(commentText, formattingOptions) {
|
|
658
690
|
// The comment's parsed content and layout details.
|
|
659
|
-
const formattingContext = getJSDocFormattingContext(
|
|
691
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
660
692
|
// The prose, rewrapped to the comment's available width.
|
|
661
693
|
const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
|
|
662
694
|
|
|
@@ -679,19 +711,20 @@ export function formatJSDocTagFormatting(sourceCode, comment) {
|
|
|
679
711
|
/**
|
|
680
712
|
* Format JSDoc sentence capitalisation and punctuation.
|
|
681
713
|
*
|
|
682
|
-
* @param {
|
|
683
|
-
* The
|
|
684
|
-
* @param {object}
|
|
685
|
-
* The
|
|
714
|
+
* @param {string} commentText
|
|
715
|
+
* The JSDoc comment text.
|
|
716
|
+
* @param {object} formattingOptions
|
|
717
|
+
* The indentation (`indent`) and newline style (`newline`) of the source
|
|
718
|
+
* around the comment.
|
|
686
719
|
*
|
|
687
720
|
* @returns {string}
|
|
688
721
|
* The formatted comment text.
|
|
689
722
|
*/
|
|
690
|
-
export function formatJSDocPunctuation(
|
|
723
|
+
export function formatJSDocPunctuation(commentText, formattingOptions) {
|
|
691
724
|
// The comment's parsed content and layout details.
|
|
692
|
-
const formattingContext = getJSDocFormattingContext(
|
|
725
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
693
726
|
// The prose, capitalised and punctuated as sentences.
|
|
694
|
-
const prose = formatUnwrappedProse(formattingContext.proseLines, true);
|
|
727
|
+
const prose = formatUnwrappedProse(formattingContext.proseLines, true, formattingContext.indent);
|
|
695
728
|
|
|
696
729
|
// The tag lines, with descriptions punctuated as sentences.
|
|
697
730
|
const tags = formatTags(
|
|
@@ -712,17 +745,18 @@ export function formatJSDocPunctuation(sourceCode, comment) {
|
|
|
712
745
|
/**
|
|
713
746
|
* Format JSDoc prose and tags to the configured line width.
|
|
714
747
|
*
|
|
715
|
-
* @param {
|
|
716
|
-
* The
|
|
717
|
-
* @param {object}
|
|
718
|
-
* The
|
|
748
|
+
* @param {string} commentText
|
|
749
|
+
* The JSDoc comment text.
|
|
750
|
+
* @param {object} formattingOptions
|
|
751
|
+
* The indentation (`indent`) and newline style (`newline`) of the source
|
|
752
|
+
* around the comment.
|
|
719
753
|
*
|
|
720
754
|
* @returns {string}
|
|
721
755
|
* The formatted comment text.
|
|
722
756
|
*/
|
|
723
|
-
export function formatJSDocWrapping(
|
|
757
|
+
export function formatJSDocWrapping(commentText, formattingOptions) {
|
|
724
758
|
// The comment's parsed content and layout details.
|
|
725
|
-
const formattingContext = getJSDocFormattingContext(
|
|
759
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
726
760
|
// The prose, rewrapped to the comment's available width.
|
|
727
761
|
const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
|
|
728
762
|
|
|
@@ -741,18 +775,3 @@ export function formatJSDocWrapping(sourceCode, comment) {
|
|
|
741
775
|
|
|
742
776
|
return renderJSDocComment(formattingContext, outputLines);
|
|
743
777
|
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Return whether a JSDoc comment contains a target tag.
|
|
747
|
-
*
|
|
748
|
-
* @param {object} sourceCode
|
|
749
|
-
* The Oxlint source code object.
|
|
750
|
-
* @param {object} comment
|
|
751
|
-
* The comment token.
|
|
752
|
-
*
|
|
753
|
-
* @returns {boolean}
|
|
754
|
-
* Whether a Phase 1 tag is present.
|
|
755
|
-
*/
|
|
756
|
-
export function hasTargetJSDocTag(sourceCode, comment) {
|
|
757
|
-
return getJSDocContent(getCommentText(sourceCode, comment)).some(isTargetTag);
|
|
758
|
-
}
|
package/comments/utils/source.js
CHANGED
|
@@ -315,7 +315,6 @@ export function hasImmediateLineComment(sourceCode, node) {
|
|
|
315
315
|
|
|
316
316
|
// Checks the comments immediately around the node.
|
|
317
317
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
318
|
-
|
|
319
318
|
// Confirms there is no blank line before the node.
|
|
320
319
|
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
321
320
|
|
|
@@ -349,7 +348,6 @@ export function hasImmediateBlockComment(sourceCode, node) {
|
|
|
349
348
|
|
|
350
349
|
// Checks the comments immediately around the node.
|
|
351
350
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
352
|
-
|
|
353
351
|
// Confirms there is no blank line before the node.
|
|
354
352
|
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
355
353
|
|
package/comments/utils/wrap.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getDisplayWidth } from "./source.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Wrap words to a maximum line width.
|
|
3
5
|
*
|
|
@@ -23,6 +25,7 @@ export function wrapWords(text, width) {
|
|
|
23
25
|
for (let index = 0; index < word.length; index += width) {
|
|
24
26
|
lines.push(word.slice(index, index + width));
|
|
25
27
|
}
|
|
28
|
+
|
|
26
29
|
continue;
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -32,6 +35,7 @@ export function wrapWords(text, width) {
|
|
|
32
35
|
currentLine += ` ${word}`;
|
|
33
36
|
} else {
|
|
34
37
|
lines.push(currentLine);
|
|
38
|
+
|
|
35
39
|
currentLine = word;
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -43,6 +47,123 @@ export function wrapWords(text, width) {
|
|
|
43
47
|
return lines;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Refill comment lines when the next line starts early enough to fit.
|
|
52
|
+
*
|
|
53
|
+
* @param {object[]} lines
|
|
54
|
+
* The comment lines, each with a display prefix and undecorated text.
|
|
55
|
+
* @param {number} maximumLineLength
|
|
56
|
+
* The maximum display width for a complete comment line.
|
|
57
|
+
*
|
|
58
|
+
* @returns {object[]}
|
|
59
|
+
* The refilled comment lines.
|
|
60
|
+
*/
|
|
61
|
+
export function refillCommentLines(lines, maximumLineLength) {
|
|
62
|
+
// The lines copied so refilling does not change the caller's values.
|
|
63
|
+
const result = lines.map(({ prefix, text }) => ({ prefix, text: text.trimEnd() }));
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < result.length - 1; index += 1) {
|
|
66
|
+
// The current line being considered for refilling.
|
|
67
|
+
const currentLine = result[index];
|
|
68
|
+
|
|
69
|
+
while (index < result.length - 1) {
|
|
70
|
+
// The following line being considered for refilling.
|
|
71
|
+
const nextLine = result[index + 1];
|
|
72
|
+
|
|
73
|
+
if (!canRefillLine(currentLine.text) || !canProvideWords(nextLine.text)) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The words still waiting on the following line.
|
|
78
|
+
const nextWords = nextLine.text.split(/\s+/).filter(Boolean);
|
|
79
|
+
|
|
80
|
+
// Whether the following line was removed after giving up all its
|
|
81
|
+
// words.
|
|
82
|
+
let removedNextLine = false;
|
|
83
|
+
|
|
84
|
+
while (nextWords.length > 0) {
|
|
85
|
+
// The first word that could move onto the current line.
|
|
86
|
+
const nextWord = nextWords[0];
|
|
87
|
+
// The candidate line after moving the next word.
|
|
88
|
+
const candidate = `${currentLine.prefix}${currentLine.text} ${nextWord}`;
|
|
89
|
+
|
|
90
|
+
if (getDisplayWidth(candidate) > maximumLineLength) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
currentLine.text = `${currentLine.text} ${nextWord}`;
|
|
95
|
+
|
|
96
|
+
nextWords.shift();
|
|
97
|
+
|
|
98
|
+
nextLine.text = nextWords.join(" ");
|
|
99
|
+
|
|
100
|
+
if (nextLine.text === "") {
|
|
101
|
+
result.splice(index + 1, 1);
|
|
102
|
+
|
|
103
|
+
removedNextLine = true;
|
|
104
|
+
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!canRefillLine(currentLine.text)) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!removedNextLine) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Return whether a line may receive words from the next line.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} text
|
|
126
|
+
* The undecorated comment text.
|
|
127
|
+
*
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
* Whether the line can be refilled.
|
|
130
|
+
*/
|
|
131
|
+
function canRefillLine(text) {
|
|
132
|
+
// The line's text without its surrounding whitespace.
|
|
133
|
+
const trimmedText = text.trim();
|
|
134
|
+
|
|
135
|
+
return trimmedText !== "" && !/[.!?]$/.test(trimmedText) && !isListItem(trimmedText);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Return whether a line may provide words to the line above it.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} text
|
|
142
|
+
* The undecorated comment text.
|
|
143
|
+
*
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
* Whether the line can provide its first word.
|
|
146
|
+
*/
|
|
147
|
+
function canProvideWords(text) {
|
|
148
|
+
// The line's text without its surrounding whitespace.
|
|
149
|
+
const trimmedText = text.trim();
|
|
150
|
+
|
|
151
|
+
return trimmedText !== "" && !isListItem(trimmedText);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Return whether text starts with a list-item marker.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} text
|
|
158
|
+
* The undecorated comment text.
|
|
159
|
+
*
|
|
160
|
+
* @returns {boolean}
|
|
161
|
+
* Whether the text is a list item.
|
|
162
|
+
*/
|
|
163
|
+
function isListItem(text) {
|
|
164
|
+
return /^(?:[-*]\s+|\d+\.\s+)/.test(text.trim());
|
|
165
|
+
}
|
|
166
|
+
|
|
46
167
|
/**
|
|
47
168
|
* Capitalise a sentence and ensure it has terminal punctuation.
|
|
48
169
|
*
|
|
@@ -83,8 +204,8 @@ export function capitaliseSentence(text) {
|
|
|
83
204
|
// The leading word, starting from the first letter character.
|
|
84
205
|
const leadingWord = trimmedText.slice(firstLetter).match(/^\p{L}[\p{L}\p{N}]*/u)?.[0] ?? "";
|
|
85
206
|
|
|
86
|
-
// A camelCase word (lowercase start, later uppercase) is a code
|
|
87
|
-
//
|
|
207
|
+
// A camelCase word (lowercase start, later uppercase) is a code identifier
|
|
208
|
+
// and must keep its own casing rather than sentence casing.
|
|
88
209
|
if (/^\p{Ll}[\p{Ll}\p{N}]*\p{Lu}/u.test(leadingWord)) {
|
|
89
210
|
return text;
|
|
90
211
|
}
|
package/comments.json
CHANGED
|
@@ -6,15 +6,10 @@
|
|
|
6
6
|
}
|
|
7
7
|
],
|
|
8
8
|
"rules": {
|
|
9
|
-
"comments/block-comments": "error",
|
|
10
9
|
"comments/class-documentation": "error",
|
|
11
10
|
"comments/configured-api-calls": "error",
|
|
11
|
+
"comments/formatting": "error",
|
|
12
12
|
"comments/function-documentation": "error",
|
|
13
|
-
"comments/jsdoc-tag-formatting": "error",
|
|
14
|
-
"comments/line-comments": "error",
|
|
15
|
-
"comments/max-line-length": "error",
|
|
16
|
-
"comments/placement": "error",
|
|
17
|
-
"comments/sentence-punctuation": "error",
|
|
18
13
|
"comments/variable-declarations": "error",
|
|
19
14
|
"comments/vue-component-documentation": "error",
|
|
20
15
|
"comments/vue-emit-documentation": "error",
|