@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.
@@ -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
- // Program need a comment, so a nested variable inside
92
- // a function is left alone.
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
- // can be tested without the tag being available from Oxlint's extracted AST.
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
- // script blocks may not have been visited yet or ever in this run.
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, "options");
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 {Set<string>}
193
- * The documented parameter names.
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
- names.add(match[1]);
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(context.sourceCode, comment);
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) {
@@ -1,5 +1,11 @@
1
- import { getCommentText, getDisplayWidth, getLineIndent, getNewline } from "./source.js";
2
- import { addTerminalPunctuation, capitaliseSentence, formatSentence, wrapWords } from "./wrap.js";
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 without changing its existing line wrapping.
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 formatted lines, built up in place.
218
- const result = lines.map((line) => line.trim());
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 {object} sourceCode
516
- * The Oxlint source code object.
517
- * @param {object} comment
518
- * The JSDoc comment token.
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(sourceCode, comment) {
524
- // The raw comment source text.
525
- const commentText = getCommentText(sourceCode, comment);
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 {object} sourceCode
617
- * The Oxlint source code object.
618
- * @param {object} comment
619
- * The JSDoc comment token.
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(sourceCode, comment) {
642
+ export function formatJSDocBlockStructure(commentText, formattingOptions) {
625
643
  // The comment's parsed content and layout details.
626
- const formattingContext = getJSDocFormattingContext(sourceCode, comment);
627
- // The prose lines, rewrapped without changing existing line breaks.
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 {object} sourceCode
650
- * The Oxlint source code object.
651
- * @param {object} comment
652
- * The JSDoc comment token.
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(sourceCode, comment) {
689
+ export function formatJSDocTagFormatting(commentText, formattingOptions) {
658
690
  // The comment's parsed content and layout details.
659
- const formattingContext = getJSDocFormattingContext(sourceCode, comment);
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 {object} sourceCode
683
- * The Oxlint source code object.
684
- * @param {object} comment
685
- * The JSDoc comment token.
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(sourceCode, comment) {
723
+ export function formatJSDocPunctuation(commentText, formattingOptions) {
691
724
  // The comment's parsed content and layout details.
692
- const formattingContext = getJSDocFormattingContext(sourceCode, comment);
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 {object} sourceCode
716
- * The Oxlint source code object.
717
- * @param {object} comment
718
- * The JSDoc comment token.
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(sourceCode, comment) {
757
+ export function formatJSDocWrapping(commentText, formattingOptions) {
724
758
  // The comment's parsed content and layout details.
725
- const formattingContext = getJSDocFormattingContext(sourceCode, comment);
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
- }
@@ -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
 
@@ -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
- // identifier and must keep its own casing rather than sentence casing.
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewishowles/lint-config",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Shared oxlint configuration for Lewis Howles projects",
5
5
  "keywords": [
6
6
  "config",