@d3lm/lint-preset 1.5.1 → 1.5.4
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 +9 -4
- package/dist/bin/comments.js +75 -31
- package/dist/bin/comments.js.map +1 -1
- package/dist/comments.cjs +60 -6
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.d.cts +11 -8
- package/dist/comments.d.ts +11 -8
- package/dist/comments.js +60 -6
- package/dist/comments.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -245,7 +245,7 @@ comments agent # hand suspicious comments to an agent, then
|
|
|
245
245
|
comments check --cwd ../other-app # run as if started in another project
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
Without targets, only comments in uncommitted changes and untracked files are checked. `--
|
|
248
|
+
Without targets, only comments in uncommitted changes and untracked files are checked. `--base main` checks everything since the merge base with `main`, and `--repo` checks every file. `--whole-files` checks every comment in the changed files instead of only the comments on changed lines. Targets can be files, directories, `file:line`, or `file:start-end`. A comment that contains `comments-ignore` is skipped. A file with a syntax error is skipped as a whole and named in a warning, because the parser stops at the error and never sees the comments after it. JSX is read in `.js` files too.
|
|
249
249
|
|
|
250
250
|
With `--json`, each paragraph of a comment comes with its issues and a `suggestion` that holds the exact rewrapped lines. Code blocks, `@example`, tables, drawings, directives, and other [text kept as written](#text-kept-as-written) have no suggestion.
|
|
251
251
|
|
|
@@ -261,6 +261,7 @@ Verdicts are layout issues that can be measured without reading the prose. They
|
|
|
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
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
|
|
264
|
+
- `split-parenthetical` means a line ends inside a parenthetical that the suggested rewrap keeps on one line within `maxWidth`
|
|
264
265
|
- `em-dash` means the comment uses `—`
|
|
265
266
|
- `dash` means the comment uses `–` or `--` between words
|
|
266
267
|
- `semicolon` means the comment joins clauses with a semicolon
|
|
@@ -268,7 +269,9 @@ Hints come from heuristics and can be false positives, because only the meaning
|
|
|
268
269
|
- `emphasis` means the comment uses `*emphasis*` or `**bold**`
|
|
269
270
|
- `banner` means the comment is a divider made of repeated characters
|
|
270
271
|
|
|
271
|
-
A paragraph with an `orphan` hint is not reported as `unbalanced
|
|
272
|
+
A paragraph with an `orphan` hint is not reported as `unbalanced` when that word is all that sets it apart from the suggested rewrap, because the agent may rightly keep the word where it is. This holds when the paragraph matches the rewrap that allows orphans. A paragraph that differs from that rewrap too, like a line that ends with the hinted word while more words fit on it, is still `unbalanced`. A line that ends early is still an `underfill`, whatever its last word is.
|
|
273
|
+
|
|
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`.
|
|
272
275
|
|
|
273
276
|
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.
|
|
274
277
|
|
|
@@ -294,7 +297,7 @@ A comment with an issue in a part that is kept is handed to the agent, which can
|
|
|
294
297
|
*/
|
|
295
298
|
```
|
|
296
299
|
|
|
297
|
-
|
|
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.
|
|
298
301
|
|
|
299
302
|
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.
|
|
300
303
|
|
|
@@ -306,7 +309,9 @@ export default defineConfig({
|
|
|
306
309
|
// defaults to prettier's printWidth, or 80
|
|
307
310
|
width: 100,
|
|
308
311
|
maxWidth: 120,
|
|
309
|
-
//
|
|
312
|
+
// prose lines may go this many columns past `width` when that saves a line
|
|
313
|
+
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
|
|
310
315
|
singleLineWidth: 120,
|
|
311
316
|
avoidSentenceOrphans: true,
|
|
312
317
|
// look for possible semicolons, em-dashes, dashes, label colons, emphasis, and banners as hints,
|
package/dist/bin/comments.js
CHANGED
|
@@ -24,8 +24,8 @@ When rules pull in different directions, the earlier one wins.
|
|
|
24
24
|
|
|
25
25
|
## Layout
|
|
26
26
|
|
|
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. 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.
|
|
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.
|
|
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.
|
|
@@ -65,7 +65,8 @@ var ISSUE_DESCRIPTIONS = {
|
|
|
65
65
|
underfill: "ends early, and words from the next line fit on it",
|
|
66
66
|
widow: "leaves fewer than three words on the last line of its paragraph",
|
|
67
67
|
unbalanced: "is in a paragraph whose lines are less even than the suggested rewrap",
|
|
68
|
-
orphan: "may end with the start of a new sentence, clause, or parenthetical, which belongs on the next line (leave it if the period or comma before it is part of an abbreviation, a name, or notation like `No. 3
|
|
68
|
+
orphan: "may end with the start of a new sentence, clause, or parenthetical, which belongs on the next line (leave it if the period or comma before it is part of an abbreviation, a name, or notation like `No. 3`, or if it is a long word like an identifier and moving it would leave the line far shorter than the lines around it)",
|
|
69
|
+
"split-parenthetical": "may split a parenthetical that the suggested rewrap keeps on one line, past the width but within the maximum (leave it if the split reads as well, like at a comma inside a list, and the line stays closer to the width)",
|
|
69
70
|
"em-dash": "may use an em-dash to join two thoughts (leave it if it's in a quotation, a title, or a string literal, or reads more clearly than the alternatives)",
|
|
70
71
|
banner: "may be a section banner (leave it if it's part of a diagram, a table, a box, a Markdown rule, a separator in an example, or legal text)",
|
|
71
72
|
semicolon: "may join clauses with a semicolon (leave it if it's code, a literal, not a clause join, or reads more clearly than a period or a joining word)",
|
|
@@ -131,7 +132,8 @@ function buildPrompt(options) {
|
|
|
131
132
|
`Fix the formatting of the comments below, following the rules above. Paths are relative to ${options.root}. Widths include indentation and the comment prefix. The notes under each comment come from the tool's measurements, so never count columns yourself.`,
|
|
132
133
|
"",
|
|
133
134
|
"- After you finish, the tool rewraps every prose paragraph as described under Layout, so a layout note on a prose paragraph needs no change from you. Change only the wording there, and leave its line breaks to the tool.",
|
|
134
|
-
"- A note that a line may end with the start of a sentence, clause, or parenthetical is the exception. Only you can tell whether it does, so if it does, move those words to the start of the next line.",
|
|
135
|
+
"- A note that a line may end with the start of a sentence, clause, or parenthetical is the exception. Only you can tell whether it does, so if it does, move those words to the start of the next line, unless the Layout rules let a long word stay.",
|
|
136
|
+
"- A note that a line may split a parenthetical is the other exception. If the parenthetical reads better on one line, move the rest of it up to the end of that line yourself.",
|
|
135
137
|
"- Parts kept as written are never rewrapped, and their notes say so. Fix those notes by hand, for example by breaking an overflowing prose line or by turning a long single-line comment into a multi-line one. Keep code, code spans, tables, box drawings, hard line breaks, and other intentional layout in them exactly as they are. If a note can only be fixed by changing those, leave it and say why in your reply.",
|
|
136
138
|
`- Notes marked \`${HINT_MARKER}\` come from heuristics. If the rule doesn't actually apply, don't change anything for that note.`,
|
|
137
139
|
"- Notes only point at places worth a look. Review each comment against the rules, and leave it as it is when it already follows them.",
|
|
@@ -489,6 +491,9 @@ function parentheticalGroups(tokens, limit) {
|
|
|
489
491
|
}
|
|
490
492
|
return groups;
|
|
491
493
|
}
|
|
494
|
+
function parentheticalAround(tokens, at, limit) {
|
|
495
|
+
return parentheticalGroups(tokens, limit).find((group) => group.start < at && at < group.end);
|
|
496
|
+
}
|
|
492
497
|
function bindParentheticals(tokens, limit) {
|
|
493
498
|
const groups = new Map(parentheticalGroups(tokens, limit).map((group) => [group.start, group.end]));
|
|
494
499
|
const units = [];
|
|
@@ -825,6 +830,19 @@ function layoutLines(tokens, firstLimit, restLimit, options) {
|
|
|
825
830
|
return lines;
|
|
826
831
|
}
|
|
827
832
|
}
|
|
833
|
+
if (options.tolerance > 0 && greedy.length > 2) {
|
|
834
|
+
const fewer = balanceTokens(
|
|
835
|
+
tokens,
|
|
836
|
+
firstLimit + options.tolerance,
|
|
837
|
+
restLimit + options.tolerance,
|
|
838
|
+
greedy.length - 1,
|
|
839
|
+
options,
|
|
840
|
+
restLimit
|
|
841
|
+
);
|
|
842
|
+
if (fewer && !hasStep(fewer, restLimit - firstLimit)) {
|
|
843
|
+
return fewer;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
828
846
|
const balanced = balanceTokens(tokens, firstLimit, restLimit, greedy.length, options);
|
|
829
847
|
if (balanced) {
|
|
830
848
|
return balanced;
|
|
@@ -880,6 +898,7 @@ var ISSUE_TIERS = {
|
|
|
880
898
|
widow: "verdict",
|
|
881
899
|
unbalanced: "verdict",
|
|
882
900
|
orphan: "hint",
|
|
901
|
+
"split-parenthetical": "hint",
|
|
883
902
|
banner: "hint",
|
|
884
903
|
"em-dash": "hint",
|
|
885
904
|
semicolon: "hint",
|
|
@@ -1375,9 +1394,44 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1375
1394
|
if (hasWidow(currentLines) && !hasWidow(wrapped)) {
|
|
1376
1395
|
lineIssues.push({ type: "widow", line: currentLines.length - 1 });
|
|
1377
1396
|
}
|
|
1378
|
-
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1397
|
+
const keepsOnlyOrphans = () => {
|
|
1398
|
+
const free = settle({ ...layoutOptions, avoidSentenceOrphans: false });
|
|
1399
|
+
return !("wrapped" in free) || sameLayout(free.wrapped, currentLines);
|
|
1400
|
+
};
|
|
1401
|
+
const splitsOnlyParenthetical = (line) => {
|
|
1402
|
+
if (layoutOptions.parentheticalOverflow === void 0) {
|
|
1403
|
+
return false;
|
|
1404
|
+
}
|
|
1405
|
+
const tokenCount = (lines) => tokenize(lines.flat().join(" ")).length;
|
|
1406
|
+
const end = tokenCount(currentLines.slice(0, line + 1));
|
|
1407
|
+
const group = parentheticalAround(tokens, end, restLimit + layoutOptions.parentheticalOverflow);
|
|
1408
|
+
if (!group) {
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
const keepsGroup = wrapped.every((_, position) => {
|
|
1412
|
+
const breakAt = tokenCount(wrapped.slice(0, position + 1));
|
|
1413
|
+
return breakAt <= group.start || breakAt >= group.end;
|
|
1414
|
+
});
|
|
1415
|
+
if (!keepsGroup) {
|
|
1416
|
+
return false;
|
|
1417
|
+
}
|
|
1418
|
+
const restOptions = { ...layoutOptions, keepFirstLine: false };
|
|
1419
|
+
const rest = bindLineStarts(tokens.slice(end), startsLine);
|
|
1420
|
+
const restLines = layoutTokens(rest, restLimit, restLimit, restOptions);
|
|
1421
|
+
return sameLayout(breakAfterSentence(restLines, restLimit, restLimit, restOptions), currentLines.slice(line + 1));
|
|
1422
|
+
};
|
|
1423
|
+
if (overflow.length === 0 && (currentLines.length === wrapped.length || options.maxWidth !== void 0)) {
|
|
1424
|
+
const line = Math.max(
|
|
1425
|
+
wrapped.findIndex((words, position) => words.join(" ") !== currentLines[position]?.join(" ")),
|
|
1426
|
+
0
|
|
1427
|
+
);
|
|
1428
|
+
if (lineIssues.length === 0 && sameLayout(even, currentLines)) {
|
|
1429
|
+
lineIssues.push({ type: "orphan", line });
|
|
1430
|
+
} else if (lineIssues.length === 0) {
|
|
1431
|
+
lineIssues.push({ type: splitsOnlyParenthetical(line) ? "split-parenthetical" : "unbalanced", line });
|
|
1432
|
+
} else if (lineIssues.every((issue) => issue.type === "orphan") && !sameLayout(even, currentLines) && !keepsOnlyOrphans()) {
|
|
1433
|
+
lineIssues.push({ type: "unbalanced", line });
|
|
1434
|
+
}
|
|
1381
1435
|
}
|
|
1382
1436
|
}
|
|
1383
1437
|
const suggestion = wrapped.map(
|
|
@@ -1507,7 +1561,9 @@ function inspectSource(source, filename, options) {
|
|
|
1507
1561
|
options.width,
|
|
1508
1562
|
Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
|
|
1509
1563
|
);
|
|
1510
|
-
const
|
|
1564
|
+
const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
|
|
1565
|
+
const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options) : Infinity;
|
|
1566
|
+
const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
|
|
1511
1567
|
const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
|
|
1512
1568
|
reports.push({
|
|
1513
1569
|
file: filename,
|
|
@@ -2167,23 +2223,12 @@ function snapshotChanges(root) {
|
|
|
2167
2223
|
var DIFF_FLAGS = ["diff", "-U0", "--no-color", "--no-ext-diff", "--no-renames", "--src-prefix=a/", "--dst-prefix=b/"];
|
|
2168
2224
|
function getChangedLines(root, scope = {}) {
|
|
2169
2225
|
if (!hasHead(root)) {
|
|
2170
|
-
|
|
2171
|
-
return new Map(files.map((file) => [file, "all"]));
|
|
2172
|
-
}
|
|
2173
|
-
let args;
|
|
2174
|
-
if (scope.staged) {
|
|
2175
|
-
args = [...DIFF_FLAGS, "--cached", "HEAD"];
|
|
2176
|
-
} else if (scope.base) {
|
|
2177
|
-
const mergeBase = gitOrThrow(root, ["merge-base", scope.base, "HEAD"]).trim();
|
|
2178
|
-
args = [...DIFF_FLAGS, mergeBase];
|
|
2179
|
-
} else {
|
|
2180
|
-
args = [...DIFF_FLAGS, "HEAD"];
|
|
2226
|
+
return new Map(listFiles2(root).map((file) => [file, "all"]));
|
|
2181
2227
|
}
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2228
|
+
const target = scope.base ? gitOrThrow(root, ["merge-base", scope.base, "HEAD"]).trim() : "HEAD";
|
|
2229
|
+
const changed = parseUnifiedDiff(gitOrThrow(root, [...DIFF_FLAGS, target]));
|
|
2230
|
+
for (const file of listUntrackedFiles(root)) {
|
|
2231
|
+
changed.set(file, "all");
|
|
2187
2232
|
}
|
|
2188
2233
|
return changed;
|
|
2189
2234
|
}
|
|
@@ -2298,7 +2343,7 @@ var Project = class {
|
|
|
2298
2343
|
throw new Error("comments: not a git repository, so pass files or directories explicitly");
|
|
2299
2344
|
}
|
|
2300
2345
|
for (const [file, lines] of getChangedLines(this.root, scope)) {
|
|
2301
|
-
add(path4.join(this.root, file), lines);
|
|
2346
|
+
add(path4.join(this.root, file), scope.wholeFiles ? "all" : lines);
|
|
2302
2347
|
}
|
|
2303
2348
|
return files;
|
|
2304
2349
|
}
|
|
@@ -2809,8 +2854,8 @@ Targets are files, directories, file:line, or file:start-end. Without targets, o
|
|
|
2809
2854
|
overlapping uncommitted changes (and untracked files) are considered.
|
|
2810
2855
|
|
|
2811
2856
|
Options:
|
|
2812
|
-
--staged Only consider staged changes
|
|
2813
2857
|
--base <ref> Consider changes since the merge base with <ref>
|
|
2858
|
+
--whole-files Consider every comment in the changed files, not only comments on changed lines
|
|
2814
2859
|
--repo Consider every file in the repository
|
|
2815
2860
|
--suspicious Only comments with layout issues (overflow, early line ends, last lines of one or two words)
|
|
2816
2861
|
or hints (possible orphans, em-dashes, banners, ...). For agent and hook (the default), only
|
|
@@ -2830,7 +2875,7 @@ Config overrides (take precedence over the config file):
|
|
|
2830
2875
|
--width <n> Preferred comment line length
|
|
2831
2876
|
--max-width <n> Hard limit for prose (default: 120, never below width)
|
|
2832
2877
|
--single-line-width <n> Longest line a one-paragraph comment may stay on (default: 120)
|
|
2833
|
-
--tolerance <n>
|
|
2878
|
+
--tolerance <n> Columns prose may go past width to save a line, within max-width (default: 0)
|
|
2834
2879
|
--[no-]orphans Keep a sentence's first word off the end of a line, and end the first of two lines
|
|
2835
2880
|
with a sentence when the second is no longer than the first (default: on)
|
|
2836
2881
|
--[no-]style Look for possible semicolons, em-dashes, dashes, label colons, emphasis, and banners
|
|
@@ -2856,8 +2901,8 @@ var OPTIONS = {
|
|
|
2856
2901
|
"max-files": { type: "string" },
|
|
2857
2902
|
"max-agents": { type: "string" },
|
|
2858
2903
|
instructions: { type: "string" },
|
|
2859
|
-
staged: { type: "boolean" },
|
|
2860
2904
|
base: { type: "string" },
|
|
2905
|
+
"whole-files": { type: "boolean" },
|
|
2861
2906
|
repo: { type: "boolean" },
|
|
2862
2907
|
suspicious: { type: "boolean" },
|
|
2863
2908
|
all: { type: "boolean" },
|
|
@@ -2944,7 +2989,7 @@ async function createProject(cwd, values) {
|
|
|
2944
2989
|
return { project, configPath: loaded.filepath };
|
|
2945
2990
|
}
|
|
2946
2991
|
function scopeFrom(positionals, values) {
|
|
2947
|
-
return { targets: positionals,
|
|
2992
|
+
return { targets: positionals, base: values.base, wholeFiles: values["whole-files"], repo: values.repo };
|
|
2948
2993
|
}
|
|
2949
2994
|
function selectFrom(values, project) {
|
|
2950
2995
|
if (values.all) {
|
|
@@ -3005,9 +3050,8 @@ function commentsOf(analyses) {
|
|
|
3005
3050
|
function hintEmptyScope(analyses, scope) {
|
|
3006
3051
|
const isDefaultScope = (scope.targets ?? []).length === 0 && !scope.repo && !scope.base;
|
|
3007
3052
|
if (analyses.length === 0 && isDefaultScope) {
|
|
3008
|
-
const changes = scope.staged ? "staged changes" : "uncommitted changes";
|
|
3009
3053
|
log2(
|
|
3010
|
-
|
|
3054
|
+
"comments: no uncommitted changes in JavaScript or TypeScript files. Pass --repo, --base <ref>, or files to widen the scope."
|
|
3011
3055
|
);
|
|
3012
3056
|
}
|
|
3013
3057
|
}
|