@kazupon/eslint-plugin 0.5.0 → 0.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 +6 -5
- package/lib/index.d.ts +9 -0
- package/lib/index.js +366 -172
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -61,11 +61,12 @@ The rules with the following star ⭐ are included in the configs.
|
|
|
61
61
|
|
|
62
62
|
### @kazupon/eslint-plugin Rules
|
|
63
63
|
|
|
64
|
-
| Rule ID
|
|
65
|
-
|
|
|
66
|
-
| [@kazupon/enforce-header-comment](https://eslint-plugin.kazupon.dev/rules/enforce-header-comment.html)
|
|
67
|
-
| [@kazupon/no-tag-comments](https://eslint-plugin.kazupon.dev/rules/no-tag-comments.html)
|
|
68
|
-
| [@kazupon/prefer-
|
|
64
|
+
| Rule ID | Description | Category | Fixable | RECOMMENDED |
|
|
65
|
+
| :--------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | :------- | :-----: | :---------: |
|
|
66
|
+
| [@kazupon/enforce-header-comment](https://eslint-plugin.kazupon.dev/rules/enforce-header-comment.html) | Enforce heading the comment in source code file | Comment | | ⭐ |
|
|
67
|
+
| [@kazupon/no-tag-comments](https://eslint-plugin.kazupon.dev/rules/no-tag-comments.html) | disallow tag comments | Comment | | ⭐ |
|
|
68
|
+
| [@kazupon/prefer-inline-code-words-comments](https://eslint-plugin.kazupon.dev/rules/prefer-inline-code-words-comments.html) | enforce the use of inline code for specific words on comments | Comment | 🔧 | ⭐ |
|
|
69
|
+
| [@kazupon/prefer-scope-on-tag-comment](https://eslint-plugin.kazupon.dev/rules/prefer-scope-on-tag-comment.html) | enforce adding a scope to tag comments | Comment | | ⭐ |
|
|
69
70
|
|
|
70
71
|
<!--RULES_TABLE_END-->
|
|
71
72
|
|
package/lib/index.d.ts
CHANGED
|
@@ -10,8 +10,17 @@ declare const plugin: Omit<ESLint.Plugin, "configs"> & {
|
|
|
10
10
|
};
|
|
11
11
|
declare const recommendedConfig: Linter.Config[];
|
|
12
12
|
declare const commentConfig: Linter.Config[];
|
|
13
|
+
/**
|
|
14
|
+
* Plugin Configurations.
|
|
15
|
+
*/
|
|
13
16
|
declare const configs: {
|
|
17
|
+
/**
|
|
18
|
+
* Recommended configuration
|
|
19
|
+
*/
|
|
14
20
|
recommended: typeof recommendedConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Comment configuration
|
|
23
|
+
*/
|
|
15
24
|
comment: typeof commentConfig;
|
|
16
25
|
};
|
|
17
26
|
/** @alias */
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,194 @@
|
|
|
1
1
|
import { parseComment } from "@es-joy/jsdoccomment";
|
|
2
2
|
|
|
3
|
+
//#region src/utils/comment.ts
|
|
4
|
+
/**
|
|
5
|
+
* Remove JSDoc asterisk prefix if present
|
|
6
|
+
*
|
|
7
|
+
* @param line - The line of text to strip
|
|
8
|
+
* @returns The stripped line of text
|
|
9
|
+
*/
|
|
10
|
+
function stripJSDocPrefix(line) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
return trimmed.startsWith("*") ? trimmed.slice(1).trim() : trimmed;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if the text starts with any of the given tags
|
|
16
|
+
*
|
|
17
|
+
* @param text - The text to check
|
|
18
|
+
* @param tags - Array of tags to search for
|
|
19
|
+
* @returns Tag detection result or null if no tag found
|
|
20
|
+
*/
|
|
21
|
+
function detectTag(text, tags) {
|
|
22
|
+
for (const tag of tags) {
|
|
23
|
+
const tagRegex = /* @__PURE__ */ new RegExp(`^${tag}\\b`);
|
|
24
|
+
const match = text.match(tagRegex);
|
|
25
|
+
if (match) {
|
|
26
|
+
const afterTag = text.slice(tag.length);
|
|
27
|
+
if (afterTag.startsWith("(")) {
|
|
28
|
+
const closingParenIndex = afterTag.indexOf(")");
|
|
29
|
+
if (closingParenIndex > 0) {
|
|
30
|
+
const scope = afterTag.slice(1, closingParenIndex).trim();
|
|
31
|
+
return {
|
|
32
|
+
tag,
|
|
33
|
+
hasScope: scope.length > 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
tag,
|
|
38
|
+
hasScope: false
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (afterTag === "" || afterTag.startsWith(":") || afterTag.startsWith(" ")) return {
|
|
42
|
+
tag,
|
|
43
|
+
hasScope: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculate the exact location of a tag in a comment
|
|
51
|
+
*
|
|
52
|
+
* @param comment - The comment containing the tag
|
|
53
|
+
* @param line - The line of text containing the tag
|
|
54
|
+
* @param lineIndex - The index of the line within the comment
|
|
55
|
+
* @param tag - The tag to locate
|
|
56
|
+
* @returns Location with line and column, or null if not found
|
|
57
|
+
*/
|
|
58
|
+
function calculateTagLocation(comment, line, lineIndex, tag) {
|
|
59
|
+
const tagIndex = line.indexOf(tag);
|
|
60
|
+
if (tagIndex === -1) return null;
|
|
61
|
+
if (lineIndex === 0) {
|
|
62
|
+
const tagIndexInValue = comment.value.indexOf(tag);
|
|
63
|
+
return tagIndexInValue === -1 ? null : {
|
|
64
|
+
line: comment.loc.start.line,
|
|
65
|
+
column: comment.loc.start.column + 2 + tagIndexInValue
|
|
66
|
+
};
|
|
67
|
+
} else return {
|
|
68
|
+
line: comment.loc.start.line + lineIndex,
|
|
69
|
+
column: tagIndex
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Process all comments in a source file
|
|
74
|
+
*
|
|
75
|
+
* @param sourceCode - The ESLint source code object
|
|
76
|
+
* @param callback - Function to call for each comment
|
|
77
|
+
*/
|
|
78
|
+
function processAllComments(sourceCode, callback) {
|
|
79
|
+
const comments = sourceCode.getAllComments();
|
|
80
|
+
for (const comment of comments) callback(comment);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Calculate the position of a word in a comment
|
|
84
|
+
*
|
|
85
|
+
* @param comment - The comment containing the word
|
|
86
|
+
* @param wordIndex - The index of the word in the comment value
|
|
87
|
+
* @param _word - The word itself (unused but needed for API compatibility)
|
|
88
|
+
* @returns Location with line and column
|
|
89
|
+
*/
|
|
90
|
+
function calculateWordPosition(comment, wordIndex, _word) {
|
|
91
|
+
const { value, type } = comment;
|
|
92
|
+
if (type === "Line") return {
|
|
93
|
+
line: comment.loc.start.line,
|
|
94
|
+
column: comment.loc.start.column + 2 + wordIndex
|
|
95
|
+
};
|
|
96
|
+
const beforeMatch = value.slice(0, Math.max(0, wordIndex));
|
|
97
|
+
const beforeLines = beforeMatch.split("\n");
|
|
98
|
+
const lineIndex = beforeLines.length - 1;
|
|
99
|
+
const line = comment.loc.start.line + lineIndex;
|
|
100
|
+
if (lineIndex === 0) return {
|
|
101
|
+
line,
|
|
102
|
+
column: comment.loc.start.column + 2 + beforeMatch.length
|
|
103
|
+
};
|
|
104
|
+
else {
|
|
105
|
+
const columnInValue = beforeMatch.length - beforeLines.slice(0, -1).join("\n").length - 1;
|
|
106
|
+
return {
|
|
107
|
+
line,
|
|
108
|
+
column: Math.max(0, columnInValue)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Report a comment violation with standardized location handling
|
|
114
|
+
*
|
|
115
|
+
* @param ctx - The ESLint rule context
|
|
116
|
+
* @param comment - The comment where the violation occurred
|
|
117
|
+
* @param messageId - The message ID to report
|
|
118
|
+
* @param data - Data for the message template
|
|
119
|
+
* @param location - Optional specific location within the comment
|
|
120
|
+
*/
|
|
121
|
+
function reportCommentViolation(ctx, comment, messageId, data, location) {
|
|
122
|
+
if (!comment.loc) {
|
|
123
|
+
ctx.report({
|
|
124
|
+
messageId,
|
|
125
|
+
data,
|
|
126
|
+
node: ctx.sourceCode.ast
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (location) ctx.report({
|
|
131
|
+
messageId,
|
|
132
|
+
data,
|
|
133
|
+
loc: {
|
|
134
|
+
start: {
|
|
135
|
+
line: location.line,
|
|
136
|
+
column: location.column
|
|
137
|
+
},
|
|
138
|
+
end: {
|
|
139
|
+
line: location.line,
|
|
140
|
+
column: location.column + (location.length || 0)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
else ctx.report({
|
|
145
|
+
messageId,
|
|
146
|
+
data,
|
|
147
|
+
loc: comment.loc
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse directive comment and extract description
|
|
152
|
+
* Handles both eslint-style (--) and TypeScript-style (space) separators
|
|
153
|
+
*
|
|
154
|
+
* @param text - The comment text to parse
|
|
155
|
+
* @param directives - List of directive patterns to check for
|
|
156
|
+
* @returns Parsed directive info or null if not a directive
|
|
157
|
+
*/
|
|
158
|
+
function parseDirectiveComment(text, directives) {
|
|
159
|
+
const trimmedText = text.trim();
|
|
160
|
+
for (const directive of directives) if (trimmedText.startsWith(directive)) {
|
|
161
|
+
const afterDirective = trimmedText.slice(directive.length);
|
|
162
|
+
const separatorIndex = afterDirective.indexOf("--");
|
|
163
|
+
if (separatorIndex !== -1) {
|
|
164
|
+
const description = afterDirective.slice(separatorIndex + 2).trim();
|
|
165
|
+
const separatorPos = text.indexOf("--");
|
|
166
|
+
const afterSeparator = text.slice(separatorPos + 2);
|
|
167
|
+
const descriptionStart = separatorPos + 2 + (afterSeparator.length - afterSeparator.trimStart().length);
|
|
168
|
+
return {
|
|
169
|
+
directive,
|
|
170
|
+
description,
|
|
171
|
+
descriptionStart
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (afterDirective.trim()) {
|
|
175
|
+
const spaceMatch = afterDirective.match(/^\s+/);
|
|
176
|
+
if (spaceMatch) {
|
|
177
|
+
const description = afterDirective.trim();
|
|
178
|
+
const directiveIndex = text.indexOf(directive);
|
|
179
|
+
const descriptionStart = directiveIndex + directive.length + spaceMatch[0].length;
|
|
180
|
+
return {
|
|
181
|
+
directive,
|
|
182
|
+
description,
|
|
183
|
+
descriptionStart
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
3
192
|
//#region src/utils/constants.ts
|
|
4
193
|
/**
|
|
5
194
|
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
@@ -12,7 +201,7 @@ const name = "@kazupon/eslint-plugin";
|
|
|
12
201
|
/**
|
|
13
202
|
* The plugin version.
|
|
14
203
|
*/
|
|
15
|
-
const version = "0.
|
|
204
|
+
const version = "0.6.1";
|
|
16
205
|
/**
|
|
17
206
|
* The namespace for rules
|
|
18
207
|
*/
|
|
@@ -22,7 +211,7 @@ const namespace = "@kazupon";
|
|
|
22
211
|
//#region src/utils/rule.ts
|
|
23
212
|
const BLOB_URL = "https://eslint-plugin.kazupon.dev/rules";
|
|
24
213
|
function RuleCreator(urlCreator, namespace$1 = "") {
|
|
25
|
-
return function createNamedRule({ meta, name: name$1,...rule$
|
|
214
|
+
return function createNamedRule({ meta, name: name$1,...rule$4 }) {
|
|
26
215
|
const ruleId = namespace$1 ? `${namespace$1}/${name$1}` : name$1;
|
|
27
216
|
return {
|
|
28
217
|
meta: {
|
|
@@ -34,7 +223,7 @@ function RuleCreator(urlCreator, namespace$1 = "") {
|
|
|
34
223
|
ruleId
|
|
35
224
|
}
|
|
36
225
|
},
|
|
37
|
-
...rule$
|
|
226
|
+
...rule$4
|
|
38
227
|
};
|
|
39
228
|
};
|
|
40
229
|
}
|
|
@@ -53,7 +242,7 @@ function initializeTagDiagnosis(tags) {
|
|
|
53
242
|
function validTagDiagnosis(tagDiagnosis) {
|
|
54
243
|
return Object.keys(tagDiagnosis).every((tag) => tagDiagnosis[tag] === "ok");
|
|
55
244
|
}
|
|
56
|
-
const rule$
|
|
245
|
+
const rule$3 = createRule({
|
|
57
246
|
name: "enforce-header-comment",
|
|
58
247
|
meta: {
|
|
59
248
|
type: "suggestion",
|
|
@@ -74,6 +263,7 @@ const rule$2 = createRule({
|
|
|
74
263
|
create(ctx) {
|
|
75
264
|
/**
|
|
76
265
|
* Report the tag diagnosis
|
|
266
|
+
*
|
|
77
267
|
* @param comment - A target comment node
|
|
78
268
|
* @param tags - A list of tags to check
|
|
79
269
|
* @param tagDiagnosis - A map of tag diagnosis
|
|
@@ -83,11 +273,7 @@ const rule$2 = createRule({
|
|
|
83
273
|
let reported = false;
|
|
84
274
|
for (const tag of tags) {
|
|
85
275
|
if (tagDiagnosis[tag] === "ok") continue;
|
|
86
|
-
ctx
|
|
87
|
-
loc: comment.loc,
|
|
88
|
-
messageId: tagDiagnosis[tag] === "require" ? "headerCommentNeedTag" : "headerCommentNeedTagValue",
|
|
89
|
-
data: { tag }
|
|
90
|
-
});
|
|
276
|
+
reportCommentViolation(ctx, comment, tagDiagnosis[tag] === "require" ? "headerCommentNeedTag" : "headerCommentNeedTagValue", { tag });
|
|
91
277
|
reported = true;
|
|
92
278
|
}
|
|
93
279
|
return reported;
|
|
@@ -181,78 +367,33 @@ const rule$2 = createRule({
|
|
|
181
367
|
};
|
|
182
368
|
}
|
|
183
369
|
});
|
|
184
|
-
var enforce_header_comment_default = rule$
|
|
370
|
+
var enforce_header_comment_default = rule$3;
|
|
185
371
|
|
|
186
372
|
//#endregion
|
|
187
|
-
//#region src/utils/
|
|
188
|
-
/**
|
|
189
|
-
* Remove JSDoc asterisk prefix if present
|
|
190
|
-
*/
|
|
191
|
-
function stripJSDocPrefix(line) {
|
|
192
|
-
const trimmed = line.trim();
|
|
193
|
-
return trimmed.startsWith("*") ? trimmed.slice(1).trim() : trimmed;
|
|
194
|
-
}
|
|
373
|
+
//#region src/utils/options.ts
|
|
195
374
|
/**
|
|
196
|
-
*
|
|
197
|
-
* @
|
|
198
|
-
* @param tags Array of tags to search for
|
|
199
|
-
* @returns Tag detection result or null if no tag found
|
|
375
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
376
|
+
* @license MIT
|
|
200
377
|
*/
|
|
201
|
-
function detectTag(text, tags) {
|
|
202
|
-
for (const tag of tags) {
|
|
203
|
-
const tagRegex = /* @__PURE__ */ new RegExp(`^${tag}\\b`);
|
|
204
|
-
const match = text.match(tagRegex);
|
|
205
|
-
if (match) {
|
|
206
|
-
const afterTag = text.slice(tag.length);
|
|
207
|
-
if (afterTag.startsWith("(")) {
|
|
208
|
-
const closingParenIndex = afterTag.indexOf(")");
|
|
209
|
-
if (closingParenIndex > 0) {
|
|
210
|
-
const scope = afterTag.slice(1, closingParenIndex).trim();
|
|
211
|
-
return {
|
|
212
|
-
tag,
|
|
213
|
-
hasScope: scope.length > 0
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
tag,
|
|
218
|
-
hasScope: false
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
if (afterTag === "" || afterTag.startsWith(":") || afterTag.startsWith(" ")) return {
|
|
222
|
-
tag,
|
|
223
|
-
hasScope: false
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
378
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* @
|
|
233
|
-
*
|
|
234
|
-
* @param
|
|
235
|
-
* @
|
|
379
|
+
* Parse array options with defaults
|
|
380
|
+
*
|
|
381
|
+
* @typeParam T - The type of the options object
|
|
382
|
+
*
|
|
383
|
+
* @param options - The options object that may contain array fields
|
|
384
|
+
* @param arrayFields - Object mapping field names to their default arrays
|
|
385
|
+
* @returns The parsed options with default arrays applied
|
|
236
386
|
*/
|
|
237
|
-
function
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
const tagIndexInValue = comment.value.indexOf(tag);
|
|
242
|
-
return tagIndexInValue === -1 ? null : {
|
|
243
|
-
line: comment.loc.start.line,
|
|
244
|
-
column: comment.loc.start.column + 2 + tagIndexInValue
|
|
245
|
-
};
|
|
246
|
-
} else return {
|
|
247
|
-
line: comment.loc.start.line + lineIndex,
|
|
248
|
-
column: tagIndex
|
|
249
|
-
};
|
|
387
|
+
function parseArrayOptions(options, arrayFields) {
|
|
388
|
+
const result = options ? { ...options } : {};
|
|
389
|
+
for (const [field, defaultArray] of Object.entries(arrayFields)) if (!result[field] || !Array.isArray(result[field])) result[field] = defaultArray;
|
|
390
|
+
return result;
|
|
250
391
|
}
|
|
251
392
|
|
|
252
393
|
//#endregion
|
|
253
394
|
//#region src/rules/no-tag-comments.ts
|
|
254
395
|
const DEFAULT_TAGS$1 = ["FIXME", "BUG"];
|
|
255
|
-
const rule$
|
|
396
|
+
const rule$2 = createRule({
|
|
256
397
|
name: "no-tag-comments",
|
|
257
398
|
meta: {
|
|
258
399
|
type: "problem",
|
|
@@ -275,35 +416,26 @@ const rule$1 = createRule({
|
|
|
275
416
|
}]
|
|
276
417
|
},
|
|
277
418
|
create(ctx) {
|
|
278
|
-
const options = ctx.options[0]
|
|
279
|
-
const tags = options.tags
|
|
419
|
+
const options = parseArrayOptions(ctx.options[0], { tags: DEFAULT_TAGS$1 });
|
|
420
|
+
const tags = options.tags;
|
|
280
421
|
const sourceCode = ctx.sourceCode;
|
|
281
422
|
/**
|
|
282
423
|
* Report a tag comment violation
|
|
424
|
+
*
|
|
425
|
+
* @param comment - The comment node to report
|
|
426
|
+
* @param tag - The tag that was found in the comment
|
|
427
|
+
* @param loc - Optional location information for the tag
|
|
283
428
|
*/
|
|
284
429
|
function reportTag(comment, tag, loc) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
start: {
|
|
290
|
-
line: loc.line,
|
|
291
|
-
column: loc.column
|
|
292
|
-
},
|
|
293
|
-
end: {
|
|
294
|
-
line: loc.line,
|
|
295
|
-
column: loc.column + tag.length
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
else ctx.report({
|
|
300
|
-
messageId: "tagComment",
|
|
301
|
-
data: { tag },
|
|
302
|
-
loc: comment.loc
|
|
303
|
-
});
|
|
430
|
+
reportCommentViolation(ctx, comment, "tagComment", { tag }, loc ? {
|
|
431
|
+
...loc,
|
|
432
|
+
length: tag.length
|
|
433
|
+
} : void 0);
|
|
304
434
|
}
|
|
305
435
|
/**
|
|
306
436
|
* Check a comment for tag violations
|
|
437
|
+
*
|
|
438
|
+
* @param comment - The comment node to check
|
|
307
439
|
*/
|
|
308
440
|
function checkComment(comment) {
|
|
309
441
|
const { value, type } = comment;
|
|
@@ -334,12 +466,122 @@ const rule$1 = createRule({
|
|
|
334
466
|
}
|
|
335
467
|
}
|
|
336
468
|
return { Program() {
|
|
337
|
-
|
|
338
|
-
for (const comment of comments) checkComment(comment);
|
|
469
|
+
processAllComments(sourceCode, checkComment);
|
|
339
470
|
} };
|
|
340
471
|
}
|
|
341
472
|
});
|
|
342
|
-
var no_tag_comments_default = rule$
|
|
473
|
+
var no_tag_comments_default = rule$2;
|
|
474
|
+
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/utils/regex.ts
|
|
477
|
+
/**
|
|
478
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
479
|
+
* @license MIT
|
|
480
|
+
*/
|
|
481
|
+
/**
|
|
482
|
+
* Escape special regex characters in a string
|
|
483
|
+
*
|
|
484
|
+
* @param str - The string to escape
|
|
485
|
+
* @returns The escaped string
|
|
486
|
+
*/
|
|
487
|
+
function escapeRegExp(str) {
|
|
488
|
+
return str.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Create a regex pattern with word boundaries
|
|
492
|
+
*
|
|
493
|
+
* @param word - The word to match
|
|
494
|
+
* @param flags - Optional regex flags
|
|
495
|
+
* @returns A RegExp that matches the word with boundaries
|
|
496
|
+
*/
|
|
497
|
+
function createWordBoundaryRegex(word, flags = "g") {
|
|
498
|
+
const escapedWord = escapeRegExp(word);
|
|
499
|
+
return new RegExp(`\\b${escapedWord}\\b`, flags);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if a word at a given index is wrapped with specific delimiters
|
|
503
|
+
*
|
|
504
|
+
* @param text - The text to check
|
|
505
|
+
* @param index - The starting index of the word
|
|
506
|
+
* @param word - The word to check
|
|
507
|
+
* @param startDelimiter - The starting delimiter (default: '`')
|
|
508
|
+
* @param endDelimiter - The ending delimiter (default: '`')
|
|
509
|
+
* @returns True if the word is wrapped with the delimiters
|
|
510
|
+
*/
|
|
511
|
+
function isWordWrapped(text, index, word, startDelimiter = "`", endDelimiter = "`") {
|
|
512
|
+
const beforeIndex = index - 1;
|
|
513
|
+
const afterIndex = index + word.length;
|
|
514
|
+
return beforeIndex >= 0 && afterIndex < text.length && text[beforeIndex] === startDelimiter && text[afterIndex] === endDelimiter;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region src/rules/prefer-inline-code-words-comments.ts
|
|
519
|
+
const rule$1 = createRule({
|
|
520
|
+
name: "prefer-inline-code-words-comments",
|
|
521
|
+
meta: {
|
|
522
|
+
type: "suggestion",
|
|
523
|
+
docs: {
|
|
524
|
+
description: "enforce the use of inline code for specific words on comments",
|
|
525
|
+
category: "Comment",
|
|
526
|
+
recommended: true,
|
|
527
|
+
defaultSeverity: "error"
|
|
528
|
+
},
|
|
529
|
+
fixable: "code",
|
|
530
|
+
messages: { missingInlineCode: "The word \"{{word}}\" should be wrapped in inline code" },
|
|
531
|
+
schema: [{
|
|
532
|
+
type: "object",
|
|
533
|
+
properties: { words: {
|
|
534
|
+
type: "array",
|
|
535
|
+
items: { type: "string" }
|
|
536
|
+
} },
|
|
537
|
+
required: ["words"],
|
|
538
|
+
additionalProperties: false
|
|
539
|
+
}]
|
|
540
|
+
},
|
|
541
|
+
create(ctx) {
|
|
542
|
+
const options = ctx.options[0];
|
|
543
|
+
if (!options || !options.words || options.words.length === 0) return {};
|
|
544
|
+
const words = options.words;
|
|
545
|
+
const sourceCode = ctx.sourceCode;
|
|
546
|
+
/**
|
|
547
|
+
* Check a comment for words that should be wrapped in inline code
|
|
548
|
+
*
|
|
549
|
+
* @param comment - The comment node to check
|
|
550
|
+
*/
|
|
551
|
+
function checkComment(comment) {
|
|
552
|
+
const { value } = comment;
|
|
553
|
+
for (const word of words) {
|
|
554
|
+
const regex = createWordBoundaryRegex(word);
|
|
555
|
+
let match;
|
|
556
|
+
while ((match = regex.exec(value)) !== null) {
|
|
557
|
+
const index = match.index;
|
|
558
|
+
if (isWordWrapped(value, index, word)) continue;
|
|
559
|
+
const position = calculateWordPosition(comment, index, word);
|
|
560
|
+
ctx.report({
|
|
561
|
+
messageId: "missingInlineCode",
|
|
562
|
+
data: { word },
|
|
563
|
+
loc: {
|
|
564
|
+
start: position,
|
|
565
|
+
end: {
|
|
566
|
+
line: position.line,
|
|
567
|
+
column: position.column + word.length
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
fix(fixer) {
|
|
571
|
+
const startOffset = comment.range[0] + 2 + index;
|
|
572
|
+
const endOffset = startOffset + word.length;
|
|
573
|
+
return fixer.replaceTextRange([startOffset, endOffset], `\`${word}\``);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return { Program() {
|
|
580
|
+
processAllComments(sourceCode, checkComment);
|
|
581
|
+
} };
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
var prefer_inline_code_words_comments_default = rule$1;
|
|
343
585
|
|
|
344
586
|
//#endregion
|
|
345
587
|
//#region src/rules/prefer-scope-on-tag-comment.ts
|
|
@@ -389,85 +631,34 @@ const rule = createRule({
|
|
|
389
631
|
}]
|
|
390
632
|
},
|
|
391
633
|
create(ctx) {
|
|
392
|
-
const options = ctx.options[0]
|
|
393
|
-
|
|
394
|
-
|
|
634
|
+
const options = parseArrayOptions(ctx.options[0], {
|
|
635
|
+
tags: DEFAULT_TAGS,
|
|
636
|
+
directives: DEFAULT_DIRECTIVES
|
|
637
|
+
});
|
|
638
|
+
const tags = options.tags;
|
|
639
|
+
const directives = options.directives;
|
|
395
640
|
const sourceCode = ctx.sourceCode;
|
|
396
641
|
/**
|
|
397
642
|
* Report a missing scope violation
|
|
643
|
+
*
|
|
644
|
+
* @param comment - The comment node to report
|
|
645
|
+
* @param tag - The tag that is missing a scope
|
|
398
646
|
*/
|
|
399
647
|
function reportMissingScope(comment, tag, loc) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
node: ctx.sourceCode.ast
|
|
405
|
-
});
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
if (loc && comment.loc) ctx.report({
|
|
409
|
-
messageId: "missingScope",
|
|
410
|
-
data: { tag },
|
|
411
|
-
loc: {
|
|
412
|
-
start: {
|
|
413
|
-
line: loc.line,
|
|
414
|
-
column: loc.column
|
|
415
|
-
},
|
|
416
|
-
end: {
|
|
417
|
-
line: loc.line,
|
|
418
|
-
column: loc.column + tag.length
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
else ctx.report({
|
|
423
|
-
messageId: "missingScope",
|
|
424
|
-
data: { tag },
|
|
425
|
-
loc: comment.loc
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* Check if comment starts with a directive and extract the description
|
|
430
|
-
* @returns Object with directive match info or null
|
|
431
|
-
*/
|
|
432
|
-
function parseDirectiveComment(text) {
|
|
433
|
-
const trimmedText = text.trim();
|
|
434
|
-
for (const directive of directives) if (trimmedText.startsWith(directive)) {
|
|
435
|
-
const afterDirective = trimmedText.slice(directive.length);
|
|
436
|
-
const separatorIndex = afterDirective.indexOf("--");
|
|
437
|
-
if (separatorIndex !== -1) {
|
|
438
|
-
const description = afterDirective.slice(separatorIndex + 2).trim();
|
|
439
|
-
const separatorPos = text.indexOf("--");
|
|
440
|
-
const afterSeparator = text.slice(separatorPos + 2);
|
|
441
|
-
const descriptionStart = separatorPos + 2 + (afterSeparator.length - afterSeparator.trimStart().length);
|
|
442
|
-
return {
|
|
443
|
-
directive,
|
|
444
|
-
description,
|
|
445
|
-
descriptionStart
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
if (afterDirective.trim()) {
|
|
449
|
-
const spaceMatch = afterDirective.match(/^\s+/);
|
|
450
|
-
if (spaceMatch) {
|
|
451
|
-
const description = afterDirective.trim();
|
|
452
|
-
const directiveIndex = text.indexOf(directive);
|
|
453
|
-
const descriptionStart = directiveIndex + directive.length + spaceMatch[0].length;
|
|
454
|
-
return {
|
|
455
|
-
directive,
|
|
456
|
-
description,
|
|
457
|
-
descriptionStart
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return null;
|
|
648
|
+
reportCommentViolation(ctx, comment, "missingScope", { tag }, loc ? {
|
|
649
|
+
...loc,
|
|
650
|
+
length: tag.length
|
|
651
|
+
} : void 0);
|
|
463
652
|
}
|
|
464
653
|
/**
|
|
465
654
|
* Check a comment for missing scope
|
|
655
|
+
*
|
|
656
|
+
* @param comment - The comment node to check
|
|
466
657
|
*/
|
|
467
658
|
function checkComment(comment) {
|
|
468
659
|
const { value, type } = comment;
|
|
469
660
|
if (type === "Line") {
|
|
470
|
-
const directiveInfo$1 = parseDirectiveComment(value);
|
|
661
|
+
const directiveInfo$1 = parseDirectiveComment(value, directives);
|
|
471
662
|
if (directiveInfo$1) {
|
|
472
663
|
const tagInfo$1 = detectTag(directiveInfo$1.description, tags);
|
|
473
664
|
if (tagInfo$1 && !tagInfo$1.hasScope) {
|
|
@@ -490,7 +681,7 @@ const rule = createRule({
|
|
|
490
681
|
return;
|
|
491
682
|
}
|
|
492
683
|
const lines = value.split("\n");
|
|
493
|
-
const directiveInfo = parseDirectiveComment(value);
|
|
684
|
+
const directiveInfo = parseDirectiveComment(value, directives);
|
|
494
685
|
if (directiveInfo) {
|
|
495
686
|
const tagInfo = detectTag(directiveInfo.description, tags);
|
|
496
687
|
if (tagInfo && !tagInfo.hasScope) if (lines.length === 1) {
|
|
@@ -543,8 +734,7 @@ const rule = createRule({
|
|
|
543
734
|
}
|
|
544
735
|
}
|
|
545
736
|
return { Program() {
|
|
546
|
-
|
|
547
|
-
for (const comment of comments) checkComment(comment);
|
|
737
|
+
processAllComments(sourceCode, checkComment);
|
|
548
738
|
} };
|
|
549
739
|
}
|
|
550
740
|
});
|
|
@@ -555,7 +745,8 @@ var prefer_scope_on_tag_comment_default = rule;
|
|
|
555
745
|
const rules = {
|
|
556
746
|
"enforce-header-comment": enforce_header_comment_default,
|
|
557
747
|
"no-tag-comments": no_tag_comments_default,
|
|
558
|
-
"prefer-scope-on-tag-comment": prefer_scope_on_tag_comment_default
|
|
748
|
+
"prefer-scope-on-tag-comment": prefer_scope_on_tag_comment_default,
|
|
749
|
+
"prefer-inline-code-words-comments": prefer_inline_code_words_comments_default
|
|
559
750
|
};
|
|
560
751
|
|
|
561
752
|
//#endregion
|
|
@@ -577,10 +768,10 @@ const recommendedConfig = [{
|
|
|
577
768
|
"**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
|
|
578
769
|
],
|
|
579
770
|
plugins: { [namespace]: plugin },
|
|
580
|
-
rules: Object.entries(rules).reduce((acc, [ruleName, rule$
|
|
581
|
-
if (rule$
|
|
582
|
-
const ruleId = rule$
|
|
583
|
-
acc[ruleId] = rule$
|
|
771
|
+
rules: Object.entries(rules).reduce((acc, [ruleName, rule$4]) => {
|
|
772
|
+
if (rule$4.meta?.docs?.recommended) {
|
|
773
|
+
const ruleId = rule$4.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
|
|
774
|
+
acc[ruleId] = rule$4.meta?.docs?.defaultSeverity || "warn";
|
|
584
775
|
}
|
|
585
776
|
return acc;
|
|
586
777
|
}, Object.create(null))
|
|
@@ -594,12 +785,15 @@ const commentConfig = [{
|
|
|
594
785
|
"**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
|
|
595
786
|
],
|
|
596
787
|
plugins: { [namespace]: plugin },
|
|
597
|
-
rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$
|
|
598
|
-
const ruleId = rule$
|
|
599
|
-
rules$1[ruleId] = rule$
|
|
788
|
+
rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$4]) => {
|
|
789
|
+
const ruleId = rule$4.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
|
|
790
|
+
rules$1[ruleId] = rule$4.meta?.docs?.defaultSeverity || "warn";
|
|
600
791
|
return rules$1;
|
|
601
792
|
}, Object.create(null))
|
|
602
793
|
}];
|
|
794
|
+
/**
|
|
795
|
+
* Plugin Configurations.
|
|
796
|
+
*/
|
|
603
797
|
const configs = {
|
|
604
798
|
recommended: recommendedConfig,
|
|
605
799
|
comment: commentConfig
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kazupon/eslint-plugin",
|
|
3
3
|
"description": "ESLint plugin for @kazupon",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"funding": "https://github.com/sponsors/kazupon",
|
|
7
7
|
"bugs": {
|
|
@@ -55,35 +55,37 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/compat": "^1.3.1",
|
|
58
|
-
"@eslint/markdown": "^7.
|
|
59
|
-
"@kazupon/eslint-config": "^0.
|
|
58
|
+
"@eslint/markdown": "^7.1.0",
|
|
59
|
+
"@kazupon/eslint-config": "^0.34.0",
|
|
60
60
|
"@kazupon/prettier-config": "^0.1.1",
|
|
61
|
-
"@shikijs/vitepress-twoslash": "^3.
|
|
62
|
-
"@types/node": "^22.
|
|
61
|
+
"@shikijs/vitepress-twoslash": "^3.9.1",
|
|
62
|
+
"@types/node": "^22.17.0",
|
|
63
63
|
"@vitest/eslint-plugin": "^1.3.4",
|
|
64
|
-
"bumpp": "^10.2.
|
|
64
|
+
"bumpp": "^10.2.2",
|
|
65
65
|
"eslint": "^9.32.0",
|
|
66
66
|
"eslint-config-prettier": "^10.1.8",
|
|
67
67
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
68
68
|
"eslint-plugin-import": "^2.32.0",
|
|
69
|
+
"eslint-plugin-jsdoc": "^52.0.2",
|
|
69
70
|
"eslint-plugin-jsonc": "^2.20.1",
|
|
71
|
+
"eslint-plugin-markdown-preferences": "^0.5.0",
|
|
70
72
|
"eslint-plugin-module-interop": "^0.3.1",
|
|
71
73
|
"eslint-plugin-promise": "^7.2.1",
|
|
72
|
-
"eslint-plugin-regexp": "^2.9.
|
|
74
|
+
"eslint-plugin-regexp": "^2.9.1",
|
|
73
75
|
"eslint-plugin-unicorn": "^60.0.0",
|
|
74
76
|
"eslint-plugin-unused-imports": "^4.1.4",
|
|
75
77
|
"eslint-plugin-yml": "^1.18.0",
|
|
76
|
-
"eslint-vitest-rule-tester": "^2.2.
|
|
78
|
+
"eslint-vitest-rule-tester": "^2.2.1",
|
|
77
79
|
"gh-changelogen": "^0.2.8",
|
|
78
80
|
"knip": "^5.62.0",
|
|
79
81
|
"lint-staged": "^16.1.2",
|
|
80
82
|
"pkg-pr-new": "^0.0.54",
|
|
81
83
|
"prettier": "^3.6.2",
|
|
82
84
|
"publint": "^0.3.12",
|
|
83
|
-
"tsdown": "^0.13.
|
|
85
|
+
"tsdown": "^0.13.2",
|
|
84
86
|
"tsx": "^4.20.3",
|
|
85
87
|
"twoslash-eslint": "^0.3.3",
|
|
86
|
-
"typescript": "^5.
|
|
88
|
+
"typescript": "^5.9.2",
|
|
87
89
|
"typescript-eslint": "^8.38.0",
|
|
88
90
|
"vite-plugin-eslint4b": "^0.6.0",
|
|
89
91
|
"vitepress": "^1.6.3",
|