@d3lm/lint-preset 1.5.5 → 1.6.0
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 +42 -7
- package/dist/bin/comments.js +559 -123
- package/dist/bin/comments.js.map +1 -1
- package/dist/comments.cjs +462 -77
- package/dist/comments.cjs.map +1 -1
- package/dist/comments.d.cts +46 -20
- package/dist/comments.d.ts +46 -20
- package/dist/comments.js +464 -81
- 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
|
}
|
|
@@ -1059,9 +1252,9 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1059
1252
|
parentheticalOverflow: options.maxWidth === void 0 ? void 0 : options.maxWidth - options.width,
|
|
1060
1253
|
keepFirstLine: paragraph.kind === "tag"
|
|
1061
1254
|
};
|
|
1062
|
-
const layOut = (from, settings) => {
|
|
1063
|
-
const even2 = layoutTokens(from, firstLimit, restLimit, settings);
|
|
1064
|
-
return { even: even2, wrapped: breakAfterSentence(even2, firstLimit, restLimit, settings) };
|
|
1255
|
+
const layOut = (from, settings, extra) => {
|
|
1256
|
+
const even2 = layoutTokens(from, firstLimit + extra, restLimit + extra, settings);
|
|
1257
|
+
return { even: even2, wrapped: breakAfterSentence(even2, firstLimit + extra, restLimit + extra, settings) };
|
|
1065
1258
|
};
|
|
1066
1259
|
const units = bindLineStarts(paragraph.kind === "tag" ? bindTagHead(tokens) : tokens, startsLine);
|
|
1067
1260
|
const hardLimit = restLimit + (options.maxWidth === void 0 ? options.tolerance : options.maxWidth - options.width);
|
|
@@ -1072,22 +1265,26 @@ 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
|
-
const settle = (settings) => {
|
|
1278
|
+
const settle = (settings, extra = 0) => {
|
|
1082
1279
|
let current = units;
|
|
1083
|
-
let { even: even2, wrapped: wrapped2 } = layOut(current, settings);
|
|
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)) {
|
|
@@ -1095,16 +1292,25 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1095
1292
|
return { tier: "manual", reason: `rewrap would read as ${reading}` };
|
|
1096
1293
|
}
|
|
1097
1294
|
current = current.toSpliced(start - 1, 2, pair);
|
|
1098
|
-
({ even: even2, wrapped: wrapped2 } = layOut(current, settings));
|
|
1295
|
+
({ even: even2, wrapped: wrapped2 } = layOut(current, settings, extra));
|
|
1099
1296
|
}
|
|
1100
1297
|
};
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1298
|
+
const layoutAt = (extra) => {
|
|
1299
|
+
const settings = {
|
|
1300
|
+
...layoutOptions,
|
|
1301
|
+
tolerance: layoutOptions.tolerance - extra,
|
|
1302
|
+
parentheticalOverflow: layoutOptions.parentheticalOverflow === void 0 ? void 0 : layoutOptions.parentheticalOverflow - extra
|
|
1303
|
+
};
|
|
1304
|
+
const settled = settle(settings, extra);
|
|
1305
|
+
return "wrapped" in settled ? settled : settle({ ...settings, keepSteps: true }, extra);
|
|
1306
|
+
};
|
|
1307
|
+
const layout = layoutAt(0);
|
|
1103
1308
|
if (!("wrapped" in layout)) {
|
|
1104
1309
|
return keep(layout);
|
|
1105
1310
|
}
|
|
1106
1311
|
const { even, wrapped } = layout;
|
|
1107
1312
|
const matchesLayout = sameLayout(wrapped, currentLines);
|
|
1313
|
+
const isVerdict = (issue) => ISSUE_TIERS[issue.type] === "verdict";
|
|
1108
1314
|
const lineIssues = [];
|
|
1109
1315
|
const overflow = options.maxWidth === void 0 ? [] : overflowIssues(paragraph.lines, options);
|
|
1110
1316
|
if (!matchesLayout) {
|
|
@@ -1163,14 +1369,41 @@ function analyzeParagraph(paragraph, options) {
|
|
|
1163
1369
|
lineIssues.push({ type: "unbalanced", line });
|
|
1164
1370
|
}
|
|
1165
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
|
+
}
|
|
1166
1376
|
}
|
|
1167
|
-
const
|
|
1377
|
+
const matchesWiderLayout = () => {
|
|
1378
|
+
for (let extra = 1; extra <= layoutOptions.tolerance; extra += 1) {
|
|
1379
|
+
const wider = layoutAt(extra);
|
|
1380
|
+
if ("wrapped" in wider && sameLayout(wider.wrapped, currentLines)) {
|
|
1381
|
+
return true;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return false;
|
|
1385
|
+
};
|
|
1386
|
+
const accepted = lineIssues.some((issue) => isVerdict(issue)) && matchesWiderLayout();
|
|
1387
|
+
const suggested = accepted ? currentLines : wrapped;
|
|
1388
|
+
const suggestion = suggested.map(
|
|
1168
1389
|
(words, position) => `${prefix}${position === 0 ? paragraph.marker : hang}${words.join(" ")}`
|
|
1169
1390
|
);
|
|
1170
|
-
const wrapIssues = lineIssues.map((issue) => lineIssue(issue.type, paragraph.lines[issue.line], options));
|
|
1391
|
+
const wrapIssues = lineIssues.filter((issue) => !accepted || !isVerdict(issue)).map((issue) => lineIssue(issue.type, paragraph.lines[issue.line], options));
|
|
1171
1392
|
const issues = byLine([...overflow, ...wrapIssues, ...styleIssues(paragraph, options)]);
|
|
1172
1393
|
return { ...base, reflowable: true, issues, suggestion };
|
|
1173
1394
|
}
|
|
1395
|
+
var LINE_COMMENT = "line comment";
|
|
1396
|
+
function analyzeLineParagraph(paragraph, options, language) {
|
|
1397
|
+
const first = paragraph.lines[0];
|
|
1398
|
+
const last = paragraph.lines.at(-1) ?? first;
|
|
1399
|
+
const base = { kind: paragraph.kind, startLine: first.index + 1, endLine: last.index + 1 };
|
|
1400
|
+
if (paragraph.kind === "code") {
|
|
1401
|
+
return { ...base, reflowable: false, reason: paragraph.reason, issues: [], suggestion: null };
|
|
1402
|
+
}
|
|
1403
|
+
const tier = structureOf(paragraph.lines, language)?.tier;
|
|
1404
|
+
const issues = tier === "code" || tier === "layout" ? [] : overflowIssues(paragraph.lines, options);
|
|
1405
|
+
return { ...base, reflowable: false, reason: LINE_COMMENT, issues, suggestion: null };
|
|
1406
|
+
}
|
|
1174
1407
|
function computeLineStarts(source) {
|
|
1175
1408
|
const starts = [0];
|
|
1176
1409
|
for (let index = 0; index < source.length; index += 1) {
|
|
@@ -1196,31 +1429,42 @@ function lineOf(lineStarts, offset) {
|
|
|
1196
1429
|
function stripCarriageReturn(line) {
|
|
1197
1430
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1198
1431
|
}
|
|
1199
|
-
function
|
|
1432
|
+
function blockKind(comment, language) {
|
|
1433
|
+
if (language.name === "rust") {
|
|
1434
|
+
return comment.doc ? `${comment.doc}-block-doc` : "block";
|
|
1435
|
+
}
|
|
1436
|
+
return comment.value.startsWith("*") ? "jsdoc" : "block";
|
|
1437
|
+
}
|
|
1438
|
+
function wholeComment(shape, lines, paragraphKind, reason) {
|
|
1439
|
+
const otherLines = lines.map((text, offset) => {
|
|
1440
|
+
return { index: shape.startLine + offset, prefix: "", text, verbatim: false, hardBreak: false };
|
|
1441
|
+
});
|
|
1442
|
+
return { ...shape, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
|
|
1443
|
+
}
|
|
1444
|
+
function isLegal(value, language) {
|
|
1445
|
+
return language.legal.test(replaceKeepingLines(value, CODE_SPAN, "``"));
|
|
1446
|
+
}
|
|
1447
|
+
function shapeBlock(comment, sourceLines, lineStarts, language) {
|
|
1200
1448
|
const startLine = lineOf(lineStarts, comment.start);
|
|
1201
1449
|
const endLine = lineOf(lineStarts, comment.end - 1);
|
|
1202
|
-
const
|
|
1450
|
+
const shape = { kind: blockKind(comment, language), startLine, endLine };
|
|
1203
1451
|
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
});
|
|
1208
|
-
return { kind, startLine, endLine, paragraphs: [{ kind: paragraphKind, marker: "", reason, lines: otherLines }] };
|
|
1209
|
-
};
|
|
1210
|
-
const value = comment.value.replace(/^\*/, "");
|
|
1211
|
-
if (LEGAL_COMMENT.test(replaceKeepingLines(comment.value, CODE_SPAN, "``"))) {
|
|
1212
|
-
return wholeComment("code", "legal comment");
|
|
1452
|
+
const value = comment.doc ? comment.value.slice(1) : comment.value.replace(/^\*/, "");
|
|
1453
|
+
if (isLegal(comment.value, language)) {
|
|
1454
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1213
1455
|
}
|
|
1214
|
-
if (LINE_DIRECTIVE.test(value) ||
|
|
1215
|
-
return wholeComment("code", "directive");
|
|
1456
|
+
if (LINE_DIRECTIVE.test(value) || language.blockDirective?.test(value)) {
|
|
1457
|
+
return wholeComment(shape, lines, "code", "directive");
|
|
1216
1458
|
}
|
|
1217
1459
|
const other = () => {
|
|
1218
1460
|
return wholeComment(
|
|
1461
|
+
shape,
|
|
1462
|
+
lines,
|
|
1219
1463
|
"other",
|
|
1220
1464
|
startLine === endLine ? "single-line block comment" : "unsupported block comment layout"
|
|
1221
1465
|
);
|
|
1222
1466
|
};
|
|
1223
|
-
if (startLine === endLine || !/^\s
|
|
1467
|
+
if (startLine === endLine || !/^\s*\/\*[*!]?$/.test(lines[0]) || !/^\s*\*\/$/.test(lines.at(-1) ?? "")) {
|
|
1224
1468
|
return other();
|
|
1225
1469
|
}
|
|
1226
1470
|
const body = [];
|
|
@@ -1239,7 +1483,52 @@ function shapeBlock(comment, sourceLines, lineStarts) {
|
|
|
1239
1483
|
hardBreak: HARD_BREAK.test(text)
|
|
1240
1484
|
});
|
|
1241
1485
|
}
|
|
1242
|
-
return {
|
|
1486
|
+
return { ...shape, paragraphs: segment(body, language) };
|
|
1487
|
+
}
|
|
1488
|
+
function shapeLineRun(group, sourceLines, lineStarts, language) {
|
|
1489
|
+
const startLine = lineOf(lineStarts, group[0].start);
|
|
1490
|
+
const endLine = startLine + group.length - 1;
|
|
1491
|
+
const kind = group[0].doc === "inner" ? "inner-line-doc" : "outer-line-doc";
|
|
1492
|
+
const shape = { kind, startLine, endLine };
|
|
1493
|
+
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1494
|
+
const values = group.map((comment) => comment.value.slice(1));
|
|
1495
|
+
if (isLegal(values.join("\n"), language)) {
|
|
1496
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1497
|
+
}
|
|
1498
|
+
if (LINE_DIRECTIVE.test(values[0])) {
|
|
1499
|
+
return wholeComment(shape, lines, "code", "directive");
|
|
1500
|
+
}
|
|
1501
|
+
const body = lines.map((line, offset) => {
|
|
1502
|
+
const [, marker, space, text] = /^(\s*\/\/[/!])( ?)(.*)$/.exec(line) ?? ["", "", "", line];
|
|
1503
|
+
return {
|
|
1504
|
+
index: startLine + offset,
|
|
1505
|
+
prefix: `${marker} `,
|
|
1506
|
+
text: text.trimEnd(),
|
|
1507
|
+
verbatim: !space && text.length > 0 || LINE_DIRECTIVE.test(text),
|
|
1508
|
+
hardBreak: HARD_BREAK.test(text)
|
|
1509
|
+
};
|
|
1510
|
+
});
|
|
1511
|
+
return { ...shape, paragraphs: segment(body, language) };
|
|
1512
|
+
}
|
|
1513
|
+
function shapeLineComment(group, sourceLines, lineStarts, language) {
|
|
1514
|
+
const startLine = lineOf(lineStarts, group[0].start);
|
|
1515
|
+
const endLine = startLine + group.length - 1;
|
|
1516
|
+
const shape = { kind: "line", startLine, endLine };
|
|
1517
|
+
const lines = sourceLines.slice(startLine, endLine + 1);
|
|
1518
|
+
if (isLegal(group.map((comment) => comment.value).join("\n"), language)) {
|
|
1519
|
+
return wholeComment(shape, lines, "code", "legal comment");
|
|
1520
|
+
}
|
|
1521
|
+
const body = lines.map((line, offset) => {
|
|
1522
|
+
const [, marker, space, text] = /^(\s*\/\/[/!]*)( ?)(.*)$/.exec(line) ?? ["", "", "", line];
|
|
1523
|
+
return {
|
|
1524
|
+
index: startLine + offset,
|
|
1525
|
+
prefix: `${marker}${space}`,
|
|
1526
|
+
text: text.trimEnd(),
|
|
1527
|
+
verbatim: LINE_DIRECTIVE.test(text),
|
|
1528
|
+
hardBreak: HARD_BREAK.test(text)
|
|
1529
|
+
};
|
|
1530
|
+
});
|
|
1531
|
+
return { ...shape, paragraphs: segment(body, { ...language, tags: false }) };
|
|
1243
1532
|
}
|
|
1244
1533
|
function ownsLines(comment, source, lineStarts) {
|
|
1245
1534
|
const startLine = lineOf(lineStarts, comment.start);
|
|
@@ -1249,12 +1538,12 @@ function ownsLines(comment, source, lineStarts) {
|
|
|
1249
1538
|
const after = source.slice(comment.end, lineEnd);
|
|
1250
1539
|
return /^[\t ]*$/.test(before) && /^[\t ]*\r?\n?$/.test(after);
|
|
1251
1540
|
}
|
|
1252
|
-
function singleLineLength(paragraph, options) {
|
|
1541
|
+
function singleLineLength(paragraph, options, language) {
|
|
1253
1542
|
const [first] = paragraph.lines;
|
|
1254
1543
|
if (paragraph.kind === "other" && paragraph.reason === "single-line block comment") {
|
|
1255
1544
|
return sourceLineLength(first, options.tabWidth);
|
|
1256
1545
|
}
|
|
1257
|
-
if (paragraph.kind !== "prose" || structureOf(paragraph.lines)) {
|
|
1546
|
+
if (paragraph.kind !== "prose" || structureOf(paragraph.lines, language)) {
|
|
1258
1547
|
return Infinity;
|
|
1259
1548
|
}
|
|
1260
1549
|
const text = tokenize(paragraph.lines.map((line) => line.text).join(" ")).join(" ");
|
|
@@ -1269,6 +1558,58 @@ function describeFailure(errors, lineStarts) {
|
|
|
1269
1558
|
const offset = first.labels.at(0)?.start;
|
|
1270
1559
|
return { message: first.message, line: offset === void 0 ? void 0 : lineOf(lineStarts, offset) + 1 };
|
|
1271
1560
|
}
|
|
1561
|
+
function scanJavaScript(filename, text, lineStarts) {
|
|
1562
|
+
const { program, comments, errors } = parseSource(filename, text);
|
|
1563
|
+
if (errors.length > 0 && program.body.length === 0) {
|
|
1564
|
+
return { comments: [], syntaxError: describeFailure(errors, lineStarts) };
|
|
1565
|
+
}
|
|
1566
|
+
return {
|
|
1567
|
+
comments: comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")))
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function scanRustSource(text, lineStarts) {
|
|
1571
|
+
const { comments, error } = scanRust(text);
|
|
1572
|
+
if (error) {
|
|
1573
|
+
return { comments: [], syntaxError: { message: error.message, line: lineOf(lineStarts, error.offset) + 1 } };
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
comments: comments.map((comment) => {
|
|
1577
|
+
return {
|
|
1578
|
+
type: comment.block ? "Block" : "Line",
|
|
1579
|
+
value: text.slice(comment.start + 2, comment.block ? comment.end - 2 : comment.end),
|
|
1580
|
+
start: comment.start,
|
|
1581
|
+
end: comment.end,
|
|
1582
|
+
doc: comment.doc
|
|
1583
|
+
};
|
|
1584
|
+
})
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function groupLineRuns(comments, text, lineStarts, markerOf) {
|
|
1588
|
+
const groups = [];
|
|
1589
|
+
let current;
|
|
1590
|
+
let previousLine = -1;
|
|
1591
|
+
let indent = "";
|
|
1592
|
+
let marker;
|
|
1593
|
+
for (const comment of comments) {
|
|
1594
|
+
const next = comment.type === "Line" ? markerOf(comment) : void 0;
|
|
1595
|
+
if (next === void 0 || !ownsLines(comment, text, lineStarts)) {
|
|
1596
|
+
current = void 0;
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
const line = lineOf(lineStarts, comment.start);
|
|
1600
|
+
const before = text.slice(lineStarts[line], comment.start);
|
|
1601
|
+
if (current && line === previousLine + 1 && next === marker && before === indent) {
|
|
1602
|
+
current.push(comment);
|
|
1603
|
+
} else {
|
|
1604
|
+
current = [comment];
|
|
1605
|
+
indent = before;
|
|
1606
|
+
marker = next;
|
|
1607
|
+
groups.push(current);
|
|
1608
|
+
}
|
|
1609
|
+
previousLine = line;
|
|
1610
|
+
}
|
|
1611
|
+
return groups;
|
|
1612
|
+
}
|
|
1272
1613
|
function analyzeSource(source, filename, options) {
|
|
1273
1614
|
return inspectSource(source, filename, options).comments;
|
|
1274
1615
|
}
|
|
@@ -1277,28 +1618,16 @@ function inspectSource(source, filename, options) {
|
|
|
1277
1618
|
throw new Error("comments: maxWidth must be an integer at least as large as width");
|
|
1278
1619
|
}
|
|
1279
1620
|
const text = source.startsWith(BYTE_ORDER_MARK) ? source.slice(1) : source;
|
|
1280
|
-
const
|
|
1621
|
+
const language = languageOf(filename);
|
|
1281
1622
|
const lineStarts = computeLineStarts(text);
|
|
1282
|
-
|
|
1283
|
-
|
|
1623
|
+
const scanned = language.name === "rust" ? scanRustSource(text, lineStarts) : scanJavaScript(filename, text, lineStarts);
|
|
1624
|
+
if (scanned.syntaxError) {
|
|
1625
|
+
return { comments: [], syntaxError: scanned.syntaxError };
|
|
1284
1626
|
}
|
|
1285
1627
|
const sourceLines = text.split("\n").map((line) => stripCarriageReturn(line));
|
|
1286
|
-
const parsed = comments.filter((comment) => !(comment.start === 0 && text.startsWith("#!")));
|
|
1287
1628
|
const reports = [];
|
|
1288
|
-
const
|
|
1289
|
-
return
|
|
1290
|
-
});
|
|
1291
|
-
for (const block of blocks) {
|
|
1292
|
-
const shape = shapeBlock(block, sourceLines, lineStarts);
|
|
1293
|
-
const singleLineWidth = Math.max(
|
|
1294
|
-
options.width,
|
|
1295
|
-
Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
|
|
1296
|
-
);
|
|
1297
|
-
const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
|
|
1298
|
-
const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options) : Infinity;
|
|
1299
|
-
const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
|
|
1300
|
-
const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
|
|
1301
|
-
reports.push({
|
|
1629
|
+
const report = (shape, lines, paragraphs) => {
|
|
1630
|
+
return {
|
|
1302
1631
|
file: filename,
|
|
1303
1632
|
kind: shape.kind,
|
|
1304
1633
|
startLine: shape.startLine + 1,
|
|
@@ -1308,8 +1637,47 @@ function inspectSource(source, filename, options) {
|
|
|
1308
1637
|
maxWidth: options.maxWidth,
|
|
1309
1638
|
lengths: lines.map((line) => visualLength(line.trimEnd(), options.tabWidth)),
|
|
1310
1639
|
text: lines.join("\n"),
|
|
1311
|
-
paragraphs
|
|
1640
|
+
paragraphs
|
|
1641
|
+
};
|
|
1642
|
+
};
|
|
1643
|
+
const blocks = scanned.comments.filter((comment) => {
|
|
1644
|
+
return comment.type === "Block" && ownsLines(comment, text, lineStarts) && !comment.value.includes(IGNORE_DIRECTIVE);
|
|
1645
|
+
});
|
|
1646
|
+
const runs = (markerOf) => {
|
|
1647
|
+
return groupLineRuns(scanned.comments, text, lineStarts, markerOf).filter((group) => {
|
|
1648
|
+
return !group.some((comment) => comment.value.includes(IGNORE_DIRECTIVE));
|
|
1312
1649
|
});
|
|
1650
|
+
};
|
|
1651
|
+
const shapes = [
|
|
1652
|
+
...blocks.map((block) => shapeBlock(block, sourceLines, lineStarts, language)),
|
|
1653
|
+
...runs((comment) => comment.doc).map((group) => shapeLineRun(group, sourceLines, lineStarts, language)),
|
|
1654
|
+
...runs((comment) => comment.doc ? void 0 : "//").map(
|
|
1655
|
+
(group) => shapeLineComment(group, sourceLines, lineStarts, language)
|
|
1656
|
+
)
|
|
1657
|
+
].toSorted((a, b) => a.startLine - b.startLine);
|
|
1658
|
+
for (const shape of shapes) {
|
|
1659
|
+
const lines = sourceLines.slice(shape.startLine, shape.endLine + 1);
|
|
1660
|
+
if (shape.kind === "line") {
|
|
1661
|
+
const paragraphs = shape.paragraphs.map((paragraph) => analyzeLineParagraph(paragraph, options, language));
|
|
1662
|
+
if (paragraphs.some((paragraph) => paragraph.issues.length > 0)) {
|
|
1663
|
+
reports.push(report(shape, lines, paragraphs));
|
|
1664
|
+
}
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
const singleLineWidth = Math.max(
|
|
1668
|
+
options.width,
|
|
1669
|
+
Math.min(options.singleLineWidth ?? options.width, options.maxWidth ?? Infinity)
|
|
1670
|
+
);
|
|
1671
|
+
const singleLineLimit = Math.min(singleLineWidth + options.tolerance, options.maxWidth ?? Infinity);
|
|
1672
|
+
const singleLine = shape.paragraphs.length === 1 ? singleLineLength(shape.paragraphs[0], options, language) : Infinity;
|
|
1673
|
+
const paragraphOptions = singleLine <= singleLineLimit ? { ...options, width: Math.max(singleLineWidth, singleLine) } : options;
|
|
1674
|
+
reports.push(
|
|
1675
|
+
report(
|
|
1676
|
+
shape,
|
|
1677
|
+
lines,
|
|
1678
|
+
shape.paragraphs.map((paragraph) => analyzeParagraph(paragraph, paragraphOptions, language))
|
|
1679
|
+
)
|
|
1680
|
+
);
|
|
1313
1681
|
}
|
|
1314
1682
|
return { comments: reports };
|
|
1315
1683
|
}
|
|
@@ -1330,6 +1698,9 @@ function rewrapComment(report) {
|
|
|
1330
1698
|
}
|
|
1331
1699
|
var POSITION_KEYS = /* @__PURE__ */ new Set(["start", "end", "range", "loc"]);
|
|
1332
1700
|
function codeFingerprint(source, filename) {
|
|
1701
|
+
if (languageOf(filename).name === "rust") {
|
|
1702
|
+
return rustFingerprint(source);
|
|
1703
|
+
}
|
|
1333
1704
|
const { program, errors } = parseSource(filename, source);
|
|
1334
1705
|
if (errors.length > 0) {
|
|
1335
1706
|
return JSON.stringify({ source, errors: errors.map((error) => error.message) });
|
|
@@ -1343,7 +1714,7 @@ function codeFingerprint(source, filename) {
|
|
|
1343
1714
|
// src/comments/agent.ts
|
|
1344
1715
|
import { spawn } from "child_process";
|
|
1345
1716
|
import { readFileSync } from "fs";
|
|
1346
|
-
import
|
|
1717
|
+
import path2 from "path";
|
|
1347
1718
|
var DEFAULT_RULES = `
|
|
1348
1719
|
# Comment rules
|
|
1349
1720
|
|
|
@@ -1351,7 +1722,7 @@ var DEFAULT_RULES = `
|
|
|
1351
1722
|
|
|
1352
1723
|
When rules pull in different directions, the earlier one wins.
|
|
1353
1724
|
|
|
1354
|
-
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,
|
|
1725
|
+
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).
|
|
1355
1726
|
2. Keep the meaning. An edit may make a comment clearer, never different.
|
|
1356
1727
|
3. Follow the style preferences below.
|
|
1357
1728
|
4. Change as little as possible. Wording that already reads clearly stays, even when a note points at it.
|
|
@@ -1364,10 +1735,12 @@ When rules pull in different directions, the earlier one wins.
|
|
|
1364
1735
|
- 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.
|
|
1365
1736
|
- Separate paragraphs with an empty comment line.
|
|
1366
1737
|
- Never split a backtick code span across lines.
|
|
1738
|
+
- 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.
|
|
1367
1739
|
- 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.
|
|
1368
1740
|
- 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.
|
|
1369
1741
|
- 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.
|
|
1370
1742
|
- 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.
|
|
1743
|
+
- 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.
|
|
1371
1744
|
|
|
1372
1745
|
## Style
|
|
1373
1746
|
|
|
@@ -1378,8 +1751,16 @@ These are preferences for comment prose. They never apply to code or exact text
|
|
|
1378
1751
|
- 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.
|
|
1379
1752
|
- 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*\` \`\`.
|
|
1380
1753
|
- 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.
|
|
1381
|
-
- Block and
|
|
1382
|
-
- Never turn a block or JSDoc comment into \`//\` line comments, and never write a run of consecutive \`//\` lines.
|
|
1754
|
+
- Block, JSDoc, and Rust doc comments are capitalized and end with punctuation.
|
|
1755
|
+
- 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.
|
|
1756
|
+
|
|
1757
|
+
## Rust doc comments
|
|
1758
|
+
|
|
1759
|
+
Rust doc comments are Markdown that rustdoc renders.
|
|
1760
|
+
|
|
1761
|
+
- 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)\`.
|
|
1762
|
+
- A link reference definition, like \`\` [\`Foo\`]: crate::Foo \`\`, stays on its own line as it is. Its colon is syntax, not a label.
|
|
1763
|
+
- 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.
|
|
1383
1764
|
|
|
1384
1765
|
## Editing
|
|
1385
1766
|
|
|
@@ -1388,11 +1769,11 @@ These are preferences for comment prose. They never apply to code or exact text
|
|
|
1388
1769
|
`.trim();
|
|
1389
1770
|
|
|
1390
1771
|
// src/comments/config.ts
|
|
1391
|
-
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1392
|
-
import { createRequire } from "module";
|
|
1393
|
-
import
|
|
1772
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1773
|
+
import { createRequire as createRequire2 } from "module";
|
|
1774
|
+
import path3 from "path";
|
|
1394
1775
|
import { pathToFileURL } from "url";
|
|
1395
|
-
var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"]);
|
|
1776
|
+
var DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".rs"]);
|
|
1396
1777
|
var CONFIG_FILES = [
|
|
1397
1778
|
"comments.config.ts",
|
|
1398
1779
|
"comments.config.mts",
|
|
@@ -1443,10 +1824,12 @@ function validateConfig(raw, filepath) {
|
|
|
1443
1824
|
assertType(config.avoidSentenceOrphans, "boolean", "avoidSentenceOrphans", filepath);
|
|
1444
1825
|
assertType(config.style, "boolean", "style", filepath);
|
|
1445
1826
|
assertType(config.extensions, "array", "extensions", filepath);
|
|
1827
|
+
assertType(config.include, "array", "include", filepath);
|
|
1446
1828
|
assertType(config.ignore, "array", "ignore", filepath);
|
|
1447
1829
|
assertType(config.agent, "object", "agent", filepath);
|
|
1448
1830
|
assertType(config.hook, "object", "hook", filepath);
|
|
1449
1831
|
assertStrings(config.extensions, "extensions", filepath);
|
|
1832
|
+
assertStrings(config.include, "include", filepath);
|
|
1450
1833
|
assertStrings(config.ignore, "ignore", filepath);
|
|
1451
1834
|
for (const key of ["width", "maxWidth", "singleLineWidth", "tolerance"]) {
|
|
1452
1835
|
const value = config[key];
|
|
@@ -1479,8 +1862,8 @@ function validateConfig(raw, filepath) {
|
|
|
1479
1862
|
}
|
|
1480
1863
|
assertType(agent.rules, "string", "agent.rules", filepath);
|
|
1481
1864
|
assertType(agent.instructions, "string", "agent.instructions", filepath);
|
|
1482
|
-
if (agent.scope !== void 0 &&
|
|
1483
|
-
throw new Error(`comments: \`agent.scope\` in ${filepath} must be
|
|
1865
|
+
if (agent.scope !== void 0 && !["suspicious", "failing", "all"].includes(agent.scope)) {
|
|
1866
|
+
throw new Error(`comments: \`agent.scope\` in ${filepath} must be one of suspicious, failing, all`);
|
|
1484
1867
|
}
|
|
1485
1868
|
const hook = config.hook ?? {};
|
|
1486
1869
|
if (hook.mode !== void 0 && !["agent", "report", "off"].includes(hook.mode)) {
|
|
@@ -1490,25 +1873,25 @@ function validateConfig(raw, filepath) {
|
|
|
1490
1873
|
}
|
|
1491
1874
|
async function loadConfig(cwd, options = {}) {
|
|
1492
1875
|
if (options.explicitPath) {
|
|
1493
|
-
const filepath =
|
|
1876
|
+
const filepath = path3.resolve(cwd, options.explicitPath);
|
|
1494
1877
|
return { config: validateConfig(await readConfigFile(filepath), filepath), filepath };
|
|
1495
1878
|
}
|
|
1496
|
-
let directory =
|
|
1879
|
+
let directory = path3.resolve(cwd);
|
|
1497
1880
|
for (; ; ) {
|
|
1498
1881
|
for (const name of CONFIG_FILES) {
|
|
1499
|
-
const filepath =
|
|
1500
|
-
if (
|
|
1882
|
+
const filepath = path3.join(directory, name);
|
|
1883
|
+
if (existsSync2(filepath)) {
|
|
1501
1884
|
return { config: validateConfig(await readConfigFile(filepath), filepath), filepath };
|
|
1502
1885
|
}
|
|
1503
1886
|
}
|
|
1504
|
-
const packageJsonPath =
|
|
1505
|
-
if (
|
|
1887
|
+
const packageJsonPath = path3.join(directory, "package.json");
|
|
1888
|
+
if (existsSync2(packageJsonPath)) {
|
|
1506
1889
|
const value = readJson(packageJsonPath)[PACKAGE_JSON_KEY];
|
|
1507
1890
|
if (value !== void 0) {
|
|
1508
1891
|
return { config: validateConfig(value, packageJsonPath), filepath: packageJsonPath };
|
|
1509
1892
|
}
|
|
1510
1893
|
}
|
|
1511
|
-
const parent =
|
|
1894
|
+
const parent = path3.dirname(directory);
|
|
1512
1895
|
if (directory === options.stopDir || parent === directory) {
|
|
1513
1896
|
return { config: {} };
|
|
1514
1897
|
}
|