@d3lm/lint-preset 1.6.0 → 1.6.2

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 CHANGED
@@ -261,7 +261,7 @@ Verdicts are layout issues that can be measured without reading the prose. They
261
261
 
262
262
  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:
263
263
 
264
- - `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
+ - `orphan` means a line ends with the first word of a sentence or parenthetical, or with the first word after a semicolon or colon (but not after a comma), 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
265
265
  - `split-parenthetical` means a line ends inside a parenthetical that the suggested rewrap keeps on one line within `maxWidth`
266
266
  - `em-dash` means the comment uses `—`
267
267
  - `dash` means the comment uses `–` or `--` between words
@@ -276,40 +276,34 @@ In the same way, a paragraph that splits a parenthetical is not reported as `unb
276
276
 
277
277
  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`.
278
278
 
279
+ The `tolerance` also holds for each line on its own, in both directions. A paragraph is not `unbalanced` when it has as many lines as the suggested rewrap and no line is more than `tolerance` columns shorter or longer than the same line of the rewrap. Its suggestion then keeps the lines as they are too. With `tolerance: 5`, a short word like `as` can start a line where the rewrap ends the line before with it. A line that is longer than in the rewrap must still end within `width` plus `tolerance`. This only applies to a paragraph whose only layout issue is `unbalanced`. A `widow` is still reported, and so is a paragraph that has an `orphan` hint too, so the rewrap still moves that word. A line that ends inside a link, code span, `{...}` group, or the head of a JSDoc tag is still `unbalanced` as well.
280
+
279
281
  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.
280
282
 
281
283
  ### Text That Stays on One Line
282
284
 
283
- The rewrap never breaks a line inside a code span, a `{...}` group like a JSDoc type or `{@link ...}`, or a Markdown link. A link is the text in brackets with the target right after it, which is `[text](target)` with an optional title, `[text][label]`, or `[text]` on its own. Brackets pair up like in Markdown, so a bracket in a code span or after a backslash doesn't count. Only the syntax counts, because only the renderer knows whether `[text]` resolves to anything, like an intra-doc link in Rust. A link that continues on the next line can't be followed in an editor. rust-analyzer, for one, only resolves an intra-doc link that sits on one line of a `///` comment.
284
-
285
- Each of them is one word for the layout. A line that ends inside one is `unbalanced`, whatever else holds for the paragraph, so `check` fails and the rewrap joins it. The only exception is a code span in a Rust file, which keeps its paragraph as written (see [Rust](#rust)). One that is longer than `maxWidth` allows stays intact. On a single line it is an `overflow`. When it continues on the next line, joining it would add a line that is too long, so its paragraph is kept as written (`unbreakable text across lines`), and only shortening it or moving its target to a link reference definition fixes it.
285
+ The rewrap never breaks a line inside a code span, a `{...}` group like a JSDoc type or `{@link ...}`, or a Markdown link like `[text](target)`, `[text][label]`, or `[text]`. Links split across lines can't be followed in an editor, so a line that ends inside one of these is `unbalanced`, and the rewrap joins it. If one is too long to fit within `maxWidth`, it stays intact and is reported as `overflow`.
286
286
 
287
287
  ### Text Kept as Written
288
288
 
289
- 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:
289
+ The tool only rewraps prose paragraphs, list items, and JSDoc tag descriptions. Everything else keeps its line breaks and has no suggestion:
290
290
 
291
- - 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, or as Rust in a Rust file.
292
- - 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).
293
- - 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` (or with `let`, `use`, or an attribute like `#[derive(Debug)]` in Rust files), get no hints. Hand layout, such as Markdown hard line breaks, headings, quotes, arrows, and `term - description` lists, still gets hints.
294
- - Line comments (`//`) on their own lines get `overflow` and nothing else, so a line comment that fits `maxWidth` is never reported. A run of them on consecutive lines with the same indentation is one comment, and an empty `//` line separates its paragraphs. Code and layout syntax in them are exempt like above, and so is each line with a tool directive. A line comment after code on the same line is never checked. The agent fixes an overflowing line comment by shortening it, or by turning it into a multi-line block comment (`/** */` in JavaScript and TypeScript, `/* */` in Rust), which the tool then rewraps.
291
+ - **Code** gets no checks except `banner`. This covers code blocks, `@example`, `@default`, tool directives like `eslint-disable-next-line`, legal comments, and paragraphs that parse as code.
292
+ - **Layout syntax** gets hints but no verdicts. This covers Markdown tables, box-drawing characters, divider lines, and aligned columns.
293
+ - **Text that only looks like code or hand layout** keeps its line breaks, but it still gets `overflow`.
294
+ - **Line comments** (`//`) only get `overflow`. To fix one, the agent shortens it or turns it into a block comment, which the tool then rewraps.
295
295
 
296
- To keep a drawing that is wider than the width, put it in a code fence or add `comments-ignore` to the comment.
296
+ To keep a drawing that's wider than the width, put it in a code fence or add `comments-ignore` to the comment.
297
297
 
298
298
  ### Rust
299
299
 
300
- In `.rs` files, the tool checks plain block comments (`/* */`) and every kind of doc comment: outer and inner line doc comments (`///` and `//!`) and outer and inner block doc comments (`/** */` and `/*! */`). A run of `///` or `//!` lines with the same indentation is one comment, and each line holds a prefix like the ` * ` of a block comment, so a run is checked and rewrapped like a JSDoc comment with the same text. The run ends at an empty line, at another marker, or at a line with other indentation. Other line comments, including `////`, and block comments that rustdoc doesn't read as docs, like `/***` and `/**/`, are plain comments, like in rustc. Plain line comments are only checked for lines past `maxWidth`, like `//` comments in JavaScript.
301
-
302
- Rust comments are found with `rustc`'s own lexer (`ra_ap_rustc_lexer`, built into a native addon with [NAPI-RS](https://napi.rs)), so raw strings, byte and C strings, characters, lifetimes, nested block comments, a shebang, and the frontmatter of a cargo script never produce false matches. The lexer never stops early. A source with a token that never ends, like an unterminated block comment or string, is skipped and named like a file with a syntax error, because that token holds every comment after it. Like rustc, the tool reads a `/*` inside a block comment as the start of a nested comment, even inside a code span.
303
-
304
- Doc comments are Markdown, and the checks above already treat them that way. Code blocks are doctests, so they are never touched, including hidden lines that start with `#`. Headings like `# Examples` and link reference definitions like ``[`Foo`]: crate::Foo`` are kept as written. Intra-doc links like ``[`count_provider_reads`]``, ``[`permissions`](crate::permissions)``, ``[the parser][`Parser`]``, or `[HashMap<K, V>]` always stay on one line, with their text and their target (see [Text That Stays on One Line](#text-that-stays-on-one-line)). A link that continues on the next line is joined, which rustdoc resolves the same way, since it reads runs of whitespace in a link as one space. A code span that continues on the next line is different. rustdoc shows the spaces at the start of the next line inside the code span, and how many there are depends on the indentation of every line of the item's docs, so a paragraph with such a code span is kept as written in Rust (`code span across lines`), and the tool never splits a code span itself. Rust has no JSDoc tags, so a line that starts with `@` is prose. Paragraphs are code when they parse as Rust statements, items, fields, match arms, or associated items (with `ra_ap_syntax`, the parser of rust-analyzer). A `/*!` or `//!` comment is an inner doc comment, not a legal comment, so only `@license`, `@preserve`, and copyright notices make a Rust comment legal.
305
-
306
- The code fingerprint that makes sure only comments changed compares the tokens of a Rust file, without whitespace and comments. Each run of doc comments counts as one token of its style, since doc comments are attributes of the item after them. So rewording or rewrapping a doc comment is no code change, but turning `///` into `//`, into `//!`, or into `/** */`, or moving it to another item, is.
300
+ In `.rs` files, the tool checks doc comments (`///`, `//!`, `/** */`, `/*! */`) and plain block comments the same way it checks JSDoc. Plain `//` comments only get `overflow`. Comments are found with rustc's own lexer, which ships as a native addon built with [NAPI-RS](https://napi.rs). Doctests, intra-doc links, and anything else that would change what rustdoc renders are left intact.
307
301
 
308
- The width of a Rust file defaults to `comment_width` from the nearest `rustfmt.toml` or `.rustfmt.toml`, which is 80 when it's not set, like in rustfmt, and tabs count as `tab_spaces` columns (4 by default). Prettier's config never applies to Rust files. `width` in the `comments` config still overrides both.
302
+ The width defaults to `comment_width` from `rustfmt.toml`, or 80 if it isn't set. Setting `width` in the `comments` config overrides it.
309
303
 
310
- The package ships the addon for macOS on arm64 and x64, and for Linux with glibc 2.17 or later on x64 and arm64. CI builds each of these `targets` from `native/napi.json` on its own runner and tests it there, and publishing fails when one of them is missing. In this repository, the addon is built with `pnpm run build:native`, which `build` and `test` run too, and needs a Rust toolchain. Without an addon for the platform, like on Windows or on Linux with musl, Rust files fail with a message that says how to build it, and JavaScript and TypeScript files work as before. To skip Rust files, leave `.rs` out of `extensions`, or pass `--ignore '**/*.rs'` for a single run.
304
+ The addon ships for macOS (arm64, x64) and Linux with glibc (x64, arm64). On other platforms, Rust files fail with a message that explains how to build the addon (`pnpm run build:native`, which needs a Rust toolchain). To skip Rust files, leave `.rs` out of `extensions`.
311
305
 
312
- `native/verify/verify.mjs` verifies that rewrapping Rust comments never changes code or what rustdoc renders. It rewraps the fixture crate in `native/verify/fixture` with the CLI and the comments of any `--corpus` directory, like `~/.cargo/registry/src/<index>`, with the library, at several widths. Then it checks that the code fingerprint and the words of every comment stay the same, that a second rewrap changes nothing, that every paragraph keeps its kind, and that no prose has a verdict left. The only exception is an overflow from a word that is longer than the maximum on its own, or from a line that holds no more text than the longest line of its paragraph did before, since a rewrap never adds a line like that. For the fixture, it also checks that the crate compiles and that its doctests pass. Last, it compares the JSON output of nightly rustdoc before and after: every item must render the same Markdown events, resolve the same intra-doc links, and have the same doc text apart from whitespace. It also finds the links of every item with pulldown-cmark, so the check doesn't rely on what the tool takes for a link. A link that was on one line must never continue on the next line after the rewrap, and no link of the fixture may continue on the next line at all, as long as each of them fits on a line.
306
+ `native/verify/verify.mjs` checks that rewrapping Rust comments never changes code or rustdoc output. It runs on a fixture crate and, optionally, on a `--corpus` of real crates:
313
307
 
314
308
  ```bash
315
309
  pnpm run build
@@ -318,7 +312,7 @@ node native/verify/verify.mjs --corpus ~/.cargo/registry/src/index.crates.io-194
318
312
 
319
313
  ### Configuration
320
314
 
321
- `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, code span, or link longer than `maxWidth` is never split and is reported as `overflow`.
315
+ `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`, and a paragraph whose lines are each within `tolerance` columns of the suggested rewrap (see [Issues](#issues)), 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, code span, or link longer than `maxWidth` is never split and is reported as `overflow`.
322
316
 
323
317
  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.
324
318
 
@@ -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. 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.
28
+ - Don't end a line with the first word of a new sentence or parenthetical, like \`This\` after a period or \`(the\`, or with the first word after a semicolon or colon. 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. The word after a comma is no such word. A line may end with it, like \`and\` in \`...the teardown paths, and\`.
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.
@@ -75,7 +75,7 @@ var ISSUE_DESCRIPTIONS = {
75
75
  underfill: "ends early, and words from the next line fit on it",
76
76
  widow: "leaves fewer than three words on the last line of its paragraph",
77
77
  unbalanced: "is in a paragraph whose lines are less even than the suggested rewrap, or that splits a link or code span that the rewrap keeps on one line",
78
- 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)",
78
+ orphan: "may end with the start of a new sentence or parenthetical, or with the first word after a semicolon or colon, which belongs on the next line (leave it if the punctuation 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)",
79
79
  "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)",
80
80
  "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)",
81
81
  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)",
@@ -142,7 +142,7 @@ function buildPrompt(options) {
142
142
  `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.`,
143
143
  "",
144
144
  "- 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.",
145
- "- 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.",
145
+ "- A note that a line may end with the start of a sentence or parenthetical, or with the first word after a semicolon or colon, 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.",
146
146
  "- 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.",
147
147
  "- 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.",
148
148
  `- Notes marked \`${HINT_MARKER}\` come from heuristics. If the rule doesn't actually apply, don't change anything for that note.`,
@@ -430,6 +430,7 @@ function findStyleIssues(lines) {
430
430
  // src/comments/wrap.ts
431
431
  var CLAUSE_END = /[.!?,;:]["')\]*_]*$/;
432
432
  var SENTENCE_END = /[.!?]["')\]*_]*$/;
433
+ var STATEMENT_END = /[.!?;:]["')\]*_]*$/;
433
434
  var ABBREVIATIONS = /* @__PURE__ */ new Set([
434
435
  "e.g.",
435
436
  "i.e.",
@@ -661,8 +662,8 @@ function bindParentheticals(tokens, limit) {
661
662
  }
662
663
  return units;
663
664
  }
664
- function endsClause(token) {
665
- return CLAUSE_END.test(token) && !isAbbreviation(token);
665
+ function endsStatement(token) {
666
+ return STATEMENT_END.test(token) && !isAbbreviation(token);
666
667
  }
667
668
  function endsSentence(token) {
668
669
  return SENTENCE_END.test(token) && !isAbbreviation(token);
@@ -671,7 +672,7 @@ function opensAside(token) {
671
672
  return /^[([]/.test(token) && !/[)\]]["'`.,;:!?]*$/.test(token);
672
673
  }
673
674
  function isOrphan(previous, token) {
674
- return opensAside(token) || endsClause(previous) && !CLAUSE_END.test(token);
675
+ return opensAside(token) || endsStatement(previous) && !CLAUSE_END.test(token);
675
676
  }
676
677
  function startsBlock(token) {
677
678
  return /^(?:\+|-+|=+|\*+|_{3,}|\d+[.)]|#{1,6})$/.test(token) || /^(?:>|@[A-Za-z]|`{3}|~{3}|\||<(?:\/?[A-Za-z]|[!?]))/.test(token);
@@ -1685,7 +1686,20 @@ function analyzeParagraph(paragraph, options, language) {
1685
1686
  }
1686
1687
  return false;
1687
1688
  };
1688
- const accepted = lineIssues.some((issue) => isVerdict(issue)) && matchesWiderLayout();
1689
+ const staysNearLayout = () => {
1690
+ if (currentLines.length !== wrapped.length || splitTokenLine(currentLines, units) !== -1) {
1691
+ return false;
1692
+ }
1693
+ return currentLines.every((words, position) => {
1694
+ const length = words.join(" ").length;
1695
+ const suggestedLength = wrapped[position].join(" ").length;
1696
+ const limit = (position === 0 ? firstLimit : restLimit) + layoutOptions.tolerance;
1697
+ return Math.abs(length - suggestedLength) <= layoutOptions.tolerance && length <= Math.max(suggestedLength, limit);
1698
+ });
1699
+ };
1700
+ const hasVerdict = lineIssues.some((issue) => isVerdict(issue));
1701
+ const onlyUnbalanced = lineIssues.every((issue) => issue.type === "unbalanced");
1702
+ const accepted = hasVerdict && (matchesWiderLayout() || onlyUnbalanced && staysNearLayout());
1689
1703
  const suggested = accepted ? currentLines : wrapped;
1690
1704
  const suggestion = suggested.map(
1691
1705
  (words, position) => `${prefix}${position === 0 ? paragraph.marker : hang}${words.join(" ")}`