@kazupon/eslint-plugin 0.5.0 → 0.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 +6 -5
- package/lib/index.d.ts +9 -0
- package/lib/index.js +368 -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.0";
|
|
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/
|
|
373
|
+
//#region src/utils/options.ts
|
|
188
374
|
/**
|
|
189
|
-
*
|
|
375
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
376
|
+
* @license MIT
|
|
190
377
|
*/
|
|
191
|
-
function stripJSDocPrefix(line) {
|
|
192
|
-
const trimmed = line.trim();
|
|
193
|
-
return trimmed.startsWith("*") ? trimmed.slice(1).trim() : trimmed;
|
|
194
|
-
}
|
|
195
378
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
199
|
-
*
|
|
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
|
|
200
386
|
*/
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
/**
|
|
230
|
-
* Calculate the exact location of a tag in a comment
|
|
231
|
-
* @param comment The comment containing the tag
|
|
232
|
-
* @param line The line of text containing the tag
|
|
233
|
-
* @param lineIndex The index of the line within the comment
|
|
234
|
-
* @param tag The tag to locate
|
|
235
|
-
* @returns Location with line and column, or null if not found
|
|
236
|
-
*/
|
|
237
|
-
function calculateTagLocation(comment, line, lineIndex, tag) {
|
|
238
|
-
const tagIndex = line.indexOf(tag);
|
|
239
|
-
if (tagIndex === -1) return null;
|
|
240
|
-
if (lineIndex === 0) {
|
|
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,124 @@ const rule$1 = createRule({
|
|
|
334
466
|
}
|
|
335
467
|
}
|
|
336
468
|
return { Program() {
|
|
337
|
-
|
|
338
|
-
|
|
469
|
+
processAllComments(sourceCode, checkComment);
|
|
470
|
+
} };
|
|
471
|
+
}
|
|
472
|
+
});
|
|
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
|
+
minItems: 1,
|
|
537
|
+
uniqueItems: true
|
|
538
|
+
} },
|
|
539
|
+
required: ["words"],
|
|
540
|
+
additionalProperties: false
|
|
541
|
+
}]
|
|
542
|
+
},
|
|
543
|
+
create(ctx) {
|
|
544
|
+
const options = ctx.options[0];
|
|
545
|
+
if (!options || !options.words || options.words.length === 0) return {};
|
|
546
|
+
const words = options.words;
|
|
547
|
+
const sourceCode = ctx.sourceCode;
|
|
548
|
+
/**
|
|
549
|
+
* Check a comment for words that should be wrapped in inline code
|
|
550
|
+
*
|
|
551
|
+
* @param comment - The comment node to check
|
|
552
|
+
*/
|
|
553
|
+
function checkComment(comment) {
|
|
554
|
+
const { value } = comment;
|
|
555
|
+
for (const word of words) {
|
|
556
|
+
const regex = createWordBoundaryRegex(word);
|
|
557
|
+
let match;
|
|
558
|
+
while ((match = regex.exec(value)) !== null) {
|
|
559
|
+
const index = match.index;
|
|
560
|
+
if (isWordWrapped(value, index, word)) continue;
|
|
561
|
+
const position = calculateWordPosition(comment, index, word);
|
|
562
|
+
ctx.report({
|
|
563
|
+
messageId: "missingInlineCode",
|
|
564
|
+
data: { word },
|
|
565
|
+
loc: {
|
|
566
|
+
start: position,
|
|
567
|
+
end: {
|
|
568
|
+
line: position.line,
|
|
569
|
+
column: position.column + word.length
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
fix(fixer) {
|
|
573
|
+
const startOffset = comment.range[0] + 2 + index;
|
|
574
|
+
const endOffset = startOffset + word.length;
|
|
575
|
+
return fixer.replaceTextRange([startOffset, endOffset], `\`${word}\``);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return { Program() {
|
|
582
|
+
processAllComments(sourceCode, checkComment);
|
|
339
583
|
} };
|
|
340
584
|
}
|
|
341
585
|
});
|
|
342
|
-
var
|
|
586
|
+
var prefer_inline_code_words_comments_default = rule$1;
|
|
343
587
|
|
|
344
588
|
//#endregion
|
|
345
589
|
//#region src/rules/prefer-scope-on-tag-comment.ts
|
|
@@ -389,85 +633,34 @@ const rule = createRule({
|
|
|
389
633
|
}]
|
|
390
634
|
},
|
|
391
635
|
create(ctx) {
|
|
392
|
-
const options = ctx.options[0]
|
|
393
|
-
|
|
394
|
-
|
|
636
|
+
const options = parseArrayOptions(ctx.options[0], {
|
|
637
|
+
tags: DEFAULT_TAGS,
|
|
638
|
+
directives: DEFAULT_DIRECTIVES
|
|
639
|
+
});
|
|
640
|
+
const tags = options.tags;
|
|
641
|
+
const directives = options.directives;
|
|
395
642
|
const sourceCode = ctx.sourceCode;
|
|
396
643
|
/**
|
|
397
644
|
* Report a missing scope violation
|
|
645
|
+
*
|
|
646
|
+
* @param comment - The comment node to report
|
|
647
|
+
* @param tag - The tag that is missing a scope
|
|
398
648
|
*/
|
|
399
649
|
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;
|
|
650
|
+
reportCommentViolation(ctx, comment, "missingScope", { tag }, loc ? {
|
|
651
|
+
...loc,
|
|
652
|
+
length: tag.length
|
|
653
|
+
} : void 0);
|
|
463
654
|
}
|
|
464
655
|
/**
|
|
465
656
|
* Check a comment for missing scope
|
|
657
|
+
*
|
|
658
|
+
* @param comment - The comment node to check
|
|
466
659
|
*/
|
|
467
660
|
function checkComment(comment) {
|
|
468
661
|
const { value, type } = comment;
|
|
469
662
|
if (type === "Line") {
|
|
470
|
-
const directiveInfo$1 = parseDirectiveComment(value);
|
|
663
|
+
const directiveInfo$1 = parseDirectiveComment(value, directives);
|
|
471
664
|
if (directiveInfo$1) {
|
|
472
665
|
const tagInfo$1 = detectTag(directiveInfo$1.description, tags);
|
|
473
666
|
if (tagInfo$1 && !tagInfo$1.hasScope) {
|
|
@@ -490,7 +683,7 @@ const rule = createRule({
|
|
|
490
683
|
return;
|
|
491
684
|
}
|
|
492
685
|
const lines = value.split("\n");
|
|
493
|
-
const directiveInfo = parseDirectiveComment(value);
|
|
686
|
+
const directiveInfo = parseDirectiveComment(value, directives);
|
|
494
687
|
if (directiveInfo) {
|
|
495
688
|
const tagInfo = detectTag(directiveInfo.description, tags);
|
|
496
689
|
if (tagInfo && !tagInfo.hasScope) if (lines.length === 1) {
|
|
@@ -543,8 +736,7 @@ const rule = createRule({
|
|
|
543
736
|
}
|
|
544
737
|
}
|
|
545
738
|
return { Program() {
|
|
546
|
-
|
|
547
|
-
for (const comment of comments) checkComment(comment);
|
|
739
|
+
processAllComments(sourceCode, checkComment);
|
|
548
740
|
} };
|
|
549
741
|
}
|
|
550
742
|
});
|
|
@@ -555,7 +747,8 @@ var prefer_scope_on_tag_comment_default = rule;
|
|
|
555
747
|
const rules = {
|
|
556
748
|
"enforce-header-comment": enforce_header_comment_default,
|
|
557
749
|
"no-tag-comments": no_tag_comments_default,
|
|
558
|
-
"prefer-scope-on-tag-comment": prefer_scope_on_tag_comment_default
|
|
750
|
+
"prefer-scope-on-tag-comment": prefer_scope_on_tag_comment_default,
|
|
751
|
+
"prefer-inline-code-words-comments": prefer_inline_code_words_comments_default
|
|
559
752
|
};
|
|
560
753
|
|
|
561
754
|
//#endregion
|
|
@@ -577,10 +770,10 @@ const recommendedConfig = [{
|
|
|
577
770
|
"**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
|
|
578
771
|
],
|
|
579
772
|
plugins: { [namespace]: plugin },
|
|
580
|
-
rules: Object.entries(rules).reduce((acc, [ruleName, rule$
|
|
581
|
-
if (rule$
|
|
582
|
-
const ruleId = rule$
|
|
583
|
-
acc[ruleId] = rule$
|
|
773
|
+
rules: Object.entries(rules).reduce((acc, [ruleName, rule$4]) => {
|
|
774
|
+
if (rule$4.meta?.docs?.recommended) {
|
|
775
|
+
const ruleId = rule$4.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
|
|
776
|
+
acc[ruleId] = rule$4.meta?.docs?.defaultSeverity || "warn";
|
|
584
777
|
}
|
|
585
778
|
return acc;
|
|
586
779
|
}, Object.create(null))
|
|
@@ -594,12 +787,15 @@ const commentConfig = [{
|
|
|
594
787
|
"**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
|
|
595
788
|
],
|
|
596
789
|
plugins: { [namespace]: plugin },
|
|
597
|
-
rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$
|
|
598
|
-
const ruleId = rule$
|
|
599
|
-
rules$1[ruleId] = rule$
|
|
790
|
+
rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$4]) => {
|
|
791
|
+
const ruleId = rule$4.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
|
|
792
|
+
rules$1[ruleId] = rule$4.meta?.docs?.defaultSeverity || "warn";
|
|
600
793
|
return rules$1;
|
|
601
794
|
}, Object.create(null))
|
|
602
795
|
}];
|
|
796
|
+
/**
|
|
797
|
+
* Plugin Configurations.
|
|
798
|
+
*/
|
|
603
799
|
const configs = {
|
|
604
800
|
recommended: recommendedConfig,
|
|
605
801
|
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.0",
|
|
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.4.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",
|