@d3lm/lint-preset 1.5.4 → 1.5.6
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/README.md +11 -24
- package/dist/bin/comments.js +57 -14
- package/dist/bin/comments.js.map +1 -1
- package/dist/comments.cjs +55 -13
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.d.cts +12 -7
- package/dist/comments.d.ts +12 -7
- package/dist/comments.js +55 -13
- package/dist/comments.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -260,7 +260,7 @@ Verdicts are layout issues that can be measured without reading the prose. They
|
|
|
260
260
|
|
|
261
261
|
Hints come from heuristics and can be false positives, because only the meaning of the prose shows whether the rule applies. A hint only sends a comment to the agent, which decides whether to change it. `check` only prints comments with verdicts, so a passing check stays quiet apart from its summary, even when hints remain after review. Use `list --suspicious` to inspect hints, which are marked with `(hint)` and never fail a check:
|
|
262
262
|
|
|
263
|
-
- `orphan` means a line ends with the first word of a sentence, or the first of two lines ends with the start of a sentence that fits on the second without making it longer than the first
|
|
263
|
+
- `orphan` means a line ends with the first word of a sentence, a line ends with the first two words of a sentence that the suggested rewrap moves to the next line, or the first of two lines ends with the start of a sentence that fits on the second without making it longer than the first
|
|
264
264
|
- `split-parenthetical` means a line ends inside a parenthetical that the suggested rewrap keeps on one line within `maxWidth`
|
|
265
265
|
- `em-dash` means the comment uses `—`
|
|
266
266
|
- `dash` means the comment uses `–` or `--` between words
|
|
@@ -273,31 +273,23 @@ A paragraph with an `orphan` hint is not reported as `unbalanced` when that word
|
|
|
273
273
|
|
|
274
274
|
In the same way, a paragraph that splits a parenthetical is not reported as `unbalanced` when the split is all that sets it apart from the suggested rewrap, because the split can read as well and keep the line closer to `width`. This holds when the lines before the split match the rewrap and the lines after it match the rewrap of the text after the split. Otherwise the paragraph is still `unbalanced`.
|
|
275
275
|
|
|
276
|
+
With a `tolerance`, a paragraph has no `underfill`, `widow`, or `unbalanced` verdict when it already has the layout that the tool produces for a wider `width`, up to `width` plus `tolerance`. Its suggestion then keeps the lines as they are. With `width: 100` and `tolerance: 5`, a paragraph that is laid out the way the tool lays it out for a width of 105 passes. A paragraph that only has hints keeps them, and a line past `maxWidth` is still an `overflow`.
|
|
277
|
+
|
|
276
278
|
Hints are only checked in prose, never in code spans, code blocks, `@example`, or URLs. Rules that can't be measured, like writing complete sentences, are part of the agent's prompt (see `DEFAULT_RULES` in `src/comments/agent.ts`). The prompt treats the style rules as preferences, so the agent keeps punctuation that carries meaning or reads clearly, and never changes code, code spans, literals, patterns, or quotations.
|
|
277
279
|
|
|
278
280
|
### Text Kept as Written
|
|
279
281
|
|
|
280
|
-
The tool rewraps prose paragraphs, list items, and JSDoc tag descriptions.
|
|
282
|
+
The tool rewraps prose paragraphs, list items, and JSDoc tag descriptions. Everything else is kept exactly as written and has no suggestion, so the agent or you have to fix it by hand:
|
|
281
283
|
|
|
282
|
-
- Code gets no checks besides `banner`. This covers
|
|
283
|
-
-
|
|
284
|
-
-
|
|
285
|
-
- Text laid out by hand gets hints and `overflow`. This covers a Markdown hard line break, meaning a line that ends with two or more spaces or with a backslash and is followed by another line of the same paragraph. The whole paragraph is kept byte for byte, trailing spaces included. Like in Markdown, a backslash that another backslash escapes, as in `\\`, and trailing spaces on the last line of a paragraph don't break a line, so they're rewrapped like any other prose. It also covers text that may be laid out by hand: a Markdown heading, link definition, HTML tag, or `>` quote at the start of a line (prose can start a line like `<T> is the type` or `# of retries` too), a line with two or more `|`, arrows like `-->`, `<--`, or `→`, rules like `---`, `+--`, or `====`, and two or more lines that each start with a term and a dash or colon, like `width - the maximum width`. Single-line block comments and block comments without a `*` on each line are checked the same way. So is prose that the tool can't rewrap without changing how it reads or adding a line that is too long. That is a paragraph whose rewrap would itself read as code or layout, like one that would end with `;` on the same line as a `>`. It is also a paragraph with a code span or `{...}` group that continues on the next line and doesn't fit on one line, which is what a stray backtick or brace looks like too. JSDoc tags whose content isn't a description, like `@typedef`, are never rewrapped but still have to fit the width.
|
|
284
|
+
- Code gets no checks besides `banner`. This covers code blocks, `@example`, `@default`, and `@defaultValue`, tool directives like `eslint-disable-next-line` or `@ts-expect-error`, legal comments (`/*!`, `@license`, `@preserve`, and copyright notices), and paragraphs that parse as JavaScript.
|
|
285
|
+
- Layout syntax gets hints but no verdicts, in the whole paragraph around it. This covers Markdown tables (a line that starts with `|`, or a delimiter row like `--- | ---`), box-drawing characters (U+2500 to U+257F), a line that starts with four or more of the same character among `-=*#_~+/\` or ends with four or more among `-=*#_~` (except Markdown headings and column underlines like `---- ----`), and aligned columns (a gap of two or more spaces after which text continues at the same column on at least two lines).
|
|
286
|
+
- Text that only looks like code or hand layout keeps its line breaks but still gets `overflow`, since prose can look like that too. Lines that look like code, such as ones that end with `{` or `;` or start with `import`, get no hints. Hand layout, such as Markdown hard line breaks, headings, quotes, arrows, and `term - description` lists, still gets hints.
|
|
286
287
|
|
|
287
|
-
|
|
288
|
+
To keep a drawing that is wider than the width, put it in a code fence or add `comments-ignore` to the comment.
|
|
288
289
|
|
|
289
290
|
### Configuration
|
|
290
291
|
|
|
291
|
-
`width` is the preferred line length
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
/**
|
|
295
|
-
* Splits prose into words, keeping backtick code spans and `{...}` groups (JSDoc types, `{@link ...}`)
|
|
296
|
-
* intact so that they are never broken across lines.
|
|
297
|
-
*/
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
A comment that is one paragraph stays on one line up to `singleLineWidth` plus `tolerance`, since that always saves a line, and this allowance is capped by `maxWidth`. Code and documented layout regions retain their exemptions. An indivisible token in measured prose that exceeds `maxWidth` is reported as overflow; the wrapper preserves it, so resolving that case requires changing the maximum or the comment manually. `tolerance` lets prose lines go up to that many columns past `width` when that saves a line in a paragraph of three or more lines, and it never goes past `maxWidth`. It defaults to 0. When `maxWidth` is omitted, `tolerance` also sets a legacy maximum of `width + tolerance`. Direct `analyzeSource` callers can omit `maxWidth` to retain the legacy width/tolerance behavior.
|
|
292
|
+
`width` is the preferred line length, and `maxWidth` is the hard maximum, including indentation and the comment prefix. The CLI defaults `maxWidth` to 120, or to `width` if that is larger. The wrapper may go past `width`, up to `maxWidth`, to keep a parenthetical on one line. `tolerance` (default 0) lets lines in paragraphs of three or more lines go that many columns past `width` when that saves a line. `check` also accepts a paragraph that already has the layout for a wider `width`, up to `width` plus `tolerance`, while the tool itself still wraps to `width`. A comment that is one paragraph stays on one line up to `singleLineWidth` plus `tolerance`, capped by `maxWidth`. A word or code span longer than `maxWidth` is never split and is reported as `overflow`.
|
|
301
293
|
|
|
302
294
|
Like prettier, `comments` looks for a config file in the current directory and its parents, up to the repository root. The config can be a `comments.config.{ts,mts,js,mjs,cjs}`, `.commentsrc`, or `.commentsrc.json` file, or a `comments` key in `package.json`. All options are optional.
|
|
303
295
|
|
|
@@ -309,13 +301,10 @@ export default defineConfig({
|
|
|
309
301
|
// defaults to prettier's printWidth, or 80
|
|
310
302
|
width: 100,
|
|
311
303
|
maxWidth: 120,
|
|
312
|
-
// prose lines may go this many columns past `width` when that saves a line
|
|
313
304
|
tolerance: 0,
|
|
314
|
-
// a comment that is one paragraph stays on one line up to this length plus `tolerance`, and wraps to `width` beyond it
|
|
315
305
|
singleLineWidth: 120,
|
|
316
306
|
avoidSentenceOrphans: true,
|
|
317
|
-
//
|
|
318
|
-
// which only decides what the agent sees, since its rules apply to every comment it gets
|
|
307
|
+
// report the style hints, like `em-dash` and `semicolon`
|
|
319
308
|
style: true,
|
|
320
309
|
ignore: ['src/generated/**'],
|
|
321
310
|
agent: {
|
|
@@ -336,9 +325,7 @@ export default defineConfig({
|
|
|
336
325
|
|
|
337
326
|
Most options also have a command line flag that overrides the config file for a single run. See `comments --help` for the full list.
|
|
338
327
|
|
|
339
|
-
Comments are split across agents,
|
|
340
|
-
|
|
341
|
-
In a terminal, `comments agent` shows a live table of the agents while they run. Afterwards, it lists the comments that still have issues and lets you open each agent's reply. `--no-interactive` skips that list, so the command shows the agents while they run and exits when they're done. Without a terminal, for example in a hook or in CI, it prints plain progress lines and only prints the replies of agents that failed. `--verbose` prints every reply in both cases.
|
|
328
|
+
Comments are split across several agents, and all comments of a file go to the same agent. In a terminal, `comments agent` shows the agents while they run, then lists the comments that still have issues. `--no-interactive` skips that list, and `--verbose` prints every agent's reply.
|
|
342
329
|
|
|
343
330
|
`agent.rules` points to a Markdown file that replaces the built-in formatting rules. `agent.command` runs any other agent CLI, like `['my-agent', '--prompt', '{prompt}']`. Without a `{prompt}` placeholder, the prompt goes to stdin.
|
|
344
331
|
|
package/dist/bin/comments.js
CHANGED
|
@@ -25,7 +25,7 @@ When rules pull in different directions, the earlier one wins.
|
|
|
25
25
|
## Layout
|
|
26
26
|
|
|
27
27
|
- After you finish, the tool rewraps prose paragraphs, list items, and JSDoc tag descriptions. The width is a preferred target. A parenthetical may extend a line up to the configured maximum so it stays together, and lines may go up to the configured tolerance past the width when that saves a line. In longer paragraphs, balanced lines take priority over keeping every parenthetical on one line. The tool also avoids short last lines where possible. Leave these line breaks to the tool, except for the sentence-orphan case below.
|
|
28
|
-
- Don't end a line with the first word of a new sentence, clause, or parenthetical, like \`This\` after a period, \`the\` after a comma, or \`(the\`. Move it to the next line instead. A long word, like an identifier or a code span, may stay at the end of a line when moving it would leave that line far shorter than the lines around it. Then leave the line breaks as they are.
|
|
28
|
+
- Don't end a line with the first word of a new sentence, clause, or parenthetical, like \`This\` after a period, \`the\` after a comma, or \`(the\`. Move it to the next line instead. The same goes for the first two words of a sentence, like \`It allocates\` after a period, when the note points at them. A long word, like an identifier or a code span, may stay at the end of a line when moving it would leave that line far shorter than the lines around it. Then leave the line breaks as they are.
|
|
29
29
|
- In a paragraph of two lines, the first line ends with a sentence whenever the rest fits on the second line and is no longer than the first. Don't end it with the start of the next sentence, like \`A wrap\` in \`...lay out differently. A wrap\`. Move those words to the second line instead.
|
|
30
30
|
- A comment whose whole text is one paragraph stays on a single line when that line fits a separate, larger single-line width, even past the width. Only a longer comment wraps to the width.
|
|
31
31
|
- Separate paragraphs with an empty comment line.
|
|
@@ -583,6 +583,8 @@ function hasWidow(lines) {
|
|
|
583
583
|
}
|
|
584
584
|
var FULLNESS_WEIGHT = 0.07;
|
|
585
585
|
var PARENTHETICAL_SPLIT_WEIGHT = 2;
|
|
586
|
+
var SENTENCE_START_COST = 25;
|
|
587
|
+
var MAX_SENTENCE_START_WORDS = 2;
|
|
586
588
|
var MAX_STEP = 15;
|
|
587
589
|
var EXTRA_LINES = 2;
|
|
588
590
|
function fewestLines(starts, target) {
|
|
@@ -670,7 +672,7 @@ function balanceTokens(tokens, firstLimit, restLimit, count, options, preferredL
|
|
|
670
672
|
afterOrphan: tokens.map((_, index) => {
|
|
671
673
|
return options.avoidSentenceOrphans && index >= 2 && isOrphan(tokens[index - 2], tokens[index - 1]);
|
|
672
674
|
}),
|
|
673
|
-
splitCosts
|
|
675
|
+
splitCosts: withSentenceStartCosts(tokens, splitCosts, options)
|
|
674
676
|
};
|
|
675
677
|
const targets = [];
|
|
676
678
|
for (let target = restLimit; target >= Math.max(longest, Math.floor(restLimit / 2)); target -= 1) {
|
|
@@ -736,6 +738,22 @@ function splitCostsOf(tokens, limit) {
|
|
|
736
738
|
}
|
|
737
739
|
return splitCosts;
|
|
738
740
|
}
|
|
741
|
+
function withSentenceStartCosts(tokens, splitCosts, options) {
|
|
742
|
+
if (!options.avoidSentenceOrphans || options.ignoreSentenceStarts) {
|
|
743
|
+
return splitCosts;
|
|
744
|
+
}
|
|
745
|
+
const costs = new Map(splitCosts);
|
|
746
|
+
for (const [index, token] of tokens.entries()) {
|
|
747
|
+
if (!endsSentence(token)) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const last = Math.min(index + 1 + MAX_SENTENCE_START_WORDS, tokens.length - 1);
|
|
751
|
+
for (let end = index + 2; end <= last && !endsSentence(tokens[end - 1]); end += 1) {
|
|
752
|
+
costs.set(end, (costs.get(end) ?? 0) + SENTENCE_START_COST);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return costs;
|
|
756
|
+
}
|
|
739
757
|
function hasParentheticals(tokens, restLimit, options) {
|
|
740
758
|
if (options.parentheticalOverflow === void 0) {
|
|
741
759
|
return false;
|
|
@@ -1333,9 +1351,9 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1333
1351
|
parentheticalOverflow: options.maxWidth === void 0 ? void 0 : options.maxWidth - options.width,
|
|
1334
1352
|
keepFirstLine: paragraph.kind === "tag"
|
|
1335
1353
|
};
|
|
1336
|
-
const layOut = (from, settings) => {
|
|
1337
|
-
const even2 = layoutTokens(from, firstLimit, restLimit, settings);
|
|
1338
|
-
return { even: even2, wrapped: breakAfterSentence(even2, firstLimit, restLimit, settings) };
|
|
1354
|
+
const layOut = (from, settings, extra) => {
|
|
1355
|
+
const even2 = layoutTokens(from, firstLimit + extra, restLimit + extra, settings);
|
|
1356
|
+
return { even: even2, wrapped: breakAfterSentence(even2, firstLimit + extra, restLimit + extra, settings) };
|
|
1339
1357
|
};
|
|
1340
1358
|
const units = bindLineStarts(paragraph.kind === "tag" ? bindTagHead(tokens) : tokens, startsLine);
|
|
1341
1359
|
const hardLimit = restLimit + (options.maxWidth === void 0 ? options.tolerance : options.maxWidth - options.width);
|
|
@@ -1352,9 +1370,9 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1352
1370
|
return { ...first, text, verbatim: false, hardBreak: HARD_BREAK.test(text) };
|
|
1353
1371
|
});
|
|
1354
1372
|
};
|
|
1355
|
-
const settle = (settings) => {
|
|
1373
|
+
const settle = (settings, extra = 0) => {
|
|
1356
1374
|
let current = units;
|
|
1357
|
-
let { even: even2, wrapped: wrapped2 } = layOut(current, settings);
|
|
1375
|
+
let { even: even2, wrapped: wrapped2 } = layOut(current, settings, extra);
|
|
1358
1376
|
for (; ; ) {
|
|
1359
1377
|
const body = bodyOf(wrapped2);
|
|
1360
1378
|
const rewrap = structureOf(body);
|
|
@@ -1369,11 +1387,19 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1369
1387
|
return { tier: "manual", reason: `rewrap would read as ${reading}` };
|
|
1370
1388
|
}
|
|
1371
1389
|
current = current.toSpliced(start - 1, 2, pair);
|
|
1372
|
-
({ even: even2, wrapped: wrapped2 } = layOut(current, settings));
|
|
1390
|
+
({ even: even2, wrapped: wrapped2 } = layOut(current, settings, extra));
|
|
1373
1391
|
}
|
|
1374
1392
|
};
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1393
|
+
const layoutAt = (extra) => {
|
|
1394
|
+
const settings = {
|
|
1395
|
+
...layoutOptions,
|
|
1396
|
+
tolerance: layoutOptions.tolerance - extra,
|
|
1397
|
+
parentheticalOverflow: layoutOptions.parentheticalOverflow === void 0 ? void 0 : layoutOptions.parentheticalOverflow - extra
|
|
1398
|
+
};
|
|
1399
|
+
const settled = settle(settings, extra);
|
|
1400
|
+
return "wrapped" in settled ? settled : settle({ ...settings, keepSteps: true }, extra);
|
|
1401
|
+
};
|
|
1402
|
+
const layout = layoutAt(0);
|
|
1377
1403
|
if (!("wrapped" in layout)) {
|
|
1378
1404
|
return keep(layout);
|
|
1379
1405
|
}
|
|
@@ -1398,6 +1424,10 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1398
1424
|
const free = settle({ ...layoutOptions, avoidSentenceOrphans: false });
|
|
1399
1425
|
return !("wrapped" in free) || sameLayout(free.wrapped, currentLines);
|
|
1400
1426
|
};
|
|
1427
|
+
const keepsOnlySentenceStarts = () => {
|
|
1428
|
+
const free = settle({ ...layoutOptions, ignoreSentenceStarts: true });
|
|
1429
|
+
return "wrapped" in free && (sameLayout(free.even, currentLines) || sameLayout(free.wrapped, currentLines));
|
|
1430
|
+
};
|
|
1401
1431
|
const splitsOnlyParenthetical = (line) => {
|
|
1402
1432
|
if (layoutOptions.parentheticalOverflow === void 0) {
|
|
1403
1433
|
return false;
|
|
@@ -1425,7 +1455,7 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1425
1455
|
wrapped.findIndex((words, position) => words.join(" ") !== currentLines[position]?.join(" ")),
|
|
1426
1456
|
0
|
|
1427
1457
|
);
|
|
1428
|
-
if (lineIssues.length === 0 && sameLayout(even, currentLines)) {
|
|
1458
|
+
if (lineIssues.length === 0 && (sameLayout(even, currentLines) || keepsOnlySentenceStarts())) {
|
|
1429
1459
|
lineIssues.push({ type: "orphan", line });
|
|
1430
1460
|
} else if (lineIssues.length === 0) {
|
|
1431
1461
|
lineIssues.push({ type: splitsOnlyParenthetical(line) ? "split-parenthetical" : "unbalanced", line });
|
|
@@ -1434,10 +1464,22 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1434
1464
|
}
|
|
1435
1465
|
}
|
|
1436
1466
|
}
|
|
1437
|
-
const
|
|
1467
|
+
const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
|
|
1468
|
+
const matchesWiderLayout = () => {
|
|
1469
|
+
for (let extra = 1; extra <= layoutOptions.tolerance; extra += 1) {
|
|
1470
|
+
const wider = layoutAt(extra);
|
|
1471
|
+
if ("wrapped" in wider && sameLayout(wider.wrapped, currentLines)) {
|
|
1472
|
+
return true;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return false;
|
|
1476
|
+
};
|
|
1477
|
+
const accepted = lineIssues.some((issue) => isVerdict(issue)) && matchesWiderLayout();
|
|
1478
|
+
const suggested = accepted ? currentLines : wrapped;
|
|
1479
|
+
const suggestion = suggested.map(
|
|
1438
1480
|
(words, position) => `${prefix}${position === 0 ? paragraph.marker : hang}${words.join(" ")}`
|
|
1439
1481
|
);
|
|
1440
|
-
const wrapIssues = lineIssues.map((issue) => lineIssue(issue.type, paragraph.lines[issue.line], options));
|
|
1482
|
+
const wrapIssues = lineIssues.filter((issue) => !accepted || !isVerdict(issue)).map((issue) => lineIssue(issue.type, paragraph.lines[issue.line], options));
|
|
1441
1483
|
const issues = byLine([...overflow, ...wrapIssues, ...styleIssues(paragraph, options)]);
|
|
1442
1484
|
return { ...base, reflowable: true, issues, suggestion };
|
|
1443
1485
|
}
|
|
@@ -2875,7 +2917,8 @@ Config overrides (take precedence over the config file):
|
|
|
2875
2917
|
--width <n> Preferred comment line length
|
|
2876
2918
|
--max-width <n> Hard limit for prose (default: 120, never below width)
|
|
2877
2919
|
--single-line-width <n> Longest line a one-paragraph comment may stay on (default: 120)
|
|
2878
|
-
--tolerance <n> Columns prose may go past width to save a line, within max-width (default: 0)
|
|
2920
|
+
--tolerance <n> Columns prose may go past width to save a line, within max-width (default: 0).
|
|
2921
|
+
A paragraph that has the layout for a width up to that much wider also passes
|
|
2879
2922
|
--[no-]orphans Keep a sentence's first word off the end of a line, and end the first of two lines
|
|
2880
2923
|
with a sentence when the second is no longer than the first (default: on)
|
|
2881
2924
|
--[no-]style Look for possible semicolons, em-dashes, dashes, label colons, emphasis, and banners
|