@lewishowles/lint-config 0.4.0 → 0.6.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 +23 -0
- package/README.md +38 -6
- package/base.json +23 -4
- package/comments/plugin.js +2 -12
- package/comments/rules/class-documentation.js +8 -5
- package/comments/rules/configured-api-calls.js +3 -3
- package/comments/rules/formatting.js +868 -0
- package/comments/rules/variable-declarations.js +41 -4
- package/comments/rules/vue-component-documentation.js +10 -8
- package/comments/rules/vue-emit-documentation.js +2 -1
- package/comments/utils/documentation.js +0 -1
- package/comments/utils/jsdoc.js +84 -63
- package/comments/utils/source.js +15 -2
- package/comments/utils/wrap.js +123 -2
- package/comments.json +10 -7
- package/package.json +3 -2
- package/comments/rules/block-comments.js +0 -70
- package/comments/rules/jsdoc-tag-formatting.js +0 -70
- package/comments/rules/line-comments.js +0 -86
- package/comments/rules/max-line-length.js +0 -159
- package/comments/rules/placement.js +0 -290
- package/comments/rules/sentence-punctuation.js +0 -275
|
@@ -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.
|
|
@@ -33,6 +33,18 @@ export default {
|
|
|
33
33
|
meta: {
|
|
34
34
|
docs: { description: "Require comments before variable declarations." },
|
|
35
35
|
type: "suggestion",
|
|
36
|
+
schema: [
|
|
37
|
+
{
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
rootOnly: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
defaultOptions: [{ rootOnly: false }],
|
|
36
48
|
},
|
|
37
49
|
/**
|
|
38
50
|
* Create the rule's node visitors.
|
|
@@ -56,7 +68,8 @@ export default {
|
|
|
56
68
|
return;
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
// A lone const class expression is documented by
|
|
71
|
+
// A lone const class expression is documented by
|
|
72
|
+
// class-documentation.
|
|
60
73
|
if (
|
|
61
74
|
node.kind === "const" &&
|
|
62
75
|
node.declarations.length === 1 &&
|
|
@@ -66,10 +79,34 @@ export default {
|
|
|
66
79
|
return;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
//
|
|
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
|
+
|
|
92
|
+
// The rule's resolved options for the file currently being
|
|
93
|
+
// visited.
|
|
94
|
+
const options = context.options?.[0];
|
|
95
|
+
// Resolves any export wrapper before checking for
|
|
96
|
+
// documentation.
|
|
70
97
|
const documentationNode = getDocumentationNode(node);
|
|
71
98
|
|
|
72
|
-
|
|
99
|
+
// With rootOnly, only declarations directly under the Program
|
|
100
|
+
// need a comment, so a nested variable inside a function is
|
|
101
|
+
// left alone.
|
|
102
|
+
const shouldCheckDocumentation =
|
|
103
|
+
!options?.rootOnly || documentationNode.parent?.type === "Program";
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
!isFunctionValuedConst &&
|
|
107
|
+
shouldCheckDocumentation &&
|
|
108
|
+
!hasImmediateLineComment(context.sourceCode, documentationNode)
|
|
109
|
+
) {
|
|
73
110
|
context.report({
|
|
74
111
|
message: "Variable declarations require an immediately preceding line comment.",
|
|
75
112
|
node: documentationNode,
|
|
@@ -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(
|
|
@@ -119,7 +119,8 @@ function hasComponentDocumentation(context, scriptBlock) {
|
|
|
119
119
|
return false;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
// A blank line would separate the component documentation from its entry
|
|
122
|
+
// A blank line would separate the component documentation from its entry
|
|
123
|
+
// point.
|
|
123
124
|
const commentGap = context.sourceCode.text.slice(0, comment.range[0]);
|
|
124
125
|
|
|
125
126
|
return immediateCommentGapPattern.test(commentGap);
|
|
@@ -142,7 +143,8 @@ export default {
|
|
|
142
143
|
createOnce(context) {
|
|
143
144
|
return {
|
|
144
145
|
/**
|
|
145
|
-
* Check the current component's script setup block for
|
|
146
|
+
* Check the current component's script setup block for
|
|
147
|
+
* documentation.
|
|
146
148
|
*
|
|
147
149
|
* @param {object} node
|
|
148
150
|
* The entry point parsed from the current script block.
|
|
@@ -150,8 +152,8 @@ export default {
|
|
|
150
152
|
Program(node) {
|
|
151
153
|
// createOnce builds this visitor once for the whole run, and a
|
|
152
154
|
// single file's script blocks are not necessarily visited
|
|
153
|
-
// consecutively, so the matching block is looked up fresh on
|
|
154
|
-
// call rather than tracked with shared state.
|
|
155
|
+
// consecutively, so the matching block is looked up fresh on
|
|
156
|
+
// each call rather than tracked with shared state.
|
|
155
157
|
const scriptBlock = findScriptBlock(context);
|
|
156
158
|
|
|
157
159
|
if (hasComponentDocumentation(context, scriptBlock)) {
|
|
@@ -111,7 +111,8 @@ export default {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
// Array and type-only forms have no runtime properties to
|
|
114
|
+
// Array and type-only forms have no runtime properties to
|
|
115
|
+
// document.
|
|
115
116
|
const emitsObject = getObjectArgument(node, 0);
|
|
116
117
|
|
|
117
118
|
if (emitsObject) {
|
|
@@ -293,7 +293,6 @@ function hasValueReturn(node) {
|
|
|
293
293
|
export function reportFunctionDocumentation(context, node, functionNode, options = {}) {
|
|
294
294
|
// Defaults to "Functions" when the caller names no declaration kind.
|
|
295
295
|
const subject = options.subject ?? "Functions";
|
|
296
|
-
|
|
297
296
|
// Finds the JSDoc block documenting this function, when present.
|
|
298
297
|
const comment = getDocumentationComment(context.sourceCode, node);
|
|
299
298
|
|
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,21 +210,27 @@ 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 =
|
|
219
|
-
|
|
220
|
-
|
|
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);
|
|
231
|
+
|
|
232
|
+
// The index of the current paragraph's first line, or null between
|
|
233
|
+
// paragraphs.
|
|
221
234
|
let paragraphStart = null;
|
|
222
235
|
|
|
223
236
|
for (let index = 0; index < result.length; index += 1) {
|
|
@@ -281,12 +294,14 @@ function formatProse(lines, width, addPunctuation) {
|
|
|
281
294
|
}
|
|
282
295
|
|
|
283
296
|
result.push(...wrapWords(text, width));
|
|
297
|
+
|
|
284
298
|
paragraph = [];
|
|
285
299
|
}
|
|
286
300
|
|
|
287
301
|
for (const line of lines) {
|
|
288
302
|
if (line.trim() === "") {
|
|
289
303
|
flushParagraph();
|
|
304
|
+
|
|
290
305
|
if (result.at(-1) !== "") {
|
|
291
306
|
result.push("");
|
|
292
307
|
}
|
|
@@ -336,7 +351,8 @@ function formatTags(tagLines, width, addPunctuation, normaliseTags) {
|
|
|
336
351
|
// The formatted lines, built up in place.
|
|
337
352
|
const result = [];
|
|
338
353
|
|
|
339
|
-
// The tag type of the previously written entry, used to detect group
|
|
354
|
+
// The tag type of the previously written entry, used to detect group
|
|
355
|
+
// changes.
|
|
340
356
|
let lastType = null;
|
|
341
357
|
|
|
342
358
|
// The entries regrouped into the required tag order.
|
|
@@ -406,15 +422,18 @@ function formatMixedTags(lines, width, addPunctuation) {
|
|
|
406
422
|
type: match[1],
|
|
407
423
|
};
|
|
408
424
|
preserveSection = false;
|
|
425
|
+
|
|
409
426
|
result.push(formatTagHeader(currentEntry));
|
|
410
427
|
} else if (tagName !== null) {
|
|
411
428
|
currentEntry = null;
|
|
412
429
|
preserveSection = isPreservedSectionTag(line);
|
|
430
|
+
|
|
413
431
|
result.push(line.trim());
|
|
414
432
|
} else if (preserveSection) {
|
|
415
433
|
result.push(line);
|
|
416
434
|
} else if (line.trim() === "") {
|
|
417
435
|
currentEntry = null;
|
|
436
|
+
|
|
418
437
|
if (result.at(-1) !== "") {
|
|
419
438
|
result.push("");
|
|
420
439
|
}
|
|
@@ -475,6 +494,7 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
475
494
|
}
|
|
476
495
|
|
|
477
496
|
result.push(...wrapWords(text, width).map((line) => ` ${line}`));
|
|
497
|
+
|
|
478
498
|
description = [];
|
|
479
499
|
}
|
|
480
500
|
|
|
@@ -485,11 +505,13 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
485
505
|
if (tagName !== null) {
|
|
486
506
|
flushDescription();
|
|
487
507
|
result.push(line.trim());
|
|
508
|
+
|
|
488
509
|
preserveSection = isPreservedSectionTag(line);
|
|
489
510
|
} else if (preserveSection) {
|
|
490
511
|
result.push(line);
|
|
491
512
|
} else if (line.trim() === "") {
|
|
492
513
|
flushDescription();
|
|
514
|
+
|
|
493
515
|
if (result.at(-1) !== "") {
|
|
494
516
|
result.push("");
|
|
495
517
|
}
|
|
@@ -510,23 +532,20 @@ function formatTagDescriptions(lines, width, addPunctuation) {
|
|
|
510
532
|
/**
|
|
511
533
|
* Return the details needed to format a JSDoc block comment.
|
|
512
534
|
*
|
|
513
|
-
* @param {
|
|
514
|
-
* The
|
|
515
|
-
* @param {object}
|
|
516
|
-
* 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.
|
|
517
540
|
*
|
|
518
541
|
* @returns {object}
|
|
519
542
|
* The JSDoc content and layout details.
|
|
520
543
|
*/
|
|
521
|
-
function getJSDocFormattingContext(
|
|
522
|
-
// The
|
|
523
|
-
const
|
|
524
|
-
// The indentation the comment's lines are aligned to.
|
|
525
|
-
const indent = getLineIndent(sourceCode, comment.range[0]) ?? "";
|
|
526
|
-
// The newline style used by the surrounding source.
|
|
527
|
-
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;
|
|
528
547
|
// The available content width, allowing for the indent and " * " prefix.
|
|
529
|
-
const width = Math.max(1, 80 - indent
|
|
548
|
+
const width = Math.max(1, 80 - getDisplayWidth(indent) - 3);
|
|
530
549
|
// The undecorated comment content lines.
|
|
531
550
|
const content = getJSDocContent(commentText);
|
|
532
551
|
// The content split into its prose and tag sections.
|
|
@@ -611,19 +630,20 @@ function renderJSDocComment(formattingContext, outputLines) {
|
|
|
611
630
|
/**
|
|
612
631
|
* Format JSDoc block structure and delimiters.
|
|
613
632
|
*
|
|
614
|
-
* @param {
|
|
615
|
-
* The
|
|
616
|
-
* @param {object}
|
|
617
|
-
* 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.
|
|
618
638
|
*
|
|
619
639
|
* @returns {string}
|
|
620
640
|
* The formatted comment text.
|
|
621
641
|
*/
|
|
622
|
-
export function formatJSDocBlockStructure(
|
|
642
|
+
export function formatJSDocBlockStructure(commentText, formattingOptions) {
|
|
623
643
|
// The comment's parsed content and layout details.
|
|
624
|
-
const formattingContext = getJSDocFormattingContext(
|
|
625
|
-
// The prose lines,
|
|
626
|
-
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);
|
|
627
647
|
|
|
628
648
|
// The tag lines, without spacing or grouping normalisation.
|
|
629
649
|
const tags = formatTags(
|
|
@@ -641,20 +661,34 @@ export function formatJSDocBlockStructure(sourceCode, comment) {
|
|
|
641
661
|
return renderJSDocComment(formattingContext, outputLines);
|
|
642
662
|
}
|
|
643
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
|
+
|
|
644
677
|
/**
|
|
645
678
|
* Format JSDoc tag spacing, order, and grouping.
|
|
646
679
|
*
|
|
647
|
-
* @param {
|
|
648
|
-
* The
|
|
649
|
-
* @param {object}
|
|
650
|
-
* 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.
|
|
651
685
|
*
|
|
652
686
|
* @returns {string}
|
|
653
687
|
* The formatted comment text.
|
|
654
688
|
*/
|
|
655
|
-
export function formatJSDocTagFormatting(
|
|
689
|
+
export function formatJSDocTagFormatting(commentText, formattingOptions) {
|
|
656
690
|
// The comment's parsed content and layout details.
|
|
657
|
-
const formattingContext = getJSDocFormattingContext(
|
|
691
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
658
692
|
// The prose, rewrapped to the comment's available width.
|
|
659
693
|
const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
|
|
660
694
|
|
|
@@ -677,19 +711,20 @@ export function formatJSDocTagFormatting(sourceCode, comment) {
|
|
|
677
711
|
/**
|
|
678
712
|
* Format JSDoc sentence capitalisation and punctuation.
|
|
679
713
|
*
|
|
680
|
-
* @param {
|
|
681
|
-
* The
|
|
682
|
-
* @param {object}
|
|
683
|
-
* 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.
|
|
684
719
|
*
|
|
685
720
|
* @returns {string}
|
|
686
721
|
* The formatted comment text.
|
|
687
722
|
*/
|
|
688
|
-
export function formatJSDocPunctuation(
|
|
723
|
+
export function formatJSDocPunctuation(commentText, formattingOptions) {
|
|
689
724
|
// The comment's parsed content and layout details.
|
|
690
|
-
const formattingContext = getJSDocFormattingContext(
|
|
725
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
691
726
|
// The prose, capitalised and punctuated as sentences.
|
|
692
|
-
const prose = formatUnwrappedProse(formattingContext.proseLines, true);
|
|
727
|
+
const prose = formatUnwrappedProse(formattingContext.proseLines, true, formattingContext.indent);
|
|
693
728
|
|
|
694
729
|
// The tag lines, with descriptions punctuated as sentences.
|
|
695
730
|
const tags = formatTags(
|
|
@@ -710,17 +745,18 @@ export function formatJSDocPunctuation(sourceCode, comment) {
|
|
|
710
745
|
/**
|
|
711
746
|
* Format JSDoc prose and tags to the configured line width.
|
|
712
747
|
*
|
|
713
|
-
* @param {
|
|
714
|
-
* The
|
|
715
|
-
* @param {object}
|
|
716
|
-
* 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.
|
|
717
753
|
*
|
|
718
754
|
* @returns {string}
|
|
719
755
|
* The formatted comment text.
|
|
720
756
|
*/
|
|
721
|
-
export function formatJSDocWrapping(
|
|
757
|
+
export function formatJSDocWrapping(commentText, formattingOptions) {
|
|
722
758
|
// The comment's parsed content and layout details.
|
|
723
|
-
const formattingContext = getJSDocFormattingContext(
|
|
759
|
+
const formattingContext = getJSDocFormattingContext(commentText, formattingOptions);
|
|
724
760
|
// The prose, rewrapped to the comment's available width.
|
|
725
761
|
const prose = formatProse(formattingContext.proseLines, formattingContext.width, false);
|
|
726
762
|
|
|
@@ -739,18 +775,3 @@ export function formatJSDocWrapping(sourceCode, comment) {
|
|
|
739
775
|
|
|
740
776
|
return renderJSDocComment(formattingContext, outputLines);
|
|
741
777
|
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Return whether a JSDoc comment contains a target tag.
|
|
745
|
-
*
|
|
746
|
-
* @param {object} sourceCode
|
|
747
|
-
* The Oxlint source code object.
|
|
748
|
-
* @param {object} comment
|
|
749
|
-
* The comment token.
|
|
750
|
-
*
|
|
751
|
-
* @returns {boolean}
|
|
752
|
-
* Whether a Phase 1 tag is present.
|
|
753
|
-
*/
|
|
754
|
-
export function hasTargetJSDocTag(sourceCode, comment) {
|
|
755
|
-
return getJSDocContent(getCommentText(sourceCode, comment)).some(isTargetTag);
|
|
756
|
-
}
|
package/comments/utils/source.js
CHANGED
|
@@ -156,6 +156,21 @@ export function getLineIndent(sourceCode, offset) {
|
|
|
156
156
|
return /^\s*$/.test(prefix) ? prefix : null;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Return the number of columns a source string occupies on screen, so comment
|
|
161
|
+
* lines are measured and wrapped against the 80-column limit under tab
|
|
162
|
+
* indentation. A tab counts as four columns, the width this package assumes.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} sourceText
|
|
165
|
+
* The source string to measure, such as a line or its indentation.
|
|
166
|
+
*
|
|
167
|
+
* @returns {number}
|
|
168
|
+
* The source string's width in display columns.
|
|
169
|
+
*/
|
|
170
|
+
export function getDisplayWidth(sourceText) {
|
|
171
|
+
return sourceText.replaceAll("\t", " ").length;
|
|
172
|
+
}
|
|
173
|
+
|
|
159
174
|
/**
|
|
160
175
|
* Return the source items immediately around a comment.
|
|
161
176
|
*
|
|
@@ -300,7 +315,6 @@ export function hasImmediateLineComment(sourceCode, node) {
|
|
|
300
315
|
|
|
301
316
|
// Checks the comments immediately around the node.
|
|
302
317
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
303
|
-
|
|
304
318
|
// Confirms there is no blank line before the node.
|
|
305
319
|
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
306
320
|
|
|
@@ -334,7 +348,6 @@ export function hasImmediateBlockComment(sourceCode, node) {
|
|
|
334
348
|
|
|
335
349
|
// Checks the comments immediately around the node.
|
|
336
350
|
const { next, previous } = getCommentNeighbours(sourceCode, comment);
|
|
337
|
-
|
|
338
351
|
// Confirms there is no blank line before the node.
|
|
339
352
|
const gap = sourceCode.text.slice(comment.range[1], node.range[0]);
|
|
340
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,18 +6,21 @@
|
|
|
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",
|
|
21
16
|
"comments/vue-prop-documentation": "error"
|
|
22
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"overrides": [
|
|
19
|
+
{
|
|
20
|
+
"files": ["**/*.test.*", "**/*.spec.*", "**/test/**"],
|
|
21
|
+
"rules": {
|
|
22
|
+
"comments/variable-declarations": ["error", { "rootOnly": true }]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
23
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lewishowles/lint-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Shared oxlint configuration for Lewis Howles projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"config",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"lint": "vp check",
|
|
41
41
|
"lint:fix": "vp check --fix",
|
|
42
42
|
"prepare": "vp config --no-agent",
|
|
43
|
-
"publint": "publint"
|
|
43
|
+
"publint": "publint",
|
|
44
|
+
"test:unit": "node --test test/comments/*.test.js"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"publint": "^0.3.22",
|