eslint-plugin-comment-policy 0.2.0 → 0.4.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/AGENTS.md +4 -2
- package/README.md +33 -8
- package/README.ru.md +29 -7
- package/dist/index.cjs +113 -11
- package/dist/index.mjs +113 -11
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# AGENTS.md — adopting eslint-plugin-comment-policy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Six ESLint 9 (flat-config) rules enforcing a comment policy, namespace
|
|
4
4
|
`comment-policy/`:
|
|
5
5
|
|
|
6
6
|
- `comment-policy/max-comment-lines` — prose-line cap per comment block
|
|
7
7
|
(default `max` 4, `anchoredMax` 3).
|
|
8
8
|
- `comment-policy/no-comment-narrative` — change-narrative / history prose.
|
|
9
9
|
- `comment-policy/no-comment-code-snippet` — code snippet inside a comment.
|
|
10
|
+
- `comment-policy/no-consecutive-comments` — several stand-alone comments in a
|
|
11
|
+
row (default `max` 1, `types` line+block, `skipBlankLines` true).
|
|
10
12
|
- `comment-policy/no-decorative-comment` — decorative / section markers.
|
|
11
13
|
- `comment-policy/no-line-comment` — `//` forbidden, `/* */` required.
|
|
12
14
|
|
|
@@ -20,7 +22,7 @@ Five ESLint 9 (flat-config) rules enforcing a comment policy, namespace
|
|
|
20
22
|
export default [commentPolicy.configs.recommended];
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
`recommended` turns **all
|
|
25
|
+
`recommended` turns **all six rules on at `error`** with defaults.
|
|
24
26
|
|
|
25
27
|
## Rule behavior an agent must know
|
|
26
28
|
|
package/README.md
CHANGED
|
@@ -16,13 +16,18 @@ Namespace: `comment-policy/`. Plugin scope (`meta.name`): `cyberash`.
|
|
|
16
16
|
| `comment-policy/max-comment-lines` | a comment block with more prose lines than the cap (lower cap for anchored blocks) | no |
|
|
17
17
|
| `comment-policy/no-comment-narrative` | change-narrative / history prose (`renamed from`, `previously`, `v1.2`, bare ISO dates, …) | no |
|
|
18
18
|
| `comment-policy/no-comment-code-snippet` | a code snippet (usage example) inside a comment | yes (only when the block is entirely code) |
|
|
19
|
-
| `comment-policy/no-
|
|
19
|
+
| `comment-policy/no-consecutive-comments` | several stand-alone comments stacked one after another (more than `max`) | no |
|
|
20
|
+
| `comment-policy/no-decorative-comment` | decorative / section markers, ASCII and Unicode (`=====`, `#region`, `===text===`, `────`, `══ text ══`) | yes |
|
|
20
21
|
| `comment-policy/no-line-comment` | any `//` comment; requires `/* */` | yes (converts and merges runs of `//`) |
|
|
21
22
|
|
|
22
23
|
A **comment block** is a run of consecutive full-line `//` comments separated
|
|
23
24
|
only by whitespace (no blank line). A **prose line** is a comment line that
|
|
24
25
|
still has a real word (≥3 letters) after comment markers and protected markers
|
|
25
|
-
are stripped, so anchor/marker-only lines do not count toward the cap.
|
|
26
|
+
are stripped, so anchor/marker-only lines do not count toward the cap. A
|
|
27
|
+
**comment run** (used by `no-consecutive-comments`) is a sequence of full-line
|
|
28
|
+
comments of an enabled kind separated only by whitespace; a multi-line `/* */`
|
|
29
|
+
counts as one comment, and code or a comment of a non-enabled kind breaks the
|
|
30
|
+
run.
|
|
26
31
|
|
|
27
32
|
## Install
|
|
28
33
|
|
|
@@ -40,7 +45,7 @@ import commentPolicy from "eslint-plugin-comment-policy";
|
|
|
40
45
|
export default [commentPolicy.configs.recommended];
|
|
41
46
|
```
|
|
42
47
|
|
|
43
|
-
`recommended` turns all
|
|
48
|
+
`recommended` turns all six rules on at `error` with defaults.
|
|
44
49
|
|
|
45
50
|
Or register the plugin and enable rules explicitly:
|
|
46
51
|
|
|
@@ -54,6 +59,7 @@ export default [
|
|
|
54
59
|
"comment-policy/max-comment-lines": ["error", { max: 4, anchoredMax: 3 }],
|
|
55
60
|
"comment-policy/no-comment-narrative": "error",
|
|
56
61
|
"comment-policy/no-comment-code-snippet": "error",
|
|
62
|
+
"comment-policy/no-consecutive-comments": "error",
|
|
57
63
|
"comment-policy/no-decorative-comment": "error",
|
|
58
64
|
"comment-policy/no-line-comment": "error",
|
|
59
65
|
},
|
|
@@ -66,10 +72,12 @@ export default [
|
|
|
66
72
|
### `protectedPatterns`
|
|
67
73
|
|
|
68
74
|
Shared across `max-comment-lines`, `no-comment-narrative`,
|
|
69
|
-
`no-comment-code-snippet`, `no-
|
|
70
|
-
array of regular-expression **source strings**. A block
|
|
71
|
-
is "protected": it gets the lower cap in
|
|
72
|
-
`
|
|
75
|
+
`no-comment-code-snippet`, `no-consecutive-comments`, `no-decorative-comment`,
|
|
76
|
+
and `no-line-comment`. An array of regular-expression **source strings**. A block
|
|
77
|
+
that matches any pattern is "protected": it gets the lower cap in
|
|
78
|
+
`max-comment-lines`, is exempt from `no-comment-narrative` /
|
|
79
|
+
`no-comment-code-snippet` / `no-decorative-comment`, and is excluded from
|
|
80
|
+
`no-consecutive-comments` runs.
|
|
73
81
|
|
|
74
82
|
Order matters: put the longer/more specific pattern first so a marker is fully
|
|
75
83
|
stripped before a shorter pattern that is its suffix.
|
|
@@ -92,6 +100,21 @@ stripped before a shorter pattern that is its suffix.
|
|
|
92
100
|
- `extraPatterns` — extra narrative patterns (source strings) added to the
|
|
93
101
|
built-in set.
|
|
94
102
|
|
|
103
|
+
### `no-consecutive-comments`
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
["error", { types: ["line", "block"], max: 1, skipBlankLines: true, protectedPatterns: [] }]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `types` (default `["line", "block"]`) — which comment kinds participate in a
|
|
110
|
+
run: `line` (`//`) and/or `block` (`/* */`). A comment of a kind not listed
|
|
111
|
+
breaks the run.
|
|
112
|
+
- `max` (default `1`) — how many consecutive comments are allowed; a run longer
|
|
113
|
+
than `max` is reported (with `max: 1`, any second comment in a row is flagged).
|
|
114
|
+
- `skipBlankLines` (default `true`) — when `true`, comments separated only by
|
|
115
|
+
blank lines still count as consecutive; set `false` to let a blank line break
|
|
116
|
+
the run.
|
|
117
|
+
|
|
95
118
|
### `no-comment-code-snippet`, `no-decorative-comment`, `no-line-comment`
|
|
96
119
|
|
|
97
120
|
```js
|
|
@@ -106,7 +129,9 @@ when its prose contains `*/` (which would terminate the block early).
|
|
|
106
129
|
## Notes
|
|
107
130
|
|
|
108
131
|
- `no-decorative-comment` detects markers by content in both `//` and `/* */`
|
|
109
|
-
comment forms.
|
|
132
|
+
comment forms. The divider charset covers ASCII (`= * # _ - ~`) and Unicode
|
|
133
|
+
box-drawing / dash runs (`─ ━ ═ │ ┃ ║`, en/em-dash), so banners like
|
|
134
|
+
`/* ── Section ────── */` are caught; a single such glyph inside prose is not.
|
|
110
135
|
- ESLint applies non-overlapping fixes per pass and re-lints, so a block that is
|
|
111
136
|
both a code snippet and a line comment is resolved across passes.
|
|
112
137
|
|
package/README.ru.md
CHANGED
|
@@ -16,13 +16,18 @@
|
|
|
16
16
|
| `comment-policy/max-comment-lines` | блок комментария, где прозаических строк больше капа (для анкорных блоков кап ниже) | нет |
|
|
17
17
|
| `comment-policy/no-comment-narrative` | change-narrative / историю (`renamed from`, `previously`, `v1.2`, голые ISO-даты, …) | нет |
|
|
18
18
|
| `comment-policy/no-comment-code-snippet` | код-сниппет (пример использования) внутри комментария | да (только если блок целиком код) |
|
|
19
|
+
| `comment-policy/no-consecutive-comments` | несколько отдельных комментариев подряд (больше `max`) | нет |
|
|
19
20
|
| `comment-policy/no-decorative-comment` | декоративные / секционные маркеры (`=====`, `#region`, `===text===`) | да |
|
|
20
21
|
| `comment-policy/no-line-comment` | любой `//`; требует `/* */` | да (конвертация и склейка подряд идущих `//`) |
|
|
21
22
|
|
|
22
23
|
**Блок комментария** — подряд идущие full-line `//`, разделённые только
|
|
23
24
|
пробелами (без пустой строки). **Прозаическая строка** — строка комментария, в
|
|
24
25
|
которой после снятия маркеров комментария и protected-маркеров остаётся реальное
|
|
25
|
-
слово (≥3 букв); поэтому чисто анкорные/маркерные строки в кап не идут.
|
|
26
|
+
слово (≥3 букв); поэтому чисто анкорные/маркерные строки в кап не идут. **Серия
|
|
27
|
+
комментариев** (для `no-consecutive-comments`) — последовательность full-line
|
|
28
|
+
комментариев включённого типа, разделённых только пробелами; многострочный
|
|
29
|
+
`/* */` считается одним комментарием, а код или комментарий невключённого типа
|
|
30
|
+
разрывает серию.
|
|
26
31
|
|
|
27
32
|
## Установка
|
|
28
33
|
|
|
@@ -40,7 +45,7 @@ import commentPolicy from "eslint-plugin-comment-policy";
|
|
|
40
45
|
export default [commentPolicy.configs.recommended];
|
|
41
46
|
```
|
|
42
47
|
|
|
43
|
-
`recommended` включает все
|
|
48
|
+
`recommended` включает все шесть правил на `error` с дефолтами.
|
|
44
49
|
|
|
45
50
|
Либо подключить плагин и включить правила вручную:
|
|
46
51
|
|
|
@@ -54,6 +59,7 @@ export default [
|
|
|
54
59
|
"comment-policy/max-comment-lines": ["error", { max: 4, anchoredMax: 3 }],
|
|
55
60
|
"comment-policy/no-comment-narrative": "error",
|
|
56
61
|
"comment-policy/no-comment-code-snippet": "error",
|
|
62
|
+
"comment-policy/no-consecutive-comments": "error",
|
|
57
63
|
"comment-policy/no-decorative-comment": "error",
|
|
58
64
|
"comment-policy/no-line-comment": "error",
|
|
59
65
|
},
|
|
@@ -66,11 +72,12 @@ export default [
|
|
|
66
72
|
### `protectedPatterns`
|
|
67
73
|
|
|
68
74
|
Общая для `max-comment-lines`, `no-comment-narrative`,
|
|
69
|
-
`no-comment-code-snippet`, `no-
|
|
70
|
-
**строк-исходников** регулярных выражений. Блок,
|
|
71
|
-
считается «защищённым»: получает пониженный кап в
|
|
72
|
-
|
|
73
|
-
`no-decorative-comment
|
|
75
|
+
`no-comment-code-snippet`, `no-consecutive-comments`, `no-decorative-comment` и
|
|
76
|
+
`no-line-comment`. Массив **строк-исходников** регулярных выражений. Блок,
|
|
77
|
+
совпавший с любым паттерном, считается «защищённым»: получает пониженный кап в
|
|
78
|
+
`max-comment-lines`, исключается из `no-comment-narrative` /
|
|
79
|
+
`no-comment-code-snippet` / `no-decorative-comment` и не учитывается в сериях
|
|
80
|
+
`no-consecutive-comments`.
|
|
74
81
|
|
|
75
82
|
Порядок важен: более длинный/специфичный паттерн ставьте раньше, чтобы маркер
|
|
76
83
|
снимался целиком до более короткого паттерна, который является его суффиксом.
|
|
@@ -93,6 +100,21 @@ export default [
|
|
|
93
100
|
- `extraPatterns` — дополнительные narrative-паттерны (строки-исходники) к
|
|
94
101
|
встроенному набору.
|
|
95
102
|
|
|
103
|
+
### `no-consecutive-comments`
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
["error", { types: ["line", "block"], max: 1, skipBlankLines: true, protectedPatterns: [] }]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `types` (деф. `["line", "block"]`) — какие типы комментариев участвуют в серии:
|
|
110
|
+
`line` (`//`) и/или `block` (`/* */`). Комментарий типа не из списка разрывает
|
|
111
|
+
серию.
|
|
112
|
+
- `max` (деф. `1`) — сколько комментариев подряд допустимо; серия длиннее `max`
|
|
113
|
+
репортится (при `max: 1` ловится любой второй комментарий подряд).
|
|
114
|
+
- `skipBlankLines` (деф. `true`) — при `true` комментарии, разделённые только
|
|
115
|
+
пустыми строками, всё равно считаются подряд идущими; `false` — пустая строка
|
|
116
|
+
разрывает серию.
|
|
117
|
+
|
|
96
118
|
### `no-comment-code-snippet`, `no-decorative-comment`, `no-line-comment`
|
|
97
119
|
|
|
98
120
|
```js
|
package/dist/index.cjs
CHANGED
|
@@ -86,9 +86,9 @@ function blockLoc(block) {
|
|
|
86
86
|
}
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region src/rules/max-comment-lines.ts
|
|
89
|
-
const DEFAULT_MAX = 4;
|
|
89
|
+
const DEFAULT_MAX$1 = 4;
|
|
90
90
|
const DEFAULT_ANCHORED_MAX = 3;
|
|
91
|
-
const rule$
|
|
91
|
+
const rule$5 = {
|
|
92
92
|
defaultOptions: [{}],
|
|
93
93
|
meta: {
|
|
94
94
|
type: "suggestion",
|
|
@@ -121,7 +121,7 @@ const rule$4 = {
|
|
|
121
121
|
},
|
|
122
122
|
create(context) {
|
|
123
123
|
const option = context.options[0] ?? {};
|
|
124
|
-
const max = option.max ?? DEFAULT_MAX;
|
|
124
|
+
const max = option.max ?? DEFAULT_MAX$1;
|
|
125
125
|
const anchoredMax = option.anchoredMax ?? DEFAULT_ANCHORED_MAX;
|
|
126
126
|
const { detect, strip } = compileProtected(option.protectedPatterns ?? []);
|
|
127
127
|
const sourceCode = context.sourceCode;
|
|
@@ -165,7 +165,7 @@ function snippetInfo(block) {
|
|
|
165
165
|
pure: nonEmpty.length >= 2 && codeish.length === nonEmpty.length
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
|
-
const rule$
|
|
168
|
+
const rule$4 = {
|
|
169
169
|
defaultOptions: [{}],
|
|
170
170
|
meta: {
|
|
171
171
|
type: "suggestion",
|
|
@@ -216,7 +216,7 @@ const NARRATIVE = [
|
|
|
216
216
|
/\bv\d+\.\d+\b/i,
|
|
217
217
|
/\b\d{4}-\d{2}-\d{2}\b/
|
|
218
218
|
];
|
|
219
|
-
const rule$
|
|
219
|
+
const rule$3 = {
|
|
220
220
|
defaultOptions: [{}],
|
|
221
221
|
meta: {
|
|
222
222
|
type: "suggestion",
|
|
@@ -258,11 +258,111 @@ const rule$2 = {
|
|
|
258
258
|
}
|
|
259
259
|
};
|
|
260
260
|
//#endregion
|
|
261
|
+
//#region src/lib/comment-runs.ts
|
|
262
|
+
function commentRuns(sourceCode, options) {
|
|
263
|
+
const text = sourceCode.text;
|
|
264
|
+
const isFullLine = (c) => {
|
|
265
|
+
const lineText = sourceCode.lines[c.loc.start.line - 1] ?? "";
|
|
266
|
+
return /^\s*$/.test(lineText.slice(0, c.loc.start.column));
|
|
267
|
+
};
|
|
268
|
+
const isCounted = (c) => {
|
|
269
|
+
const kind = c.type === "Line" ? "line" : "block";
|
|
270
|
+
return options.types.has(kind) && isFullLine(c) && !hasProtectedToken(sourceCode.getText(c), options.detect);
|
|
271
|
+
};
|
|
272
|
+
const isAdjacent = (prev, curr) => {
|
|
273
|
+
const between = text.slice(prev.range[1], curr.range[0]);
|
|
274
|
+
return options.skipBlankLines ? /^\s*$/.test(between) : onlyWhitespaceNoBlank(between);
|
|
275
|
+
};
|
|
276
|
+
const runs = [];
|
|
277
|
+
let current = [];
|
|
278
|
+
const flush = () => {
|
|
279
|
+
if (current.length === 0) return;
|
|
280
|
+
runs.push({
|
|
281
|
+
comments: current,
|
|
282
|
+
loc: {
|
|
283
|
+
start: current[0].loc.start,
|
|
284
|
+
end: current[current.length - 1].loc.end
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
current = [];
|
|
288
|
+
};
|
|
289
|
+
for (const c of sourceCode.getAllComments()) {
|
|
290
|
+
if (!isCounted(c)) {
|
|
291
|
+
flush();
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (current.length > 0 && !isAdjacent(current[current.length - 1], c)) flush();
|
|
295
|
+
current.push(c);
|
|
296
|
+
}
|
|
297
|
+
flush();
|
|
298
|
+
return runs;
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/rules/no-consecutive-comments.ts
|
|
302
|
+
const DEFAULT_MAX = 1;
|
|
303
|
+
const DEFAULT_TYPES = ["line", "block"];
|
|
304
|
+
const rule$2 = {
|
|
305
|
+
defaultOptions: [{}],
|
|
306
|
+
meta: {
|
|
307
|
+
type: "suggestion",
|
|
308
|
+
docs: {
|
|
309
|
+
description: "Forbid several stand-alone comments stacked one after another; merge or remove the extras.",
|
|
310
|
+
url: "https://github.com/cyberash-dev/eslint-plugin-comment-policy#no-consecutive-comments"
|
|
311
|
+
},
|
|
312
|
+
schema: [{
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
types: {
|
|
316
|
+
type: "array",
|
|
317
|
+
items: {
|
|
318
|
+
type: "string",
|
|
319
|
+
enum: ["line", "block"]
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
max: {
|
|
323
|
+
type: "integer",
|
|
324
|
+
minimum: 1
|
|
325
|
+
},
|
|
326
|
+
skipBlankLines: { type: "boolean" },
|
|
327
|
+
protectedPatterns: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: { type: "string" }
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
additionalProperties: false
|
|
333
|
+
}],
|
|
334
|
+
messages: { consecutiveComments: "{{count}} consecutive comments (> {{max}}); merge them into one or remove the extras" }
|
|
335
|
+
},
|
|
336
|
+
create(context) {
|
|
337
|
+
const option = context.options[0] ?? {};
|
|
338
|
+
const max = option.max ?? DEFAULT_MAX;
|
|
339
|
+
const skipBlankLines = option.skipBlankLines ?? true;
|
|
340
|
+
const types = new Set(option.types ?? DEFAULT_TYPES);
|
|
341
|
+
const { detect } = compileProtected(option.protectedPatterns ?? []);
|
|
342
|
+
const sourceCode = context.sourceCode;
|
|
343
|
+
return { "Program:exit"() {
|
|
344
|
+
for (const run of commentRuns(sourceCode, {
|
|
345
|
+
types,
|
|
346
|
+
skipBlankLines,
|
|
347
|
+
detect
|
|
348
|
+
})) if (run.comments.length > max) context.report({
|
|
349
|
+
loc: run.loc,
|
|
350
|
+
messageId: "consecutiveComments",
|
|
351
|
+
data: {
|
|
352
|
+
count: run.comments.length,
|
|
353
|
+
max
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
} };
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
//#endregion
|
|
261
360
|
//#region src/lib/decorative.ts
|
|
361
|
+
const DIV = "=*#_~\\-\\u2013\\u2014\\u2500-\\u257F";
|
|
262
362
|
const DECORATIVE = [
|
|
263
|
-
|
|
363
|
+
new RegExp(`^[${DIV}]{3,}$`),
|
|
264
364
|
/^#?\s*(?:region|endregion)\b/i,
|
|
265
|
-
|
|
365
|
+
new RegExp(`^[${DIV}]{2,}.*[${DIV}]{2,}$`)
|
|
266
366
|
];
|
|
267
367
|
function isDecorativeLine(content) {
|
|
268
368
|
return content.length > 0 && DECORATIVE.some((re) => re.test(content));
|
|
@@ -394,12 +494,13 @@ const configs = {};
|
|
|
394
494
|
const plugin = {
|
|
395
495
|
meta: {
|
|
396
496
|
name: "cyberash",
|
|
397
|
-
version: "0.
|
|
497
|
+
version: "0.3.0"
|
|
398
498
|
},
|
|
399
499
|
rules: {
|
|
400
|
-
"max-comment-lines": rule$
|
|
401
|
-
"no-comment-narrative": rule$
|
|
402
|
-
"no-comment-code-snippet": rule$
|
|
500
|
+
"max-comment-lines": rule$5,
|
|
501
|
+
"no-comment-narrative": rule$3,
|
|
502
|
+
"no-comment-code-snippet": rule$4,
|
|
503
|
+
"no-consecutive-comments": rule$2,
|
|
403
504
|
"no-decorative-comment": rule$1,
|
|
404
505
|
"no-line-comment": rule
|
|
405
506
|
},
|
|
@@ -414,6 +515,7 @@ configs.recommended = {
|
|
|
414
515
|
}],
|
|
415
516
|
"comment-policy/no-comment-narrative": ["error"],
|
|
416
517
|
"comment-policy/no-comment-code-snippet": ["error"],
|
|
518
|
+
"comment-policy/no-consecutive-comments": ["error"],
|
|
417
519
|
"comment-policy/no-decorative-comment": ["error"],
|
|
418
520
|
"comment-policy/no-line-comment": ["error"]
|
|
419
521
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -86,9 +86,9 @@ function blockLoc(block) {
|
|
|
86
86
|
}
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region src/rules/max-comment-lines.ts
|
|
89
|
-
const DEFAULT_MAX = 4;
|
|
89
|
+
const DEFAULT_MAX$1 = 4;
|
|
90
90
|
const DEFAULT_ANCHORED_MAX = 3;
|
|
91
|
-
const rule$
|
|
91
|
+
const rule$5 = {
|
|
92
92
|
defaultOptions: [{}],
|
|
93
93
|
meta: {
|
|
94
94
|
type: "suggestion",
|
|
@@ -121,7 +121,7 @@ const rule$4 = {
|
|
|
121
121
|
},
|
|
122
122
|
create(context) {
|
|
123
123
|
const option = context.options[0] ?? {};
|
|
124
|
-
const max = option.max ?? DEFAULT_MAX;
|
|
124
|
+
const max = option.max ?? DEFAULT_MAX$1;
|
|
125
125
|
const anchoredMax = option.anchoredMax ?? DEFAULT_ANCHORED_MAX;
|
|
126
126
|
const { detect, strip } = compileProtected(option.protectedPatterns ?? []);
|
|
127
127
|
const sourceCode = context.sourceCode;
|
|
@@ -165,7 +165,7 @@ function snippetInfo(block) {
|
|
|
165
165
|
pure: nonEmpty.length >= 2 && codeish.length === nonEmpty.length
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
|
-
const rule$
|
|
168
|
+
const rule$4 = {
|
|
169
169
|
defaultOptions: [{}],
|
|
170
170
|
meta: {
|
|
171
171
|
type: "suggestion",
|
|
@@ -216,7 +216,7 @@ const NARRATIVE = [
|
|
|
216
216
|
/\bv\d+\.\d+\b/i,
|
|
217
217
|
/\b\d{4}-\d{2}-\d{2}\b/
|
|
218
218
|
];
|
|
219
|
-
const rule$
|
|
219
|
+
const rule$3 = {
|
|
220
220
|
defaultOptions: [{}],
|
|
221
221
|
meta: {
|
|
222
222
|
type: "suggestion",
|
|
@@ -258,11 +258,111 @@ const rule$2 = {
|
|
|
258
258
|
}
|
|
259
259
|
};
|
|
260
260
|
//#endregion
|
|
261
|
+
//#region src/lib/comment-runs.ts
|
|
262
|
+
function commentRuns(sourceCode, options) {
|
|
263
|
+
const text = sourceCode.text;
|
|
264
|
+
const isFullLine = (c) => {
|
|
265
|
+
const lineText = sourceCode.lines[c.loc.start.line - 1] ?? "";
|
|
266
|
+
return /^\s*$/.test(lineText.slice(0, c.loc.start.column));
|
|
267
|
+
};
|
|
268
|
+
const isCounted = (c) => {
|
|
269
|
+
const kind = c.type === "Line" ? "line" : "block";
|
|
270
|
+
return options.types.has(kind) && isFullLine(c) && !hasProtectedToken(sourceCode.getText(c), options.detect);
|
|
271
|
+
};
|
|
272
|
+
const isAdjacent = (prev, curr) => {
|
|
273
|
+
const between = text.slice(prev.range[1], curr.range[0]);
|
|
274
|
+
return options.skipBlankLines ? /^\s*$/.test(between) : onlyWhitespaceNoBlank(between);
|
|
275
|
+
};
|
|
276
|
+
const runs = [];
|
|
277
|
+
let current = [];
|
|
278
|
+
const flush = () => {
|
|
279
|
+
if (current.length === 0) return;
|
|
280
|
+
runs.push({
|
|
281
|
+
comments: current,
|
|
282
|
+
loc: {
|
|
283
|
+
start: current[0].loc.start,
|
|
284
|
+
end: current[current.length - 1].loc.end
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
current = [];
|
|
288
|
+
};
|
|
289
|
+
for (const c of sourceCode.getAllComments()) {
|
|
290
|
+
if (!isCounted(c)) {
|
|
291
|
+
flush();
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (current.length > 0 && !isAdjacent(current[current.length - 1], c)) flush();
|
|
295
|
+
current.push(c);
|
|
296
|
+
}
|
|
297
|
+
flush();
|
|
298
|
+
return runs;
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/rules/no-consecutive-comments.ts
|
|
302
|
+
const DEFAULT_MAX = 1;
|
|
303
|
+
const DEFAULT_TYPES = ["line", "block"];
|
|
304
|
+
const rule$2 = {
|
|
305
|
+
defaultOptions: [{}],
|
|
306
|
+
meta: {
|
|
307
|
+
type: "suggestion",
|
|
308
|
+
docs: {
|
|
309
|
+
description: "Forbid several stand-alone comments stacked one after another; merge or remove the extras.",
|
|
310
|
+
url: "https://github.com/cyberash-dev/eslint-plugin-comment-policy#no-consecutive-comments"
|
|
311
|
+
},
|
|
312
|
+
schema: [{
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
types: {
|
|
316
|
+
type: "array",
|
|
317
|
+
items: {
|
|
318
|
+
type: "string",
|
|
319
|
+
enum: ["line", "block"]
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
max: {
|
|
323
|
+
type: "integer",
|
|
324
|
+
minimum: 1
|
|
325
|
+
},
|
|
326
|
+
skipBlankLines: { type: "boolean" },
|
|
327
|
+
protectedPatterns: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: { type: "string" }
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
additionalProperties: false
|
|
333
|
+
}],
|
|
334
|
+
messages: { consecutiveComments: "{{count}} consecutive comments (> {{max}}); merge them into one or remove the extras" }
|
|
335
|
+
},
|
|
336
|
+
create(context) {
|
|
337
|
+
const option = context.options[0] ?? {};
|
|
338
|
+
const max = option.max ?? DEFAULT_MAX;
|
|
339
|
+
const skipBlankLines = option.skipBlankLines ?? true;
|
|
340
|
+
const types = new Set(option.types ?? DEFAULT_TYPES);
|
|
341
|
+
const { detect } = compileProtected(option.protectedPatterns ?? []);
|
|
342
|
+
const sourceCode = context.sourceCode;
|
|
343
|
+
return { "Program:exit"() {
|
|
344
|
+
for (const run of commentRuns(sourceCode, {
|
|
345
|
+
types,
|
|
346
|
+
skipBlankLines,
|
|
347
|
+
detect
|
|
348
|
+
})) if (run.comments.length > max) context.report({
|
|
349
|
+
loc: run.loc,
|
|
350
|
+
messageId: "consecutiveComments",
|
|
351
|
+
data: {
|
|
352
|
+
count: run.comments.length,
|
|
353
|
+
max
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
} };
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
//#endregion
|
|
261
360
|
//#region src/lib/decorative.ts
|
|
361
|
+
const DIV = "=*#_~\\-\\u2013\\u2014\\u2500-\\u257F";
|
|
262
362
|
const DECORATIVE = [
|
|
263
|
-
|
|
363
|
+
new RegExp(`^[${DIV}]{3,}$`),
|
|
264
364
|
/^#?\s*(?:region|endregion)\b/i,
|
|
265
|
-
|
|
365
|
+
new RegExp(`^[${DIV}]{2,}.*[${DIV}]{2,}$`)
|
|
266
366
|
];
|
|
267
367
|
function isDecorativeLine(content) {
|
|
268
368
|
return content.length > 0 && DECORATIVE.some((re) => re.test(content));
|
|
@@ -394,12 +494,13 @@ const configs = {};
|
|
|
394
494
|
const plugin = {
|
|
395
495
|
meta: {
|
|
396
496
|
name: "cyberash",
|
|
397
|
-
version: "0.
|
|
497
|
+
version: "0.3.0"
|
|
398
498
|
},
|
|
399
499
|
rules: {
|
|
400
|
-
"max-comment-lines": rule$
|
|
401
|
-
"no-comment-narrative": rule$
|
|
402
|
-
"no-comment-code-snippet": rule$
|
|
500
|
+
"max-comment-lines": rule$5,
|
|
501
|
+
"no-comment-narrative": rule$3,
|
|
502
|
+
"no-comment-code-snippet": rule$4,
|
|
503
|
+
"no-consecutive-comments": rule$2,
|
|
403
504
|
"no-decorative-comment": rule$1,
|
|
404
505
|
"no-line-comment": rule
|
|
405
506
|
},
|
|
@@ -414,6 +515,7 @@ configs.recommended = {
|
|
|
414
515
|
}],
|
|
415
516
|
"comment-policy/no-comment-narrative": ["error"],
|
|
416
517
|
"comment-policy/no-comment-code-snippet": ["error"],
|
|
518
|
+
"comment-policy/no-consecutive-comments": ["error"],
|
|
417
519
|
"comment-policy/no-decorative-comment": ["error"],
|
|
418
520
|
"comment-policy/no-line-comment": ["error"]
|
|
419
521
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-comment-policy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "ESLint rules enforcing a per-file comment policy: prose-line caps, no change-narrative, no code snippets, no decorative markers, block comments only (JS + TS).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|