@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/README.md +37 -10
- package/dist/bin/comments.js +542 -114
- package/dist/bin/comments.js.map +1 -1
- package/dist/comments.cjs +447 -69
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.d.cts +51 -25
- package/dist/comments.d.ts +51 -25
- package/dist/comments.js +449 -73
- package/dist/comments.js.map +1 -1
- package/native/comments.darwin-arm64.node +0 -0
- package/native/comments.darwin-x64.node +0 -0
- package/native/comments.linux-arm64-gnu.node +0 -0
- package/native/comments.linux-x64-gnu.node +0 -0
- package/package.json +9 -6
package/dist/comments.js
CHANGED
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
// src/comments/analyze.ts
|
|
2
2
|
import { parseSync } from "oxc-parser";
|
|
3
3
|
|
|
4
|
+
// src/comments/rust.ts
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
var BINARY_NAME = "comments";
|
|
10
|
+
function platformSuffixes() {
|
|
11
|
+
const { platform, arch } = process;
|
|
12
|
+
switch (platform) {
|
|
13
|
+
case "darwin": {
|
|
14
|
+
return [`darwin-${arch}`, "darwin-universal"];
|
|
15
|
+
}
|
|
16
|
+
case "linux": {
|
|
17
|
+
return arch === "arm" ? ["linux-arm-gnueabihf", "linux-arm-musleabihf"] : [`linux-${arch}-gnu`, `linux-${arch}-musl`];
|
|
18
|
+
}
|
|
19
|
+
case "win32": {
|
|
20
|
+
return [`win32-${arch}-msvc`, `win32-${arch}-gnu`];
|
|
21
|
+
}
|
|
22
|
+
default: {
|
|
23
|
+
return [`${platform}-${arch}`];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function findBinaries() {
|
|
28
|
+
const names = platformSuffixes().map((suffix) => `${BINARY_NAME}.${suffix}.node`);
|
|
29
|
+
for (let directory = path.dirname(fileURLToPath(import.meta.url)); ; directory = path.dirname(directory)) {
|
|
30
|
+
const found = names.map((name) => path.join(directory, "native", name)).filter((file) => existsSync(file));
|
|
31
|
+
if (found.length > 0) {
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
if (path.dirname(directory) === directory) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var binding;
|
|
40
|
+
function loadBinding() {
|
|
41
|
+
if (binding) {
|
|
42
|
+
return binding;
|
|
43
|
+
}
|
|
44
|
+
const require2 = createRequire(import.meta.url);
|
|
45
|
+
const errors = [];
|
|
46
|
+
for (const file of findBinaries()) {
|
|
47
|
+
try {
|
|
48
|
+
binding = require2(file);
|
|
49
|
+
return binding;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
errors.push(`${file}: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const reason = errors.length > 0 ? `, and loading it failed (${errors.join("; ")})` : "";
|
|
55
|
+
throw new Error(
|
|
56
|
+
`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\`.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function scanRust(source) {
|
|
60
|
+
return loadBinding().scanRust(source);
|
|
61
|
+
}
|
|
62
|
+
function rustFingerprint(source) {
|
|
63
|
+
return loadBinding().rustFingerprint(source);
|
|
64
|
+
}
|
|
65
|
+
function isRustCode(text) {
|
|
66
|
+
return loadBinding().isRustCode(text);
|
|
67
|
+
}
|
|
68
|
+
|
|
4
69
|
// src/comments/style.ts
|
|
5
70
|
var BANNER = /^(?!#{1,6}\s)([-=*#_~+/\\])\1{3,}|([-=*#_~])\2{3,}\s*$/;
|
|
6
71
|
var COLUMN_RULE = /^([-=_~])\1*(?:\s+\1+)+$/;
|
|
@@ -124,12 +189,94 @@ function findCodeSpanEnd(text, start) {
|
|
|
124
189
|
}
|
|
125
190
|
return -1;
|
|
126
191
|
}
|
|
192
|
+
function isEscaped(text, index) {
|
|
193
|
+
let backslashes = 0;
|
|
194
|
+
while (text[index - 1 - backslashes] === "\\") {
|
|
195
|
+
backslashes += 1;
|
|
196
|
+
}
|
|
197
|
+
return backslashes % 2 === 1;
|
|
198
|
+
}
|
|
199
|
+
function findUnescaped(text, char, start) {
|
|
200
|
+
for (let index = start; index < text.length; index += 1) {
|
|
201
|
+
if (text[index] === "\\") {
|
|
202
|
+
index += 1;
|
|
203
|
+
} else if (text[index] === char) {
|
|
204
|
+
return index;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return -1;
|
|
208
|
+
}
|
|
209
|
+
function findClosingBracket(text, start) {
|
|
210
|
+
let depth = 0;
|
|
211
|
+
for (let index = start; index < text.length; index += 1) {
|
|
212
|
+
switch (text[index]) {
|
|
213
|
+
case "\\": {
|
|
214
|
+
index += 1;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "`": {
|
|
218
|
+
const close = findCodeSpanEnd(text, index);
|
|
219
|
+
index = close === -1 ? index + backtickRun(text, index) - 1 : close;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "[": {
|
|
223
|
+
depth += 1;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "]": {
|
|
227
|
+
depth -= 1;
|
|
228
|
+
if (depth === 0) {
|
|
229
|
+
return index;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return -1;
|
|
236
|
+
}
|
|
237
|
+
function findDestinationEnd(text, start) {
|
|
238
|
+
let depth = 0;
|
|
239
|
+
for (let index = start; index < text.length; index += 1) {
|
|
240
|
+
const char = text[index];
|
|
241
|
+
let close = -1;
|
|
242
|
+
if (char === "\\") {
|
|
243
|
+
index += 1;
|
|
244
|
+
} else if (char === "<" && text.slice(start + 1, index).trim() === "") {
|
|
245
|
+
close = findUnescaped(text, ">", index + 1);
|
|
246
|
+
} else if ((char === '"' || char === "'") && /\s/.test(text[index - 1])) {
|
|
247
|
+
close = findUnescaped(text, char, index + 1);
|
|
248
|
+
} else if (char === "(") {
|
|
249
|
+
depth += 1;
|
|
250
|
+
} else if (char === ")" && --depth === 0) {
|
|
251
|
+
return index;
|
|
252
|
+
}
|
|
253
|
+
index = close === -1 ? index : close;
|
|
254
|
+
}
|
|
255
|
+
return -1;
|
|
256
|
+
}
|
|
257
|
+
function findLinkEnd(text, start) {
|
|
258
|
+
const close = findClosingBracket(text, start);
|
|
259
|
+
if (close === -1 || text[close + 1] !== "(") {
|
|
260
|
+
return close;
|
|
261
|
+
}
|
|
262
|
+
const end = findDestinationEnd(text, close + 1);
|
|
263
|
+
return end === -1 ? close : end;
|
|
264
|
+
}
|
|
127
265
|
function tokenize(text) {
|
|
266
|
+
return splitWords(text, true);
|
|
267
|
+
}
|
|
268
|
+
function splitWords(text, keepLinks) {
|
|
128
269
|
const tokens = [];
|
|
129
270
|
let current = "";
|
|
130
271
|
let index = 0;
|
|
131
272
|
while (index < text.length) {
|
|
132
273
|
const char = text[index];
|
|
274
|
+
const linkEnd = keepLinks && char === "[" && !isEscaped(text, index) ? findLinkEnd(text, index) : -1;
|
|
275
|
+
if (linkEnd !== -1) {
|
|
276
|
+
current += splitWords(text.slice(index, linkEnd + 1), false).join(" ");
|
|
277
|
+
index = linkEnd + 1;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
133
280
|
const close = char === "`" ? findCodeSpanEnd(text, index) : char === "{" ? findClosingBrace(text, index) : -1;
|
|
134
281
|
if (char === "`" && close === -1) {
|
|
135
282
|
const run = backtickRun(text, index);
|
|
@@ -225,7 +372,7 @@ function isOrphan(previous, token) {
|
|
|
225
372
|
return opensAside(token) || endsClause(previous) && !CLAUSE_END.test(token);
|
|
226
373
|
}
|
|
227
374
|
function startsBlock(token) {
|
|
228
|
-
return /^(
|
|
375
|
+
return /^(?:\+|-+|=+|\*+|_{3,}|\d+[.)]|#{1,6})$/.test(token) || /^(?:>|@[A-Za-z]|`{3}|~{3}|\||<(?:\/?[A-Za-z]|[!?]))/.test(token);
|
|
229
376
|
}
|
|
230
377
|
function bindLineStarts(tokens, startsLine2 = startsBlock) {
|
|
231
378
|
const units = [];
|
|
@@ -663,6 +810,7 @@ var NAMED_TAGS = /* @__PURE__ */ new Set(["arg", "argument", "param", "prop", "p
|
|
|
663
810
|
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)/;
|
|
664
811
|
var BLOCK_DIRECTIVE = /^\s*(?:eslint\s|globals?\s|exported\s|jshint\s|jslint\s|type\s*:)/;
|
|
665
812
|
var LEGAL_COMMENT = /^!|@license\b|@preserve\b|\bcopyright\s+(?:\(c\)|©|\d{4})/i;
|
|
813
|
+
var RUST_LEGAL_COMMENT = /@license\b|@preserve\b|\bcopyright\s+(?:\(c\)|©|\d{4})/i;
|
|
666
814
|
var DEFINITION = /^(?:``|[\w$.[\]]+(?:[ -][\w$.[\]]+)?)(?: [-–]|:) \S/;
|
|
667
815
|
var BOX_DRAWING = /[\u2500-\u257F]/;
|
|
668
816
|
var TABLE_DELIMITER = /^\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*)+\|?$/;
|
|
@@ -737,12 +885,35 @@ ${text}
|
|
|
737
885
|
}
|
|
738
886
|
return properties.length > 1 || text.trimEnd().endsWith(",") || properties.some((property) => property.type === "Property" && CODE_VALUES.has(property.value.type));
|
|
739
887
|
}
|
|
740
|
-
function isCode(texts) {
|
|
741
|
-
|
|
742
|
-
return parsesAsStatements(text) || parsesAsProperties(text);
|
|
888
|
+
function isCode(texts, language) {
|
|
889
|
+
return language.isCode(texts.map((line) => line.replace(LIST_MARKER, "")).join("\n"));
|
|
743
890
|
}
|
|
744
891
|
function isCodeLine(text) {
|
|
745
|
-
return text.endsWith("{") || text.endsWith(";") && (/[()[\]{}=<>]/.test(text) || !/\s/.test(text)) || /^[}\])]/.test(text)
|
|
892
|
+
return text.endsWith("{") || text.endsWith(";") && (/[()[\]{}=<>]/.test(text) || !/\s/.test(text)) || /^[}\])]/.test(text);
|
|
893
|
+
}
|
|
894
|
+
var JAVASCRIPT = {
|
|
895
|
+
name: "javascript",
|
|
896
|
+
isCode: (text) => parsesAsStatements(text) || parsesAsProperties(text),
|
|
897
|
+
isCodeLine: (text) => {
|
|
898
|
+
return isCodeLine(text) || /^(?:const|let|var)\s+[\w$]+\s*[:=]/.test(text) || /^(?:import|export)\s.*(?:\sfrom\s|[{}])/.test(text);
|
|
899
|
+
},
|
|
900
|
+
legal: LEGAL_COMMENT,
|
|
901
|
+
blockDirective: BLOCK_DIRECTIVE,
|
|
902
|
+
tags: true,
|
|
903
|
+
joinsCodeSpans: true
|
|
904
|
+
};
|
|
905
|
+
var RUST = {
|
|
906
|
+
name: "rust",
|
|
907
|
+
isCode: (text) => isRustCode(text),
|
|
908
|
+
isCodeLine: (text) => {
|
|
909
|
+
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);
|
|
910
|
+
},
|
|
911
|
+
legal: RUST_LEGAL_COMMENT,
|
|
912
|
+
tags: false,
|
|
913
|
+
joinsCodeSpans: false
|
|
914
|
+
};
|
|
915
|
+
function languageOf(filename) {
|
|
916
|
+
return filename.endsWith(".rs") ? RUST : JAVASCRIPT;
|
|
746
917
|
}
|
|
747
918
|
function layoutReason(prose, raw) {
|
|
748
919
|
if (prose.startsWith("|") || TABLE_DELIMITER.test(prose)) {
|
|
@@ -756,8 +927,10 @@ function layoutReason(prose, raw) {
|
|
|
756
927
|
}
|
|
757
928
|
return void 0;
|
|
758
929
|
}
|
|
930
|
+
var THEMATIC_BREAK = /^(?:(?:\*\s*){3,}|(?:-\s*){3,}|(?:_\s*){3,})$/;
|
|
759
931
|
function isMarkup(prose) {
|
|
760
|
-
|
|
932
|
+
const text = prose.replace(LIST_MARKER, "");
|
|
933
|
+
return /^#{1,6}(?:\s|$)/.test(text) || /^\[[^\]]+\]:/.test(text) || /^<(?:\/?[a-z]|[!?])/i.test(text) || text.startsWith(">") || /^(?:=+|-+)$/.test(prose) || THEMATIC_BREAK.test(prose);
|
|
761
934
|
}
|
|
762
935
|
function mask(text) {
|
|
763
936
|
return text.replaceAll(/[^\n]/g, "x");
|
|
@@ -784,11 +957,14 @@ function hasAlignedColumns(lines) {
|
|
|
784
957
|
}
|
|
785
958
|
return false;
|
|
786
959
|
}
|
|
787
|
-
function structureOf(lines) {
|
|
960
|
+
function structureOf(lines, language) {
|
|
788
961
|
const trimmed = lines.map((line) => line.text.trim()).join("\n");
|
|
789
962
|
const texts = replaceKeepingLines(trimmed, CODE_SPAN, "``").split("\n");
|
|
790
963
|
const proseLines = stripNonProse(trimmed).split("\n");
|
|
791
|
-
if (isCode(
|
|
964
|
+
if (isCode(
|
|
965
|
+
lines.map((line) => line.text.trim()),
|
|
966
|
+
language
|
|
967
|
+
)) {
|
|
792
968
|
return { tier: "code", reason: "code" };
|
|
793
969
|
}
|
|
794
970
|
let layout;
|
|
@@ -797,7 +973,7 @@ function structureOf(lines) {
|
|
|
797
973
|
for (const [position, line] of texts.entries()) {
|
|
798
974
|
const prose = proseLines[position].trim();
|
|
799
975
|
layout ??= layoutReason(prose, lines[position]?.text ?? "");
|
|
800
|
-
if (isCodeLine(line.trim())) {
|
|
976
|
+
if (language.isCodeLine(line.trim())) {
|
|
801
977
|
codeLike = true;
|
|
802
978
|
} else if (isMarkup(prose)) {
|
|
803
979
|
manual ??= "markup";
|
|
@@ -836,7 +1012,7 @@ var FenceTracker = class {
|
|
|
836
1012
|
return true;
|
|
837
1013
|
}
|
|
838
1014
|
};
|
|
839
|
-
function segment(lines) {
|
|
1015
|
+
function segment(lines, language) {
|
|
840
1016
|
const paragraphs = [];
|
|
841
1017
|
const fence = new FenceTracker();
|
|
842
1018
|
let current;
|
|
@@ -873,6 +1049,10 @@ function segment(lines) {
|
|
|
873
1049
|
continue;
|
|
874
1050
|
}
|
|
875
1051
|
if (line.verbatim) {
|
|
1052
|
+
if (fence.next(text)) {
|
|
1053
|
+
start("code", line, "", "code block");
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
876
1056
|
start("code", line, "", "directive");
|
|
877
1057
|
flush();
|
|
878
1058
|
continue;
|
|
@@ -897,7 +1077,7 @@ function segment(lines) {
|
|
|
897
1077
|
}
|
|
898
1078
|
continue;
|
|
899
1079
|
}
|
|
900
|
-
const tag = JSDOC_TAG.exec(text);
|
|
1080
|
+
const tag = language.tags ? JSDOC_TAG.exec(text) : null;
|
|
901
1081
|
if (tag) {
|
|
902
1082
|
start("tag", line, "", PROSE_TAGS.has(tag[1]) ? void 0 : `@${tag[1]} tag`);
|
|
903
1083
|
inCodeTag = CODE_TAGS.has(tag[1]);
|
|
@@ -1004,7 +1184,19 @@ function byLine(issues) {
|
|
|
1004
1184
|
function sameLayout(a, b) {
|
|
1005
1185
|
return a.length === b.length && a.every((words, position) => words.join(" ") === b[position].join(" "));
|
|
1006
1186
|
}
|
|
1007
|
-
function
|
|
1187
|
+
function splitTokenLine(lines, tokens) {
|
|
1188
|
+
let next = 0;
|
|
1189
|
+
return lines.findIndex((words) => {
|
|
1190
|
+
const line = words.join(" ");
|
|
1191
|
+
let text = "";
|
|
1192
|
+
while (text.length < line.length && next < tokens.length) {
|
|
1193
|
+
text += `${text ? " " : ""}${tokens[next]}`;
|
|
1194
|
+
next += 1;
|
|
1195
|
+
}
|
|
1196
|
+
return text !== line;
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
function analyzeParagraph(paragraph, options, language) {
|
|
1008
1200
|
const first = paragraph.lines[0];
|
|
1009
1201
|
const last = paragraph.lines.at(-1) ?? first;
|
|
1010
1202
|
const base = { kind: paragraph.kind, startLine: first.index + 1, endLine: last.index + 1 };
|
|
@@ -1031,7 +1223,8 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1031
1223
|
const structure2 = structureOf(
|
|
1032
1224
|
prose.lines.map((line) => {
|
|
1033
1225
|
return { ...line, text: stripBlockSyntax(line.text) };
|
|
1034
|
-
})
|
|
1226
|
+
}),
|
|
1227
|
+
language
|
|
1035
1228
|
);
|
|
1036
1229
|
const issues2 = keptIssues(prose, structure2, options);
|
|
1037
1230
|
return { ...base, reflowable: false, reason: paragraph.reason, issues: issues2, suggestion: null };
|
|
@@ -1040,7 +1233,7 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1040
1233
|
const issues2 = keptIssues(paragraph, structure2, options);
|
|
1041
1234
|
return { ...base, reflowable: false, reason: structure2.reason, issues: issues2, suggestion: null };
|
|
1042
1235
|
};
|
|
1043
|
-
const structure = structureOf(paragraph.lines);
|
|
1236
|
+
const structure = structureOf(paragraph.lines, language);
|
|
1044
1237
|
if (structure) {
|
|
1045
1238
|
return keep(structure);
|
|
1046
1239
|
}
|
|
@@ -1072,10 +1265,14 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1072
1265
|
if (!units.every((unit) => staysInLimit(unit))) {
|
|
1073
1266
|
return keep({ tier: "manual", reason: "unbreakable text across lines" });
|
|
1074
1267
|
}
|
|
1268
|
+
const text = currentLines.map((words) => words.join(" ")).join("\n");
|
|
1269
|
+
if (!language.joinsCodeSpans && [...text.matchAll(CODE_SPAN)].some((span) => span[0].includes("\n"))) {
|
|
1270
|
+
return keep({ tier: "manual", reason: "code span across lines" });
|
|
1271
|
+
}
|
|
1075
1272
|
const bodyOf = (lines) => {
|
|
1076
1273
|
return lines.map((words, position) => {
|
|
1077
|
-
const
|
|
1078
|
-
return { ...first, text, verbatim: false, hardBreak: HARD_BREAK.test(
|
|
1274
|
+
const text2 = `${position === 0 ? paragraph.marker : hang}${words.join(" ")}`;
|
|
1275
|
+
return { ...first, text: text2, verbatim: false, hardBreak: HARD_BREAK.test(text2) };
|
|
1079
1276
|
});
|
|
1080
1277
|
};
|
|
1081
1278
|
const settle = (settings, extra = 0) => {
|
|
@@ -1083,11 +1280,11 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1083
1280
|
let { even: even2, wrapped: wrapped2 } = layOut(current, settings, extra);
|
|
1084
1281
|
for (; ; ) {
|
|
1085
1282
|
const body = bodyOf(wrapped2);
|
|
1086
|
-
const rewrap = structureOf(body);
|
|
1283
|
+
const rewrap = structureOf(body, language);
|
|
1087
1284
|
if (!rewrap) {
|
|
1088
1285
|
return { even: even2, wrapped: wrapped2 };
|
|
1089
1286
|
}
|
|
1090
|
-
const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1)) !== void 0);
|
|
1287
|
+
const tipping = body.findIndex((_, position) => structureOf(body.slice(0, position + 1), language) !== void 0);
|
|
1091
1288
|
const start = wrapped2.slice(0, Math.max(tipping, 1)).flat().length;
|
|
1092
1289
|
const pair = `${current[start - 1]} ${current[start]}`;
|
|
1093
1290
|
if (body.length < 2 || !staysInLimit(pair)) {
|
|
@@ -1113,6 +1310,7 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1113
1310
|
}
|
|
1114
1311
|
const { even, wrapped } = layout;
|
|
1115
1312
|
const matchesLayout = sameLayout(wrapped, currentLines);
|
|
1313
|
+
const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
|
|
1116
1314
|
const lineIssues = [];
|
|
1117
1315
|
const overflow = options.maxWidth === void 0 ? [] : overflowIssues(paragraph.lines, options);
|
|
1118
1316
|
if (!matchesLayout) {
|
|
@@ -1171,8 +1369,11 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1171
1369
|
lineIssues.push({ type: "unbalanced", line });
|
|
1172
1370
|
}
|
|
1173
1371
|
}
|
|
1372
|
+
const splitLine = splitTokenLine(currentLines, tokens);
|
|
1373
|
+
if (splitLine !== -1 && overflow.length === 0 && !lineIssues.some((issue) => isVerdict(issue))) {
|
|
1374
|
+
lineIssues.push({ type: "unbalanced", line: splitLine });
|
|
1375
|
+
}
|
|
1174
1376
|
}
|
|
1175
|
-
const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
|
|
1176
1377
|
const matchesWiderLayout = () => {
|
|
1177
1378
|
for (let extra = 1; extra <= layoutOptions.tolerance; extra += 1) {
|
|
1178
1379
|
const wider = layoutAt(extra);
|
|
@@ -1182,7 +1383,20 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1182
1383
|
}
|
|
1183
1384
|
return false;
|
|
1184
1385
|
};
|
|
1185
|
-
const
|
|
1386
|
+
const staysNearLayout = () => {
|
|
1387
|
+
if (currentLines.length !== wrapped.length || splitTokenLine(currentLines, units) !== -1) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
return currentLines.every((words, position) => {
|
|
1391
|
+
const length = words.join(" ").length;
|
|
1392
|
+
const suggestedLength = wrapped[position].join(" ").length;
|
|
1393
|
+
const limit = (position === 0 ? firstLimit : restLimit) + layoutOptions.tolerance;
|
|
1394
|
+
return Math.abs(length - suggestedLength) <= layoutOptions.tolerance && length <= Math.max(suggestedLength, limit);
|
|
1395
|
+
});
|
|
1396
|
+
};
|
|
1397
|
+
const hasVerdict = lineIssues.some((issue) => isVerdict(issue));
|
|
1398
|
+
const onlyUnbalanced = lineIssues.every((issue) => issue.type === "unbalanced");
|
|
1399
|
+
const accepted = hasVerdict && (matchesWiderLayout() || onlyUnbalanced && staysNearLayout());
|
|
1186
1400
|
const suggested = accepted ? currentLines : wrapped;
|
|
1187
1401
|
const suggestion = suggested.map(
|
|
1188
1402
|
(words, position) => `${prefix}${position === 0 ? paragraph.marker : hang}${words.join(" ")}`
|
|
@@ -1191,6 +1405,18 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1191
1405
|
const issues = byLine([...overflow, ...wrapIssues, ...styleIssues(paragraph, options)]);
|
|
1192
1406
|
return { ...base, reflowable: true, issues, suggestion };
|
|
1193
1407
|
}
|
|
1408
|
+
var LINE_COMMENT = "line comment";
|
|
1409
|
+
function analyzeLineParagraph(paragraph, options, language) {
|
|
1410
|
+
const first = paragraph.lines[0];
|
|
1411
|
+
const last = paragraph.lines.at(-1) ?? first;
|
|
1412
|
+
const base = { kind: paragraph.kind, startLine: first.index + 1, endLine: last.index + 1 };
|
|
1413
|
+
if (paragraph.kind === "code") {
|
|
1414
|
+
return { ...base, reflowable: false, reason: paragraph.reason, issues: [], suggestion: null };
|
|
1415
|
+
}
|
|
1416
|
+
const tier = structureOf(paragraph.lines, language)?.tier;
|
|
1417
|
+
const issues = tier === "code" || tier === "layout" ? [] : overflowIssues(paragraph.lines, options);
|
|
1418
|
+
return { ...base, reflowable: false, reason: LINE_COMMENT, issues, suggestion: null };
|
|
1419
|
+
}
|
|
1194
1420
|
function computeLineStarts(source) {
|
|
1195
1421
|
const starts = [0];
|
|
1196
1422
|
for (let index = 0; index < source.length; index += 1) {
|
|
@@ -1216,31 +1442,42 @@ function lineOf(lineStarts, offset) {
|
|
|
1216
1442
|
function stripCarriageReturn(line) {
|
|
1217
1443
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1218
1444
|
}
|
|
1219
|
-
function
|
|
1445
|
+
function blockKind(comment, language) {
|
|
1446
|
+
if (language.name === "rust") {
|
|
1447
|
+
return comment.doc ? `${comment.doc}-block-doc` : "block";
|
|
1448
|
+
}
|
|
1449
|
+
return comment.value.startsWith("*") ? "jsdoc" : "block";
|
|
1450
|
+
}
|
|
1451
|
+
function wholeComment(shape, lines, paragraphKind, reason) {
|
|
1452
|
+
const otherLines = lines.map((text, offset) => {
|
|
1453
|
+
return { index: shape.startLine + offset, prefix: "", text, verbatim: false, hardBreak: false };
|
|
1454
|
+
});
|
|
1455
|
+
return { ...shape, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
|
|
1456
|
+
}
|
|
1457
|
+
function isLegal(value, language) {
|
|
1458
|
+
return language.legal.test(replaceKeepingLines(value, CODE_SPAN, "``"));
|
|
1459
|
+
}
|
|
1460
|
+
function shapeBlock(comment, sourceLines, lineStarts, language) {
|
|
1220
1461
|
const startLine = lineOf(lineStarts, comment.start);
|
|
1221
1462
|
const endLine = lineOf(lineStarts, comment.end - 1);
|
|
1222
|
-
const
|
|
1463
|
+
const shape = { kind: blockKind(comment, language), startLine, endLine };
|
|
1223
1464
|
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
});
|
|
1228
|
-
return { kind, startLine, endLine, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
|
|
1229
|
-
};
|
|
1230
|
-
const value = comment.value.replace(/^\*/, "");
|
|
1231
|
-
if (LEGAL_COMMENT.test(replaceKeepingLines(comment.value, CODE_SPAN, "``"))) {
|
|
1232
|
-
return wholeComment("code", "legal comment");
|
|
1465
|
+
const value = comment.doc ? comment.value.slice(1) : comment.value.replace(/^\*/, "");
|
|
1466
|
+
if (isLegal(comment.value, language)) {
|
|
1467
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1233
1468
|
}
|
|
1234
|
-
if (LINE_DIRECTIVE.test(value) ||
|
|
1235
|
-
return wholeComment("code", "directive");
|
|
1469
|
+
if (LINE_DIRECTIVE.test(value) || language.blockDirective?.test(value)) {
|
|
1470
|
+
return wholeComment(shape, lines, "code", "directive");
|
|
1236
1471
|
}
|
|
1237
1472
|
const other = () => {
|
|
1238
1473
|
return wholeComment(
|
|
1474
|
+
shape,
|
|
1475
|
+
lines,
|
|
1239
1476
|
"other",
|
|
1240
1477
|
startLine === endLine ? "single-line block comment" : "unsupported block comment layout"
|
|
1241
1478
|
);
|
|
1242
1479
|
};
|
|
1243
|
-
if (startLine === endLine || !/^\s
|
|
1480
|
+
if (startLine === endLine || !/^\s*\/\*[*!]?$/.test(lines[0]) || !/^\s*\*\/$/.test(lines.at(-1) ?? "")) {
|
|
1244
1481
|
return other();
|
|
1245
1482
|
}
|
|
1246
1483
|
const body = [];
|
|
@@ -1259,7 +1496,52 @@ function shapeBlock(comment, sourceLines, lineStarts) {
|
|
|
1259
1496
|
hardBreak: HARD_BREAK.test(text)
|
|
1260
1497
|
});
|
|
1261
1498
|
}
|
|
1262
|
-
return {
|
|
1499
|
+
return { ...shape, paragraphs: segment(body, language) };
|
|
1500
|
+
}
|
|
1501
|
+
function shapeLineRun(group, sourceLines, lineStarts, language) {
|
|
1502
|
+
const startLine = lineOf(lineStarts, group[0].start);
|
|
1503
|
+
const endLine = startLine + group.length - 1;
|
|
1504
|
+
const kind = group[0].doc === "inner" ? "inner-line-doc" : "outer-line-doc";
|
|
1505
|
+
const shape = { kind, startLine, endLine };
|
|
1506
|
+
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1507
|
+
const values = group.map((comment) => comment.value.slice(1));
|
|
1508
|
+
if (isLegal(values.join("\n"), language)) {
|
|
1509
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1510
|
+
}
|
|
1511
|
+
if (LINE_DIRECTIVE.test(values[0])) {
|
|
1512
|
+
return wholeComment(shape, lines, "code", "directive");
|
|
1513
|
+
}
|
|
1514
|
+
const body = lines.map((line, offset) => {
|
|
1515
|
+
const [, marker, space, text] = /^(\s*\/\/[/!])( ?)(.*)$/.exec(line) ?? ["", "", "", line];
|
|
1516
|
+
return {
|
|
1517
|
+
index: startLine + offset,
|
|
1518
|
+
prefix: `${marker} `,
|
|
1519
|
+
text: text.trimEnd(),
|
|
1520
|
+
verbatim: !space && text.length > 0 || LINE_DIRECTIVE.test(text),
|
|
1521
|
+
hardBreak: HARD_BREAK.test(text)
|
|
1522
|
+
};
|
|
1523
|
+
});
|
|
1524
|
+
return { ...shape, paragraphs: segment(body, language) };
|
|
1525
|
+
}
|
|
1526
|
+
function shapeLineComment(group, sourceLines, lineStarts, language) {
|
|
1527
|
+
const startLine = lineOf(lineStarts, group[0].start);
|
|
1528
|
+
const endLine = startLine + group.length - 1;
|
|
1529
|
+
const shape = { kind: "line", startLine, endLine };
|
|
1530
|
+
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1531
|
+
if (isLegal(group.map((comment) => comment.value).join("\n"), language)) {
|
|
1532
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1533
|
+
}
|
|
1534
|
+
const body = lines.map((line, offset) => {
|
|
1535
|
+
const [, marker, space, text] = /^(\s*\/\/[/!]*)( ?)(.*)$/.exec(line) ?? ["", "", "", line];
|
|
1536
|
+
return {
|
|
1537
|
+
index: startLine + offset,
|
|
1538
|
+
prefix: `${marker}${space}`,
|
|
1539
|
+
text: text.trimEnd(),
|
|
1540
|
+
verbatim: LINE_DIRECTIVE.test(text),
|
|
1541
|
+
hardBreak: HARD_BREAK.test(text)
|
|
1542
|
+
};
|
|
1543
|
+
});
|
|
1544
|
+
return { ...shape, paragraphs: segment(body, { ...language, tags: false }) };
|
|
1263
1545
|
}
|
|
1264
1546
|
function ownsLines(comment, source, lineStarts) {
|
|
1265
1547
|
const startLine = lineOf(lineStarts, comment.start);
|
|
@@ -1269,12 +1551,12 @@ function ownsLines(comment, source, lineStarts) {
|
|
|
1269
1551
|
const after = source.slice(comment.end, lineEnd);
|
|
1270
1552
|
return /^[\t ]*$/.test(before) && /^[\t ]*\r?\n?$/.test(after);
|
|
1271
1553
|
}
|
|
1272
|
-
function singleLineLength(paragraph, options) {
|
|
1554
|
+
function singleLineLength(paragraph, options, language) {
|
|
1273
1555
|
const [first] = paragraph.lines;
|
|
1274
1556
|
if (paragraph.kind === "other" && paragraph.reason === "single-line block comment") {
|
|
1275
1557
|
return sourceLineLength(first, options.tabWidth);
|
|
1276
1558
|
}
|
|
1277
|
-
if (paragraph.kind !== "prose" || structureOf(paragraph.lines)) {
|
|
1559
|
+
if (paragraph.kind !== "prose" || structureOf(paragraph.lines, language)) {
|
|
1278
1560
|
return Infinity;
|
|
1279
1561
|
}
|
|
1280
1562
|
const text = tokenize(paragraph.lines.map((line) => line.text).join(" ")).join(" ");
|
|
@@ -1289,6 +1571,58 @@ function describeFailure(errors, lineStarts) {
|
|
|
1289
1571
|
const offset = first.labels.at(0)?.start;
|
|
1290
1572
|
return { message: first.message, line: offset === void 0 ? void 0 : lineOf(lineStarts, offset) + 1 };
|
|
1291
1573
|
}
|
|
1574
|
+
function scanJavaScript(filename, text, lineStarts) {
|
|
1575
|
+
const { program, comments, errors } = parseSource(filename, text);
|
|
1576
|
+
if (errors.length > 0 && program.body.length === 0) {
|
|
1577
|
+
return { comments: [], syntaxError: describeFailure(errors, lineStarts) };
|
|
1578
|
+
}
|
|
1579
|
+
return {
|
|
1580
|
+
comments: comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")))
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
function scanRustSource(text, lineStarts) {
|
|
1584
|
+
const { comments, error } = scanRust(text);
|
|
1585
|
+
if (error) {
|
|
1586
|
+
return { comments: [], syntaxError: { message: error.message, line: lineOf(lineStarts, error.offset) + 1 } };
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
comments: comments.map((comment) => {
|
|
1590
|
+
return {
|
|
1591
|
+
type: comment.block ? "Block" : "Line",
|
|
1592
|
+
value: text.slice(comment.start + 2, comment.block ? comment.end - 2 : comment.end),
|
|
1593
|
+
start: comment.start,
|
|
1594
|
+
end: comment.end,
|
|
1595
|
+
doc: comment.doc
|
|
1596
|
+
};
|
|
1597
|
+
})
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
function groupLineRuns(comments, text, lineStarts, markerOf) {
|
|
1601
|
+
const groups = [];
|
|
1602
|
+
let current;
|
|
1603
|
+
let previousLine = -1;
|
|
1604
|
+
let indent = "";
|
|
1605
|
+
let marker;
|
|
1606
|
+
for (const comment of comments) {
|
|
1607
|
+
const next = comment.type === "Line" ? markerOf(comment) : void 0;
|
|
1608
|
+
if (next === void 0 || !ownsLines(comment, text, lineStarts)) {
|
|
1609
|
+
current = void 0;
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
const line = lineOf(lineStarts, comment.start);
|
|
1613
|
+
const before = text.slice(lineStarts[line], comment.start);
|
|
1614
|
+
if (current && line === previousLine + 1 && next === marker && before === indent) {
|
|
1615
|
+
current.push(comment);
|
|
1616
|
+
} else {
|
|
1617
|
+
current = [comment];
|
|
1618
|
+
indent = before;
|
|
1619
|
+
marker = next;
|
|
1620
|
+
groups.push(current);
|
|
1621
|
+
}
|
|
1622
|
+
previousLine = line;
|
|
1623
|
+
}
|
|
1624
|
+
return groups;
|
|
1625
|
+
}
|
|
1292
1626
|
function analyzeSource(source, filename, options) {
|
|
1293
1627
|
return inspectSource(source, filename, options).comments;
|
|
1294
1628
|
}
|
|
@@ -1297,28 +1631,16 @@ function inspectSource(source, filename, options) {
|
|
|
1297
1631
|
throw new Error("comments: maxWidth must be an integer at least as large as width");
|
|
1298
1632
|
}
|
|
1299
1633
|
const text = source.startsWith(BYTE_ORDER_MARK) ? source.slice(1) : source;
|
|
1300
|
-
const
|
|
1634
|
+
const language = languageOf(filename);
|
|
1301
1635
|
const lineStarts = computeLineStarts(text);
|
|
1302
|
-
|
|
1303
|
-
|
|
1636
|
+
const scanned = language.name === "rust" ? scanRustSource(text, lineStarts) : scanJavaScript(filename, text, lineStarts);
|
|
1637
|
+
if (scanned.syntaxError) {
|
|
1638
|
+
return { comments: [], syntaxError: scanned.syntaxError };
|
|
1304
1639
|
}
|
|
1305
1640
|
const sourceLines = text.split("\n").map((line) => stripCarriageReturn(line));
|
|
1306
|
-
const parsed = comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")));
|
|
1307
1641
|
const reports = [];
|
|
1308
|
-
const
|
|
1309
|
-
return
|
|
1310
|
-
});
|
|
1311
|
-
for (const block of blocks) {
|
|
1312
|
-
const shape = shapeBlock(block, sourceLines, lineStarts);
|
|
1313
|
-
const singleLineWidth = Math.max(
|
|
1314
|
-
options.width,
|
|
1315
|
-
Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
|
|
1316
|
-
);
|
|
1317
|
-
const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
|
|
1318
|
-
const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options) : Infinity;
|
|
1319
|
-
const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
|
|
1320
|
-
const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
|
|
1321
|
-
reports.push({
|
|
1642
|
+
const report = (shape, lines, paragraphs) => {
|
|
1643
|
+
return {
|
|
1322
1644
|
file: filename,
|
|
1323
1645
|
kind: shape.kind,
|
|
1324
1646
|
startLine: shape.startLine + 1,
|
|
@@ -1328,8 +1650,47 @@ function inspectSource(source, filename, options) {
|
|
|
1328
1650
|
maxWidth: options.maxWidth,
|
|
1329
1651
|
lengths: lines.map((line) => visualLength(line.trimEnd(), options.tabWidth)),
|
|
1330
1652
|
text: lines.join("\n"),
|
|
1331
|
-
paragraphs
|
|
1653
|
+
paragraphs
|
|
1654
|
+
};
|
|
1655
|
+
};
|
|
1656
|
+
const blocks = scanned.comments.filter((comment) => {
|
|
1657
|
+
return comment.type === "Block" && ownsLines(comment, text, lineStarts) && !comment.value.includes(IGNORE_DIRECTIVE);
|
|
1658
|
+
});
|
|
1659
|
+
const runs = (markerOf) => {
|
|
1660
|
+
return groupLineRuns(scanned.comments, text, lineStarts, markerOf).filter((group) => {
|
|
1661
|
+
return !group.some((comment) => comment.value.includes(IGNORE_DIRECTIVE));
|
|
1332
1662
|
});
|
|
1663
|
+
};
|
|
1664
|
+
const shapes = [
|
|
1665
|
+
...blocks.map((block) => shapeBlock(block, sourceLines, lineStarts, language)),
|
|
1666
|
+
...runs((comment) => comment.doc).map((group) => shapeLineRun(group, sourceLines, lineStarts, language)),
|
|
1667
|
+
...runs((comment) => comment.doc ? void 0 : "//").map(
|
|
1668
|
+
(group) => shapeLineComment(group, sourceLines, lineStarts, language)
|
|
1669
|
+
)
|
|
1670
|
+
].toSorted((a, b) => a.startLine - b.startLine);
|
|
1671
|
+
for (const shape of shapes) {
|
|
1672
|
+
const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
|
|
1673
|
+
if (shape.kind === "line") {
|
|
1674
|
+
const paragraphs = shape.paragraphs.map((paragraph) => analyzeLineParagraph(paragraph, options, language));
|
|
1675
|
+
if (paragraphs.some((paragraph) => paragraph.issues.length > 0)) {
|
|
1676
|
+
reports.push(report(shape, lines, paragraphs));
|
|
1677
|
+
}
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
const singleLineWidth = Math.max(
|
|
1681
|
+
options.width,
|
|
1682
|
+
Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
|
|
1683
|
+
);
|
|
1684
|
+
const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
|
|
1685
|
+
const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options, language) : Infinity;
|
|
1686
|
+
const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
|
|
1687
|
+
reports.push(
|
|
1688
|
+
report(
|
|
1689
|
+
shape,
|
|
1690
|
+
lines,
|
|
1691
|
+
shape.paragraphs.map((paragraph) => analyzeParagraph(paragraph, paragraphOptions, language))
|
|
1692
|
+
)
|
|
1693
|
+
);
|
|
1333
1694
|
}
|
|
1334
1695
|
return { comments: reports };
|
|
1335
1696
|
}
|
|
@@ -1350,6 +1711,9 @@ function rewrapComment(report) {
|
|
|
1350
1711
|
}
|
|
1351
1712
|
var POSITION_KEYS = /* @__PURE__ */ new Set(["start", "end", "range", "loc"]);
|
|
1352
1713
|
function codeFingerprint(source, filename) {
|
|
1714
|
+
if (languageOf(filename).name === "rust") {
|
|
1715
|
+
return rustFingerprint(source);
|
|
1716
|
+
}
|
|
1353
1717
|
const { program, errors } = parseSource(filename, source);
|
|
1354
1718
|
if (errors.length > 0) {
|
|
1355
1719
|
return JSON.stringify({ source, errors: errors.map((error) => error.message) });
|
|
@@ -1363,7 +1727,7 @@ function codeFingerprint(source, filename) {
|
|
|
1363
1727
|
// src/comments/agent.ts
|
|
1364
1728
|
import { spawn } from "child_process";
|
|
1365
1729
|
import { readFileSync } from "fs";
|
|
1366
|
-
import
|
|
1730
|
+
import path2 from "path";
|
|
1367
1731
|
var DEFAULT_RULES = `
|
|
1368
1732
|
# Comment rules
|
|
1369
1733
|
|
|
@@ -1371,7 +1735,7 @@ var DEFAULT_RULES = `
|
|
|
1371
1735
|
|
|
1372
1736
|
When rules pull in different directions, the earlier one wins.
|
|
1373
1737
|
|
|
1374
|
-
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,
|
|
1738
|
+
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).
|
|
1375
1739
|
2. Keep the meaning. An edit may make a comment clearer, never different.
|
|
1376
1740
|
3. Follow the style preferences below.
|
|
1377
1741
|
4. Change as little as possible. Wording that already reads clearly stays, even when a note points at it.
|
|
@@ -1384,10 +1748,12 @@ When rules pull in different directions, the earlier one wins.
|
|
|
1384
1748
|
- 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.
|
|
1385
1749
|
- Separate paragraphs with an empty comment line.
|
|
1386
1750
|
- Never split a backtick code span across lines.
|
|
1751
|
+
- 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.
|
|
1387
1752
|
- 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.
|
|
1388
1753
|
- 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.
|
|
1389
1754
|
- 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.
|
|
1390
1755
|
- 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.
|
|
1756
|
+
- 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.
|
|
1391
1757
|
|
|
1392
1758
|
## Style
|
|
1393
1759
|
|
|
@@ -1398,8 +1764,16 @@ These are preferences for comment prose. They never apply to code or exact text
|
|
|
1398
1764
|
- 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.
|
|
1399
1765
|
- 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*\` \`\`.
|
|
1400
1766
|
- 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.
|
|
1401
|
-
- Block and
|
|
1402
|
-
- Never turn a block or JSDoc comment into \`//\` line comments, and never write a run of consecutive \`//\` lines.
|
|
1767
|
+
- Block, JSDoc, and Rust doc comments are capitalized and end with punctuation.
|
|
1768
|
+
- 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.
|
|
1769
|
+
|
|
1770
|
+
## Rust doc comments
|
|
1771
|
+
|
|
1772
|
+
Rust doc comments are Markdown that rustdoc renders.
|
|
1773
|
+
|
|
1774
|
+
- 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)\`.
|
|
1775
|
+
- A link reference definition, like \`\` [\`Foo\`]: crate::Foo \`\`, stays on its own line as it is. Its colon is syntax, not a label.
|
|
1776
|
+
- 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.
|
|
1403
1777
|
|
|
1404
1778
|
## Editing
|
|
1405
1779
|
|
|
@@ -1408,11 +1782,11 @@ These are preferences for comment prose. They never apply to code or exact text
|
|
|
1408
1782
|
`.trim();
|
|
1409
1783
|
|
|
1410
1784
|
// src/comments/config.ts
|
|
1411
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1412
|
-
import { createRequire } from "module";
|
|
1413
|
-
import
|
|
1785
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1786
|
+
import { createRequire as createRequire2 } from "module";
|
|
1787
|
+
import path3 from "path";
|
|
1414
1788
|
import { pathToFileURL } from "url";
|
|
1415
|
-
var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"]);
|
|
1789
|
+
var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".rs"]);
|
|
1416
1790
|
var CONFIG_FILES = [
|
|
1417
1791
|
"comments.config.ts",
|
|
1418
1792
|
"comments.config.mts",
|
|
@@ -1463,10 +1837,12 @@ function validateConfig(raw, filepath) {
|
|
|
1463
1837
|
assertType(config.avoidSentenceOrphans, "boolean", "avoidSentenceOrphans", filepath);
|
|
1464
1838
|
assertType(config.style, "boolean", "style", filepath);
|
|
1465
1839
|
assertType(config.extensions, "array", "extensions", filepath);
|
|
1840
|
+
assertType(config.include, "array", "include", filepath);
|
|
1466
1841
|
assertType(config.ignore, "array", "ignore", filepath);
|
|
1467
1842
|
assertType(config.agent, "object", "agent", filepath);
|
|
1468
1843
|
assertType(config.hook, "object", "hook", filepath);
|
|
1469
1844
|
assertStrings(config.extensions, "extensions", filepath);
|
|
1845
|
+
assertStrings(config.include, "include", filepath);
|
|
1470
1846
|
assertStrings(config.ignore, "ignore", filepath);
|
|
1471
1847
|
for (const key of ["width", "maxWidth", "singleLineWidth", "tolerance"]) {
|
|
1472
1848
|
const value = config[key];
|
|
@@ -1499,8 +1875,8 @@ function validateConfig(raw, filepath) {
|
|
|
1499
1875
|
}
|
|
1500
1876
|
assertType(agent.rules, "string", "agent.rules", filepath);
|
|
1501
1877
|
assertType(agent.instructions, "string", "agent.instructions", filepath);
|
|
1502
|
-
if (agent.scope !== void 0 &&
|
|
1503
|
-
throw new Error(`comments: \`agent.scope\` in ${filepath} must be
|
|
1878
|
+
if (agent.scope !== void 0 && !["suspicious", "failing", "all"].includes(agent.scope)) {
|
|
1879
|
+
throw new Error(`comments: \`agent.scope\` in ${filepath} must be one of suspicious, failing, all`);
|
|
1504
1880
|
}
|
|
1505
1881
|
const hook = config.hook ?? {};
|
|
1506
1882
|
if (hook.mode !== void 0 && !["agent", "report", "off"].includes(hook.mode)) {
|
|
@@ -1510,25 +1886,25 @@ function validateConfig(raw, filepath) {
|
|
|
1510
1886
|
}
|
|
1511
1887
|
async function loadConfig(cwd, options = {}) {
|
|
1512
1888
|
if (options.explicitPath) {
|
|
1513
|
-
const filepath =
|
|
1889
|
+
const filepath = path3.resolve(cwd, options.explicitPath);
|
|
1514
1890
|
return { config: validateConfig(await readConfigFile(filepath), filepath), filepath };
|
|
1515
1891
|
}
|
|
1516
|
-
let directory =
|
|
1892
|
+
let directory = path3.resolve(cwd);
|
|
1517
1893
|
for (; ; ) {
|
|
1518
1894
|
for (const name of CONFIG_FILES) {
|
|
1519
|
-
const filepath =
|
|
1520
|
-
if (
|
|
1895
|
+
const filepath = path3.join(directory, name);
|
|
1896
|
+
if (existsSync2(filepath)) {
|
|
1521
1897
|
return { config: validateConfig(await readConfigFile(filepath), filepath), filepath };
|
|
1522
1898
|
}
|
|
1523
1899
|
}
|
|
1524
|
-
const packageJsonPath =
|
|
1525
|
-
if (
|
|
1900
|
+
const packageJsonPath = path3.join(directory, "package.json");
|
|
1901
|
+
if (existsSync2(packageJsonPath)) {
|
|
1526
1902
|
const value = readJson(packageJsonPath)[PACKAGE_JSON_KEY];
|
|
1527
1903
|
if (value !== void 0) {
|
|
1528
1904
|
return { config: validateConfig(value, packageJsonPath), filepath: packageJsonPath };
|
|
1529
1905
|
}
|
|
1530
1906
|
}
|
|
1531
|
-
const parent =
|
|
1907
|
+
const parent = path3.dirname(directory);
|
|
1532
1908
|
if (directory === options.stopDir || parent === directory) {
|
|
1533
1909
|
return { config: {} };
|
|
1534
1910
|
}
|