@d3lm/lint-preset 1.5.6 → 1.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.
package/dist/comments.cjs CHANGED
@@ -1,8 +1,75 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }require('./chunk-56WU3HSD.cjs');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+ var _chunk56WU3HSDcjs = require('./chunk-56WU3HSD.cjs');
2
4
 
3
5
  // src/comments/analyze.ts
4
6
  var _oxcparser = require('oxc-parser');
5
7
 
8
+ // src/comments/rust.ts
9
+ var _fs = require('fs');
10
+ var _module = require('module');
11
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
12
+ var _url = require('url');
13
+ var BINARY_NAME = "comments";
14
+ function platformSuffixes() {
15
+ const { platform, arch } = process;
16
+ switch (platform) {
17
+ case "darwin": {
18
+ return [`darwin-${arch}`, "darwin-universal"];
19
+ }
20
+ case "linux": {
21
+ return arch === "arm" ? ["linux-arm-gnueabihf", "linux-arm-musleabihf"] : [`linux-${arch}-gnu`, `linux-${arch}-musl`];
22
+ }
23
+ case "win32": {
24
+ return [`win32-${arch}-msvc`, `win32-${arch}-gnu`];
25
+ }
26
+ default: {
27
+ return [`${platform}-${arch}`];
28
+ }
29
+ }
30
+ }
31
+ function findBinaries() {
32
+ const names = platformSuffixes().map((suffix) => `${BINARY_NAME}.${suffix}.node`);
33
+ for (let directory = _path2.default.dirname(_url.fileURLToPath.call(void 0, _chunk56WU3HSDcjs.importMetaUrl)); ; directory = _path2.default.dirname(directory)) {
34
+ const found = names.map((name) => _path2.default.join(directory, "native", name)).filter((file) => _fs.existsSync.call(void 0, file));
35
+ if (found.length > 0) {
36
+ return found;
37
+ }
38
+ if (_path2.default.dirname(directory) === directory) {
39
+ return [];
40
+ }
41
+ }
42
+ }
43
+ var binding;
44
+ function loadBinding() {
45
+ if (binding) {
46
+ return binding;
47
+ }
48
+ const require2 = _module.createRequire.call(void 0, _chunk56WU3HSDcjs.importMetaUrl);
49
+ const errors = [];
50
+ for (const file of findBinaries()) {
51
+ try {
52
+ binding = require2(file);
53
+ return binding;
54
+ } catch (error) {
55
+ errors.push(`${file}: ${error.message}`);
56
+ }
57
+ }
58
+ const reason = errors.length > 0 ? `, and loading it failed (${errors.join("; ")})` : "";
59
+ throw new Error(
60
+ `comments: Rust files need the native addon for ${process.platform}-${process.arch}${reason}. Build it with \`pnpm run build:native\`, or leave \`.rs\` out of \`extensions\`.`
61
+ );
62
+ }
63
+ function scanRust(source) {
64
+ return loadBinding().scanRust(source);
65
+ }
66
+ function rustFingerprint(source) {
67
+ return loadBinding().rustFingerprint(source);
68
+ }
69
+ function isRustCode(text) {
70
+ return loadBinding().isRustCode(text);
71
+ }
72
+
6
73
  // src/comments/style.ts
7
74
  var BANNER = /^(?!#{1,6}\s)([-=*#_~+/\\])\1{3,}|([-=*#_~])\2{3,}\s*$/;
8
75
  var COLUMN_RULE = /^([-=_~])\1*(?:\s+\1+)+$/;
@@ -126,12 +193,94 @@ function findCodeSpanEnd(text, start) {
126
193
  }
127
194
  return -1;
128
195
  }
196
+ function isEscaped(text, index) {
197
+ let backslashes = 0;
198
+ while (text[index - 1 - backslashes] === "\\") {
199
+ backslashes += 1;
200
+ }
201
+ return backslashes % 2 === 1;
202
+ }
203
+ function findUnescaped(text, char, start) {
204
+ for (let index = start; index < text.length; index += 1) {
205
+ if (text[index] === "\\") {
206
+ index += 1;
207
+ } else if (text[index] === char) {
208
+ return index;
209
+ }
210
+ }
211
+ return -1;
212
+ }
213
+ function findClosingBracket(text, start) {
214
+ let depth = 0;
215
+ for (let index = start; index < text.length; index += 1) {
216
+ switch (text[index]) {
217
+ case "\\": {
218
+ index += 1;
219
+ break;
220
+ }
221
+ case "`": {
222
+ const close = findCodeSpanEnd(text, index);
223
+ index = close === -1 ? index + backtickRun(text, index) - 1 : close;
224
+ break;
225
+ }
226
+ case "[": {
227
+ depth += 1;
228
+ break;
229
+ }
230
+ case "]": {
231
+ depth -= 1;
232
+ if (depth === 0) {
233
+ return index;
234
+ }
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ return -1;
240
+ }
241
+ function findDestinationEnd(text, start) {
242
+ let depth = 0;
243
+ for (let index = start; index < text.length; index += 1) {
244
+ const char = text[index];
245
+ let close = -1;
246
+ if (char === "\\") {
247
+ index += 1;
248
+ } else if (char === "<" && text.slice(start + 1, index).trim() === "") {
249
+ close = findUnescaped(text, ">", index + 1);
250
+ } else if ((char === '"' || char === "'") && /\s/.test(text[index - 1])) {
251
+ close = findUnescaped(text, char, index + 1);
252
+ } else if (char === "(") {
253
+ depth += 1;
254
+ } else if (char === ")" && --depth === 0) {
255
+ return index;
256
+ }
257
+ index = close === -1 ? index : close;
258
+ }
259
+ return -1;
260
+ }
261
+ function findLinkEnd(text, start) {
262
+ const close = findClosingBracket(text, start);
263
+ if (close === -1 || text[close + 1] !== "(") {
264
+ return close;
265
+ }
266
+ const end = findDestinationEnd(text, close + 1);
267
+ return end === -1 ? close : end;
268
+ }
129
269
  function tokenize(text) {
270
+ return splitWords(text, true);
271
+ }
272
+ function splitWords(text, keepLinks) {
130
273
  const tokens = [];
131
274
  let current = "";
132
275
  let index = 0;
133
276
  while (index < text.length) {
134
277
  const char = text[index];
278
+ const linkEnd = keepLinks && char === "[" && !isEscaped(text, index) ? findLinkEnd(text, index) : -1;
279
+ if (linkEnd !== -1) {
280
+ current += splitWords(text.slice(index, linkEnd + 1), false).join(" ");
281
+ index = linkEnd + 1;
282
+ continue;
283
+ }
135
284
  const close = char === "`" ? findCodeSpanEnd(text, index) : char === "{" ? findClosingBrace(text, index) : -1;
136
285
  if (char === "`" && close === -1) {
137
286
  const run = backtickRun(text, index);
@@ -227,7 +376,7 @@ function isOrphan(previous, token) {
227
376
  return opensAside(token) || endsClause(previous) && !CLAUSE_END.test(token);
228
377
  }
229
378
  function startsBlock(token) {
230
- return /^(?:[-*+>]|\d+[.)]|#{1,6})$/.test(token) || /^(?:@[A-Za-z]|`{3}|~{3}|\||<\/?[A-Za-z])/.test(token);
379
+ return /^(?:\+|-+|=+|\*+|_{3,}|\d+[.)]|#{1,6})$/.test(token) || /^(?:>|@[A-Za-z]|`{3}|~{3}|\||<(?:\/?[A-Za-z]|[!?]))/.test(token);
231
380
  }
232
381
  function bindLineStarts(tokens, startsLine2 = startsBlock) {
233
382
  const units = [];
@@ -665,6 +814,7 @@ var NAMED_TAGS = /* @__PURE__ */ new Set(["arg", "argument", "param", "prop", "p
665
814
  var LINE_DIRECTIVE = /^\s*(?:(?:eslint|oxlint|stylelint)-(?:disable(?:-next-line|-line)?|enable)\b|eslint-env\b|@ts-(?:ignore|expect-error|nocheck|check)\b|@jsx\w*\b|@vite-ignore\b|[@#]__[A-Z_]+__|@(?:vitest|jest)-environment\b|(?:istanbul|c8|v8)\s+ignore\b|prettier-ignore\b|biome-ignore\b|deno-(?:lint-ignore|fmt-ignore)(?:-file)?\b|tslint:|(?:c[sS]pell|spell-?checker)\s*:|(?:webpack|turbopack)[A-Z][a-zA-Z]*\s*:|vim?:\s|#(?:region|endregion)\b|<reference\s)/;
666
815
  var BLOCK_DIRECTIVE = /^\s*(?:eslint\s|globals?\s|exported\s|jshint\s|jslint\s|type\s*:)/;
667
816
  var LEGAL_COMMENT = /^!|@license\b|@preserve\b|\bcopyright\s+(?:\(c\)|©|\d{4})/i;
817
+ var RUST_LEGAL_COMMENT = /@license\b|@preserve\b|\bcopyright\s+(?:\(c\)|©|\d{4})/i;
668
818
  var DEFINITION = /^(?:``|[\w$.[\]]+(?:[ -][\w$.[\]]+)?)(?: [-–]|:) \S/;
669
819
  var BOX_DRAWING = /[\u2500-\u257F]/;
670
820
  var TABLE_DELIMITER = /^\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*)+\|?$/;
@@ -739,12 +889,35 @@ ${text}
739
889
  }
740
890
  return properties.length > 1 || text.trimEnd().endsWith(",") || properties.some((property) => property.type === "Property" && CODE_VALUES.has(property.value.type));
741
891
  }
742
- function isCode(texts) {
743
- const text = texts.map((line) => line.replace(LIST_MARKER, "")).join("\n");
744
- return parsesAsStatements(text) || parsesAsProperties(text);
892
+ function isCode(texts, language) {
893
+ return language.isCode(texts.map((line) => line.replace(LIST_MARKER, "")).join("\n"));
745
894
  }
746
895
  function isCodeLine(text) {
747
- return text.endsWith("{") || text.endsWith(";") && (/[()[\]{}=<>]/.test(text) || !/\s/.test(text)) || /^[}\])]/.test(text) || /^(?:const|let|var)\s+[\w$]+\s*[:=]/.test(text) || /^(?:import|export)\s.*(?:\sfrom\s|[{}])/.test(text);
896
+ return text.endsWith("{") || text.endsWith(";") && (/[()[\]{}=<>]/.test(text) || !/\s/.test(text)) || /^[}\])]/.test(text);
897
+ }
898
+ var JAVASCRIPT = {
899
+ name: "javascript",
900
+ isCode: (text) => parsesAsStatements(text) || parsesAsProperties(text),
901
+ isCodeLine: (text) => {
902
+ return isCodeLine(text) || /^(?:const|let|var)\s+[\w$]+\s*[:=]/.test(text) || /^(?:import|export)\s.*(?:\sfrom\s|[{}])/.test(text);
903
+ },
904
+ legal: LEGAL_COMMENT,
905
+ blockDirective: BLOCK_DIRECTIVE,
906
+ tags: true,
907
+ joinsCodeSpans: true
908
+ };
909
+ var RUST = {
910
+ name: "rust",
911
+ isCode: (text) => isRustCode(text),
912
+ isCodeLine: (text) => {
913
+ return isCodeLine(text) || /^let\s+(?:mut\s+)?\w+\s*[:=]/.test(text) || /^(?:pub(?:\([\w:\s]+\))?\s+)?(?:const|static(?:\s+mut)?)\s+[A-Z_\d]+\s*:/.test(text) || /^(?:pub(?:\([\w:\s]+\))?\s+)?use\s+[\w:]*::/.test(text) || /^#!?\[/.test(text);
914
+ },
915
+ legal: RUST_LEGAL_COMMENT,
916
+ tags: false,
917
+ joinsCodeSpans: false
918
+ };
919
+ function languageOf(filename) {
920
+ return filename.endsWith(".rs") ? RUST : JAVASCRIPT;
748
921
  }
749
922
  function layoutReason(prose, raw) {
750
923
  if (prose.startsWith("|") || TABLE_DELIMITER.test(prose)) {
@@ -758,8 +931,10 @@ function layoutReason(prose, raw) {
758
931
  }
759
932
  return void 0;
760
933
  }
934
+ var THEMATIC_BREAK = /^(?:(?:\*\s*){3,}|(?:-\s*){3,}|(?:_\s*){3,})$/;
761
935
  function isMarkup(prose) {
762
- return /^#{1,6}\s/.test(prose) || /^\[[^\]]+\]:/.test(prose) || /^<\/?[a-z]/i.test(prose) || /^>(?:\s|$)/.test(prose);
936
+ const text = prose.replace(LIST_MARKER, "");
937
+ return /^#{1,6}(?:\s|$)/.test(text) || /^\[[^\]]+\]:/.test(text) || /^<(?:\/?[a-z]|[!?])/i.test(text) || text.startsWith(">") || /^(?:=+|-+)$/.test(prose) || THEMATIC_BREAK.test(prose);
763
938
  }
764
939
  function mask(text) {
765
940
  return text.replaceAll(/[^\n]/g, "x");
@@ -786,11 +961,14 @@ function hasAlignedColumns(lines) {
786
961
  }
787
962
  return false;
788
963
  }
789
- function structureOf(lines) {
964
+ function structureOf(lines, language) {
790
965
  const trimmed = lines.map((line) => line.text.trim()).join("\n");
791
966
  const texts = replaceKeepingLines(trimmed, CODE_SPAN, "``").split("\n");
792
967
  const proseLines = stripNonProse(trimmed).split("\n");
793
- if (isCode(lines.map((line) => line.text.trim()))) {
968
+ if (isCode(
969
+ lines.map((line) => line.text.trim()),
970
+ language
971
+ )) {
794
972
  return { tier: "code", reason: "code" };
795
973
  }
796
974
  let layout;
@@ -799,7 +977,7 @@ function structureOf(lines) {
799
977
  for (const [position, line] of texts.entries()) {
800
978
  const prose = proseLines[position].trim();
801
979
  layout ??= layoutReason(prose, _nullishCoalesce(_optionalChain([lines, 'access', _6 => _6[position], 'optionalAccess', _7 => _7.text]), () => ( "")));
802
- if (isCodeLine(line.trim())) {
980
+ if (language.isCodeLine(line.trim())) {
803
981
  codeLike = true;
804
982
  } else if (isMarkup(prose)) {
805
983
  manual ??= "markup";
@@ -838,7 +1016,7 @@ var FenceTracker = class {
838
1016
  return true;
839
1017
  }
840
1018
  };
841
- function segment(lines) {
1019
+ function segment(lines, language) {
842
1020
  const paragraphs = [];
843
1021
  const fence = new FenceTracker();
844
1022
  let current;
@@ -875,6 +1053,10 @@ function segment(lines) {
875
1053
  continue;
876
1054
  }
877
1055
  if (line.verbatim) {
1056
+ if (fence.next(text)) {
1057
+ start("code", line, "", "code block");
1058
+ continue;
1059
+ }
878
1060
  start("code", line, "", "directive");
879
1061
  flush();
880
1062
  continue;
@@ -899,7 +1081,7 @@ function segment(lines) {
899
1081
  }
900
1082
  continue;
901
1083
  }
902
- const tag = JSDOC_TAG.exec(text);
1084
+ const tag = language.tags ? JSDOC_TAG.exec(text) : null;
903
1085
  if (tag) {
904
1086
  start("tag", line, "", PROSE_TAGS.has(tag[1]) ? void 0 : `@${tag[1]} tag`);
905
1087
  inCodeTag = CODE_TAGS.has(tag[1]);
@@ -1006,7 +1188,19 @@ function byLine(issues) {
1006
1188
  function sameLayout(a, b) {
1007
1189
  return a.length === b.length && a.every((words, position) => words.join(" ") === b[position].join(" "));
1008
1190
  }
1009
- function analyzeParagraph(paragraph, options) {
1191
+ function splitTokenLine(lines, tokens) {
1192
+ let next = 0;
1193
+ return lines.findIndex((words) => {
1194
+ const line = words.join(" ");
1195
+ let text = "";
1196
+ while (text.length < line.length && next < tokens.length) {
1197
+ text += `${text ? " " : ""}${tokens[next]}`;
1198
+ next += 1;
1199
+ }
1200
+ return text !== line;
1201
+ });
1202
+ }
1203
+ function analyzeParagraph(paragraph, options, language) {
1010
1204
  const first = paragraph.lines[0];
1011
1205
  const last = _nullishCoalesce(paragraph.lines.at(-1), () => ( first));
1012
1206
  const base = { kind: paragraph.kind, startLine: first.index + 1, endLine: last.index + 1 };
@@ -1033,7 +1227,8 @@ function analyzeParagraph(paragraph, options) {
1033
1227
  const structure2 = structureOf(
1034
1228
  prose.lines.map((line) => {
1035
1229
  return { ...line, text: stripBlockSyntax(line.text) };
1036
- })
1230
+ }),
1231
+ language
1037
1232
  );
1038
1233
  const issues2 = keptIssues(prose, structure2, options);
1039
1234
  return { ...base, reflowable: false, reason: paragraph.reason, issues: issues2, suggestion: null };
@@ -1042,7 +1237,7 @@ function analyzeParagraph(paragraph, options) {
1042
1237
  const issues2 = keptIssues(paragraph, structure2, options);
1043
1238
  return { ...base, reflowable: false, reason: structure2.reason, issues: issues2, suggestion: null };
1044
1239
  };
1045
- const structure = structureOf(paragraph.lines);
1240
+ const structure = structureOf(paragraph.lines, language);
1046
1241
  if (structure) {
1047
1242
  return keep(structure);
1048
1243
  }
@@ -1074,10 +1269,14 @@ function analyzeParagraph(paragraph, options) {
1074
1269
  if (!units.every((unit) => staysInLimit(unit))) {
1075
1270
  return keep({ tier: "manual", reason: "unbreakable text across lines" });
1076
1271
  }
1272
+ const text = currentLines.map((words) => words.join(" ")).join("\n");
1273
+ if (!language.joinsCodeSpans && [...text.matchAll(CODE_SPAN)].some((span) => span[0].includes("\n"))) {
1274
+ return keep({ tier: "manual", reason: "code span across lines" });
1275
+ }
1077
1276
  const bodyOf = (lines) => {
1078
1277
  return lines.map((words, position) => {
1079
- const text = `${position === 0 ? paragraph.marker : hang}${words.join(" ")}`;
1080
- return { ...first, text, verbatim: false, hardBreak: HARD_BREAK.test(text) };
1278
+ const text2 = `${position === 0 ? paragraph.marker : hang}${words.join(" ")}`;
1279
+ return { ...first, text: text2, verbatim: false, hardBreak: HARD_BREAK.test(text2) };
1081
1280
  });
1082
1281
  };
1083
1282
  const settle = (settings, extra = 0) => {
@@ -1085,11 +1284,11 @@ function analyzeParagraph(paragraph, options) {
1085
1284
  let { even: even2, wrapped: wrapped2 } = layOut(current, settings, extra);
1086
1285
  for (; ; ) {
1087
1286
  const body = bodyOf(wrapped2);
1088
- const rewrap = structureOf(body);
1287
+ const rewrap = structureOf(body, language);
1089
1288
  if (!rewrap) {
1090
1289
  return { even: even2, wrapped: wrapped2 };
1091
1290
  }
1092
- const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1)) !== void 0);
1291
+ const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1), language) !== void 0);
1093
1292
  const start = wrapped2.slice(0, Math.max(tipping, 1)).flat().length;
1094
1293
  const pair = `${current[start - 1]} ${current[start]}`;
1095
1294
  if (body.length < 2 || !staysInLimit(pair)) {
@@ -1115,6 +1314,7 @@ function analyzeParagraph(paragraph, options) {
1115
1314
  }
1116
1315
  const { even, wrapped } = layout;
1117
1316
  const matchesLayout = sameLayout(wrapped, currentLines);
1317
+ const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
1118
1318
  const lineIssues = [];
1119
1319
  const overflow = options.maxWidth === void 0 ? [] : overflowIssues(paragraph.lines, options);
1120
1320
  if (!matchesLayout) {
@@ -1173,8 +1373,11 @@ function analyzeParagraph(paragraph, options) {
1173
1373
  lineIssues.push({ type: "unbalanced", line });
1174
1374
  }
1175
1375
  }
1376
+ const splitLine = splitTokenLine(currentLines, tokens);
1377
+ if (splitLine !== -1 && overflow.length === 0 && !lineIssues.some((issue) => isVerdict(issue))) {
1378
+ lineIssues.push({ type: "unbalanced", line: splitLine });
1379
+ }
1176
1380
  }
1177
- const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
1178
1381
  const matchesWiderLayout = () => {
1179
1382
  for (let extra = 1; extra <= layoutOptions.tolerance; extra += 1) {
1180
1383
  const wider = layoutAt(extra);
@@ -1184,7 +1387,20 @@ function analyzeParagraph(paragraph, options) {
1184
1387
  }
1185
1388
  return false;
1186
1389
  };
1187
- const accepted = lineIssues.some((issue) => isVerdict(issue)) && matchesWiderLayout();
1390
+ const staysNearLayout = () => {
1391
+ if (currentLines.length !== wrapped.length || splitTokenLine(currentLines, units) !== -1) {
1392
+ return false;
1393
+ }
1394
+ return currentLines.every((words, position) => {
1395
+ const length = words.join(" ").length;
1396
+ const suggestedLength = wrapped[position].join(" ").length;
1397
+ const limit = (position === 0 ? firstLimit : restLimit) + layoutOptions.tolerance;
1398
+ return Math.abs(length - suggestedLength) <= layoutOptions.tolerance && length <= Math.max(suggestedLength, limit);
1399
+ });
1400
+ };
1401
+ const hasVerdict = lineIssues.some((issue) => isVerdict(issue));
1402
+ const onlyUnbalanced = lineIssues.every((issue) => issue.type === "unbalanced");
1403
+ const accepted = hasVerdict && (matchesWiderLayout() || onlyUnbalanced && staysNearLayout());
1188
1404
  const suggested = accepted ? currentLines : wrapped;
1189
1405
  const suggestion = suggested.map(
1190
1406
  (words, position) => `${prefix}${position === 0 ? paragraph.marker : hang}${words.join(" ")}`
@@ -1193,6 +1409,18 @@ function analyzeParagraph(paragraph, options) {
1193
1409
  const issues = byLine([...overflow, ...wrapIssues, ...styleIssues(paragraph, options)]);
1194
1410
  return { ...base, reflowable: true, issues, suggestion };
1195
1411
  }
1412
+ var LINE_COMMENT = "line comment";
1413
+ function analyzeLineParagraph(paragraph, options, language) {
1414
+ const first = paragraph.lines[0];
1415
+ const last = _nullishCoalesce(paragraph.lines.at(-1), () => ( first));
1416
+ const base = { kind: paragraph.kind, startLine: first.index + 1, endLine: last.index + 1 };
1417
+ if (paragraph.kind === "code") {
1418
+ return { ...base, reflowable: false, reason: paragraph.reason, issues: [], suggestion: null };
1419
+ }
1420
+ const tier = _optionalChain([structureOf, 'call', _41 => _41(paragraph.lines, language), 'optionalAccess', _42 => _42.tier]);
1421
+ const issues = tier === "code" || tier === "layout" ? [] : overflowIssues(paragraph.lines, options);
1422
+ return { ...base, reflowable: false, reason: LINE_COMMENT, issues, suggestion: null };
1423
+ }
1196
1424
  function computeLineStarts(source) {
1197
1425
  const starts = [0];
1198
1426
  for (let index = 0; index < source.length; index += 1) {
@@ -1218,31 +1446,42 @@ function lineOf(lineStarts, offset) {
1218
1446
  function stripCarriageReturn(line) {
1219
1447
  return line.endsWith("\r") ? line.slice(0, -1) : line;
1220
1448
  }
1221
- function shapeBlock(comment, sourceLines, lineStarts) {
1449
+ function blockKind(comment, language) {
1450
+ if (language.name === "rust") {
1451
+ return comment.doc ? `${comment.doc}-block-doc` : "block";
1452
+ }
1453
+ return comment.value.startsWith("*") ? "jsdoc" : "block";
1454
+ }
1455
+ function wholeComment(shape, lines, paragraphKind, reason) {
1456
+ const otherLines = lines.map((text, offset) => {
1457
+ return { index: shape.startLine + offset, prefix: "", text, verbatim: false, hardBreak: false };
1458
+ });
1459
+ return { ...shape, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
1460
+ }
1461
+ function isLegal(value, language) {
1462
+ return language.legal.test(replaceKeepingLines(value, CODE_SPAN, "``"));
1463
+ }
1464
+ function shapeBlock(comment, sourceLines, lineStarts, language) {
1222
1465
  const startLine = lineOf(lineStarts, comment.start);
1223
1466
  const endLine = lineOf(lineStarts, comment.end - 1);
1224
- const kind = comment.value.startsWith("*") ? "jsdoc" : "block";
1467
+ const shape = { kind: blockKind(comment, language), startLine, endLine };
1225
1468
  const lines = sourceLines.slice(startLine, endLine + 1);
1226
- const wholeComment = (paragraphKind, reason) => {
1227
- const otherLines = lines.map((text, offset) => {
1228
- return { index: startLine + offset, prefix: "", text, verbatim: false, hardBreak: false };
1229
- });
1230
- return { kind, startLine, endLine, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
1231
- };
1232
- const value = comment.value.replace(/^\*/, "");
1233
- if (LEGAL_COMMENT.test(replaceKeepingLines(comment.value, CODE_SPAN, "``"))) {
1234
- return wholeComment("code", "legal comment");
1469
+ const value = comment.doc ? comment.value.slice(1) : comment.value.replace(/^\*/, "");
1470
+ if (isLegal(comment.value, language)) {
1471
+ return wholeComment(shape, lines, "code", "legal comment");
1235
1472
  }
1236
- if (LINE_DIRECTIVE.test(value) || BLOCK_DIRECTIVE.test(value)) {
1237
- return wholeComment("code", "directive");
1473
+ if (LINE_DIRECTIVE.test(value) || _optionalChain([language, 'access', _43 => _43.blockDirective, 'optionalAccess', _44 => _44.test, 'call', _45 => _45(value)])) {
1474
+ return wholeComment(shape, lines, "code", "directive");
1238
1475
  }
1239
1476
  const other = () => {
1240
1477
  return wholeComment(
1478
+ shape,
1479
+ lines,
1241
1480
  "other",
1242
1481
  startLine === endLine ? "single-line block comment" : "unsupported block comment layout"
1243
1482
  );
1244
1483
  };
1245
- if (startLine === endLine || !/^\s*\/\*\*?$/.test(lines[0]) || !/^\s*\*\/$/.test(_nullishCoalesce(lines.at(-1), () => ( "")))) {
1484
+ if (startLine === endLine || !/^\s*\/\*[*!]?$/.test(lines[0]) || !/^\s*\*\/$/.test(_nullishCoalesce(lines.at(-1), () => ( "")))) {
1246
1485
  return other();
1247
1486
  }
1248
1487
  const body = [];
@@ -1261,7 +1500,52 @@ function shapeBlock(comment, sourceLines, lineStarts) {
1261
1500
  hardBreak: HARD_BREAK.test(text)
1262
1501
  });
1263
1502
  }
1264
- return { kind, startLine, endLine, paragraphs: segment(body) };
1503
+ return { ...shape, paragraphs: segment(body, language) };
1504
+ }
1505
+ function shapeLineRun(group, sourceLines, lineStarts, language) {
1506
+ const startLine = lineOf(lineStarts, group[0].start);
1507
+ const endLine = startLine + group.length - 1;
1508
+ const kind = group[0].doc === "inner" ? "inner-line-doc" : "outer-line-doc";
1509
+ const shape = { kind, startLine, endLine };
1510
+ const lines = sourceLines.slice(startLine, endLine + 1);
1511
+ const values = group.map((comment) => comment.value.slice(1));
1512
+ if (isLegal(values.join("\n"), language)) {
1513
+ return wholeComment(shape, lines, "code", "legal comment");
1514
+ }
1515
+ if (LINE_DIRECTIVE.test(values[0])) {
1516
+ return wholeComment(shape, lines, "code", "directive");
1517
+ }
1518
+ const body = lines.map((line, offset) => {
1519
+ const [, marker, space, text] = _nullishCoalesce(/^(\s*\/\/[/!])( ?)(.*)$/.exec(line), () => ( ["", "", "", line]));
1520
+ return {
1521
+ index: startLine + offset,
1522
+ prefix: `${marker} `,
1523
+ text: text.trimEnd(),
1524
+ verbatim: !space && text.length > 0 || LINE_DIRECTIVE.test(text),
1525
+ hardBreak: HARD_BREAK.test(text)
1526
+ };
1527
+ });
1528
+ return { ...shape, paragraphs: segment(body, language) };
1529
+ }
1530
+ function shapeLineComment(group, sourceLines, lineStarts, language) {
1531
+ const startLine = lineOf(lineStarts, group[0].start);
1532
+ const endLine = startLine + group.length - 1;
1533
+ const shape = { kind: "line", startLine, endLine };
1534
+ const lines = sourceLines.slice(startLine, endLine + 1);
1535
+ if (isLegal(group.map((comment) => comment.value).join("\n"), language)) {
1536
+ return wholeComment(shape, lines, "code", "legal comment");
1537
+ }
1538
+ const body = lines.map((line, offset) => {
1539
+ const [, marker, space, text] = _nullishCoalesce(/^(\s*\/\/[/!]*)( ?)(.*)$/.exec(line), () => ( ["", "", "", line]));
1540
+ return {
1541
+ index: startLine + offset,
1542
+ prefix: `${marker}${space}`,
1543
+ text: text.trimEnd(),
1544
+ verbatim: LINE_DIRECTIVE.test(text),
1545
+ hardBreak: HARD_BREAK.test(text)
1546
+ };
1547
+ });
1548
+ return { ...shape, paragraphs: segment(body, { ...language, tags: false }) };
1265
1549
  }
1266
1550
  function ownsLines(comment, source, lineStarts) {
1267
1551
  const startLine = lineOf(lineStarts, comment.start);
@@ -1271,12 +1555,12 @@ function ownsLines(comment, source, lineStarts) {
1271
1555
  const after = source.slice(comment.end, lineEnd);
1272
1556
  return /^[\t ]*$/.test(before) && /^[\t ]*\r?\n?$/.test(after);
1273
1557
  }
1274
- function singleLineLength(paragraph, options) {
1558
+ function singleLineLength(paragraph, options, language) {
1275
1559
  const [first] = paragraph.lines;
1276
1560
  if (paragraph.kind === "other" && paragraph.reason === "single-line block comment") {
1277
1561
  return sourceLineLength(first, options.tabWidth);
1278
1562
  }
1279
- if (paragraph.kind !== "prose" || structureOf(paragraph.lines)) {
1563
+ if (paragraph.kind !== "prose" || structureOf(paragraph.lines, language)) {
1280
1564
  return Infinity;
1281
1565
  }
1282
1566
  const text = tokenize(paragraph.lines.map((line) => line.text).join(" ")).join(" ");
@@ -1288,9 +1572,61 @@ function parseSource(filename, source) {
1288
1572
  }
1289
1573
  function describeFailure(errors, lineStarts) {
1290
1574
  const [first] = errors;
1291
- const offset = _optionalChain([first, 'access', _41 => _41.labels, 'access', _42 => _42.at, 'call', _43 => _43(0), 'optionalAccess', _44 => _44.start]);
1575
+ const offset = _optionalChain([first, 'access', _46 => _46.labels, 'access', _47 => _47.at, 'call', _48 => _48(0), 'optionalAccess', _49 => _49.start]);
1292
1576
  return { message: first.message, line: offset === void 0 ? void 0 : lineOf(lineStarts, offset) + 1 };
1293
1577
  }
1578
+ function scanJavaScript(filename, text, lineStarts) {
1579
+ const { program, comments, errors } = parseSource(filename, text);
1580
+ if (errors.length > 0 && program.body.length === 0) {
1581
+ return { comments: [], syntaxError: describeFailure(errors, lineStarts) };
1582
+ }
1583
+ return {
1584
+ comments: comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")))
1585
+ };
1586
+ }
1587
+ function scanRustSource(text, lineStarts) {
1588
+ const { comments, error } = scanRust(text);
1589
+ if (error) {
1590
+ return { comments: [], syntaxError: { message: error.message, line: lineOf(lineStarts, error.offset) + 1 } };
1591
+ }
1592
+ return {
1593
+ comments: comments.map((comment) => {
1594
+ return {
1595
+ type: comment.block ? "Block" : "Line",
1596
+ value: text.slice(comment.start + 2, comment.block ? comment.end - 2 : comment.end),
1597
+ start: comment.start,
1598
+ end: comment.end,
1599
+ doc: comment.doc
1600
+ };
1601
+ })
1602
+ };
1603
+ }
1604
+ function groupLineRuns(comments, text, lineStarts, markerOf) {
1605
+ const groups = [];
1606
+ let current;
1607
+ let previousLine = -1;
1608
+ let indent = "";
1609
+ let marker;
1610
+ for (const comment of comments) {
1611
+ const next = comment.type === "Line" ? markerOf(comment) : void 0;
1612
+ if (next === void 0 || !ownsLines(comment, text, lineStarts)) {
1613
+ current = void 0;
1614
+ continue;
1615
+ }
1616
+ const line = lineOf(lineStarts, comment.start);
1617
+ const before = text.slice(lineStarts[line], comment.start);
1618
+ if (current && line === previousLine + 1 && next === marker && before === indent) {
1619
+ current.push(comment);
1620
+ } else {
1621
+ current = [comment];
1622
+ indent = before;
1623
+ marker = next;
1624
+ groups.push(current);
1625
+ }
1626
+ previousLine = line;
1627
+ }
1628
+ return groups;
1629
+ }
1294
1630
  function analyzeSource(source, filename, options) {
1295
1631
  return inspectSource(source, filename, options).comments;
1296
1632
  }
@@ -1299,39 +1635,66 @@ function inspectSource(source, filename, options) {
1299
1635
  throw new Error("comments: maxWidth must be an integer at least as large as width");
1300
1636
  }
1301
1637
  const text = source.startsWith(BYTE_ORDER_MARK) ? source.slice(1) : source;
1302
- const { program, comments, errors } = parseSource(filename, text);
1638
+ const language = languageOf(filename);
1303
1639
  const lineStarts = computeLineStarts(text);
1304
- if (errors.length > 0 && program.body.length === 0) {
1305
- return { comments: [], syntaxError: describeFailure(errors, lineStarts) };
1640
+ const scanned = language.name === "rust" ? scanRustSource(text, lineStarts) : scanJavaScript(filename, text, lineStarts);
1641
+ if (scanned.syntaxError) {
1642
+ return { comments: [], syntaxError: scanned.syntaxError };
1306
1643
  }
1307
1644
  const sourceLines = text.split("\n").map((line) => stripCarriageReturn(line));
1308
- const parsed = comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")));
1309
1645
  const reports = [];
1310
- const blocks = parsed.filter((comment) => {
1311
- return comment.type === "Block" && ownsLines(comment, text, lineStarts) && !comment.value.includes(IGNORE_DIRECTIVE);
1312
- });
1313
- for (const block of blocks) {
1314
- const shape = shapeBlock(block, sourceLines, lineStarts);
1315
- const singleLineWidth = Math.max(
1316
- options.width,
1317
- Math.min(_nullishCoalesce(options.singleLineWidth, () => ( options.width)), _nullishCoalesce(options.maxWidth, () => ( Infinity)))
1318
- );
1319
- const singleLineLimit = Math.min(singleLineWidth + options.tolerance, _nullishCoalesce(options.maxWidth, () => ( Infinity)));
1320
- const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options) : Infinity;
1321
- const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
1322
- const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
1323
- reports.push({
1646
+ const report = (shape, lines, paragraphs) => {
1647
+ return {
1324
1648
  file: filename,
1325
1649
  kind: shape.kind,
1326
1650
  startLine: shape.startLine + 1,
1327
1651
  endLine: shape.endLine + 1,
1328
- indent: _nullishCoalesce(_optionalChain([/^[\t ]*/, 'access', _45 => _45.exec, 'call', _46 => _46(lines[0]), 'optionalAccess', _47 => _47[0]]), () => ( "")),
1652
+ indent: _nullishCoalesce(_optionalChain([/^[\t ]*/, 'access', _50 => _50.exec, 'call', _51 => _51(lines[0]), 'optionalAccess', _52 => _52[0]]), () => ( "")),
1329
1653
  width: options.width,
1330
1654
  maxWidth: options.maxWidth,
1331
1655
  lengths: lines.map((line) => visualLength(line.trimEnd(), options.tabWidth)),
1332
1656
  text: lines.join("\n"),
1333
- paragraphs: shape.paragraphs.map((paragraph) => analyzeParagraph(paragraph, paragraphOptions))
1657
+ paragraphs
1658
+ };
1659
+ };
1660
+ const blocks = scanned.comments.filter((comment) => {
1661
+ return comment.type === "Block" && ownsLines(comment, text, lineStarts) && !comment.value.includes(IGNORE_DIRECTIVE);
1662
+ });
1663
+ const runs = (markerOf) => {
1664
+ return groupLineRuns(scanned.comments, text, lineStarts, markerOf).filter((group) => {
1665
+ return !group.some((comment) => comment.value.includes(IGNORE_DIRECTIVE));
1334
1666
  });
1667
+ };
1668
+ const shapes = [
1669
+ ...blocks.map((block) => shapeBlock(block, sourceLines, lineStarts, language)),
1670
+ ...runs((comment) => comment.doc).map((group) => shapeLineRun(group, sourceLines, lineStarts, language)),
1671
+ ...runs((comment) => comment.doc ? void 0 : "//").map(
1672
+ (group) => shapeLineComment(group, sourceLines, lineStarts, language)
1673
+ )
1674
+ ].toSorted((a, b) => a.startLine - b.startLine);
1675
+ for (const shape of shapes) {
1676
+ const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
1677
+ if (shape.kind === "line") {
1678
+ const paragraphs = shape.paragraphs.map((paragraph) => analyzeLineParagraph(paragraph, options, language));
1679
+ if (paragraphs.some((paragraph) => paragraph.issues.length > 0)) {
1680
+ reports.push(report(shape, lines, paragraphs));
1681
+ }
1682
+ continue;
1683
+ }
1684
+ const singleLineWidth = Math.max(
1685
+ options.width,
1686
+ Math.min(_nullishCoalesce(options.singleLineWidth, () => ( options.width)), _nullishCoalesce(options.maxWidth, () => ( Infinity)))
1687
+ );
1688
+ const singleLineLimit = Math.min(singleLineWidth + options.tolerance, _nullishCoalesce(options.maxWidth, () => ( Infinity)));
1689
+ const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options, language) : Infinity;
1690
+ const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
1691
+ reports.push(
1692
+ report(
1693
+ shape,
1694
+ lines,
1695
+ shape.paragraphs.map((paragraph) => analyzeParagraph(paragraph, paragraphOptions, language))
1696
+ )
1697
+ );
1335
1698
  }
1336
1699
  return { comments: reports };
1337
1700
  }
@@ -1352,6 +1715,9 @@ function rewrapComment(report) {
1352
1715
  }
1353
1716
  var POSITION_KEYS = /* @__PURE__ */ new Set(["start", "end", "range", "loc"]);
1354
1717
  function codeFingerprint(source, filename) {
1718
+ if (languageOf(filename).name === "rust") {
1719
+ return rustFingerprint(source);
1720
+ }
1355
1721
  const { program, errors } = parseSource(filename, source);
1356
1722
  if (errors.length > 0) {
1357
1723
  return JSON.stringify({ source, errors: errors.map((error) => error.message) });
@@ -1364,8 +1730,8 @@ function codeFingerprint(source, filename) {
1364
1730
 
1365
1731
  // src/comments/agent.ts
1366
1732
  var _child_process = require('child_process');
1367
- var _fs = require('fs');
1368
- var _path = require('path'); var _path2 = _interopRequireDefault(_path);
1733
+
1734
+
1369
1735
  var DEFAULT_RULES = `
1370
1736
  # Comment rules
1371
1737
 
@@ -1373,7 +1739,7 @@ var DEFAULT_RULES = `
1373
1739
 
1374
1740
  When rules pull in different directions, the earlier one wins.
1375
1741
 
1376
- 1. Keep code and exact text byte for byte, including every punctuation character in it. That covers code blocks, \`@example\`, code spans, identifiers, string literals like error messages, regexes, globs and other patterns like \`*test*\`, URLs, quoted text, titles, and notation like \`host:port\`.
1742
+ 1. Keep code and exact text byte for byte, including every punctuation character in it. That covers code blocks, \`@example\`, code spans, identifiers, string literals like error messages, regexes, globs and other patterns like \`*test*\`, URLs, quoted text, titles, notation like \`host:port\`, and Rust intra-doc links (see below).
1377
1743
  2. Keep the meaning. An edit may make a comment clearer, never different.
1378
1744
  3. Follow the style preferences below.
1379
1745
  4. Change as little as possible. Wording that already reads clearly stays, even when a note points at it.
@@ -1386,10 +1752,12 @@ When rules pull in different directions, the earlier one wins.
1386
1752
  - 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.
1387
1753
  - Separate paragraphs with an empty comment line.
1388
1754
  - Never split a backtick code span across lines.
1755
+ - Never split a Markdown link across lines. A link is the text in brackets and the target right after it, like \`[the docs](https://example.com)\`, \`[the docs][label]\`, or \`[the docs]\`, and all of it stays on one line, from its first bracket to its last bracket or parenthesis. An editor can't follow a link that continues on the next line. When a line is too long, break it before or after the link.
1389
1756
  - Keep code blocks, \`@example\`, lists, tables, box drawings, aligned columns, and Markdown hard line breaks (a line that ends with two spaces or a backslash) laid out as they are. List items wrap with their continuation lines indented to the item text, and a JSDoc tag description that doesn't fit continues on the next line without indentation.
1390
1757
  - The tool measures every line, taking the tolerance and the parts it keeps as written into account, and the notes on each comment list what is still wrong. Act on those notes. Don't count columns, and don't apply a stricter limit than the notes show.
1391
1758
  - The maximum is a hard limit for measured prose, including long tokens. If a URL, code span, or other exact text exceeds it, preserve that text and explain the unresolved overflow instead of changing its contents.
1392
1759
  - The tool never rewraps the parts it keeps as written, like a single-line block comment, a paragraph with hard line breaks, or lines that may be laid out by hand. When a note says one of their prose lines is too long, break or shorten it yourself. Exact text and intentional layout win over the width, so keep code, code spans, and the layout in them intact, and if a note can only be fixed by changing them, leave it and say why in your reply.
1760
+ - The tool never rewraps a line comment (\`//\`) either. When a note says one of its lines is too long, shorten the wording so the comment fits on its line and still says the same thing. When it can't be shortened without losing meaning, turn it into a multi-line block comment instead: \`/**\` in JavaScript and TypeScript, or \`/*\` in Rust, on its own line, then the text on lines that start with \` * \`, then \` */\` on its own line, all at the indentation of the line comment. Like other block comments, it starts with a capital letter and ends with punctuation. Leave its line breaks to the tool, which rewraps it like any other block comment. Never split a line comment into more \`//\` lines.
1393
1761
 
1394
1762
  ## Style
1395
1763
 
@@ -1400,8 +1768,16 @@ These are preferences for comment prose. They never apply to code or exact text
1400
1768
  - Prefer a sentence over a "Label: explanation" fragment, like \`...the socket. Optional: without it...\` or \`Default: refuse\` in the middle of prose. A colon is fine where it introduces a list, code block, or example, in a term's definition, in notation, in a quotation, and in a \`Note:\` or \`Optional:\` that starts a comment. A JSDoc tag like \`@note\` or \`@todo\` can replace a label, and \`@default\` fits a default value, not a description.
1401
1769
  - Avoid asterisk emphasis like \`*this*\`, and use plain wording instead. A name or pattern in asterisks, like \`*test*\` or \`*.spec.ts\`, is a pattern, so keep its asterisks and wrap it in a code span, like \`\` \`*test*\` \`\`.
1402
1770
  - Avoid section banner comments, i.e. dividers or headings made of dashes, equals signs, asterisks, or other repeated characters that split a file into visual sections. Let the code structure carry the grouping, or write one short, plain sentence comment (e.g. \`// handlers for control mode\`). Rules that belong to a diagram, a table, a box drawing, a Markdown document, an example, or a legal header aren't banners.
1403
- - Block and JSDoc comments are capitalized and end with punctuation.
1404
- - Never turn a block or JSDoc comment into \`//\` line comments, and never write a run of consecutive \`//\` lines.
1771
+ - Block, JSDoc, and Rust doc comments are capitalized and end with punctuation.
1772
+ - Never change the kind of a comment, except to turn a line comment that is too long into a block comment (see Layout). Don't turn a block or JSDoc comment into \`//\` line comments, and never write a run of consecutive \`//\` lines. In Rust, a run of \`///\` or \`//!\` lines is one doc comment, so keep its markers, and never turn a doc comment (\`///\`, \`//!\`, \`/**\`, \`/*!\`) into another kind, since that changes or removes documentation.
1773
+
1774
+ ## Rust doc comments
1775
+
1776
+ Rust doc comments are Markdown that rustdoc renders.
1777
+
1778
+ - Intra-doc links are exact text. Keep their brackets, backticks, and targets, like \`\` [\`count_provider_reads\`] \`\`, \`\` [\`permissions\`](crate::permissions) \`\`, \`\` [the parser][\`Parser\`] \`\`, or \`[Vec<T>]\`, since rustdoc resolves the text in brackets, or the target after it, as a path. Each link stays on one line (see Layout), including a link with spaces in its text, like \`[the provider module](crate::provider)\`.
1779
+ - A link reference definition, like \`\` [\`Foo\`]: crate::Foo \`\`, stays on its own line as it is. Its colon is syntax, not a label.
1780
+ - Headings like \`# Examples\`, \`# Panics\`, \`# Errors\`, and \`# Safety\` are sections that rustdoc and readers look for, so keep them. Code blocks are doctests, so keep their lines, including hidden lines that start with \`#\`, exactly as they are.
1405
1781
 
1406
1782
  ## Editing
1407
1783
 
@@ -1411,10 +1787,10 @@ These are preferences for comment prose. They never apply to code or exact text
1411
1787
 
1412
1788
  // src/comments/config.ts
1413
1789
 
1414
- var _module = require('module');
1415
1790
 
1416
- var _url = require('url');
1417
- var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"]);
1791
+
1792
+
1793
+ var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".rs"]);
1418
1794
  var CONFIG_FILES = [
1419
1795
  "comments.config.ts",
1420
1796
  "comments.config.mts",
@@ -1465,10 +1841,12 @@ function validateConfig(raw, filepath) {
1465
1841
  assertType(config.avoidSentenceOrphans, "boolean", "avoidSentenceOrphans", filepath);
1466
1842
  assertType(config.style, "boolean", "style", filepath);
1467
1843
  assertType(config.extensions, "array", "extensions", filepath);
1844
+ assertType(config.include, "array", "include", filepath);
1468
1845
  assertType(config.ignore, "array", "ignore", filepath);
1469
1846
  assertType(config.agent, "object", "agent", filepath);
1470
1847
  assertType(config.hook, "object", "hook", filepath);
1471
1848
  assertStrings(config.extensions, "extensions", filepath);
1849
+ assertStrings(config.include, "include", filepath);
1472
1850
  assertStrings(config.ignore, "ignore", filepath);
1473
1851
  for (const key of ["width", "maxWidth", "singleLineWidth", "tolerance"]) {
1474
1852
  const value = config[key];
@@ -1501,8 +1879,8 @@ function validateConfig(raw, filepath) {
1501
1879
  }
1502
1880
  assertType(agent.rules, "string", "agent.rules", filepath);
1503
1881
  assertType(agent.instructions, "string", "agent.instructions", filepath);
1504
- if (agent.scope !== void 0 && agent.scope !== "suspicious" && agent.scope !== "all") {
1505
- throw new Error(`comments: \`agent.scope\` in ${filepath} must be "suspicious" or "all"`);
1882
+ if (agent.scope !== void 0 && !["suspicious", "failing", "all"].includes(agent.scope)) {
1883
+ throw new Error(`comments: \`agent.scope\` in ${filepath} must be one of suspicious, failing, all`);
1506
1884
  }
1507
1885
  const hook = _nullishCoalesce(config.hook, () => ( {}));
1508
1886
  if (hook.mode !== void 0 && !["agent", "report", "off"].includes(hook.mode)) {