@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 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 | 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-scope-on-tag-comment](https://eslint-plugin.kazupon.dev/rules/prefer-scope-on-tag-comment.html) | enforce adding a scope to tag comments | Comment | | ⭐ |
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.5.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$3 }) {
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$3
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$2 = createRule({
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.report({
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$2;
370
+ var enforce_header_comment_default = rule$3;
185
371
 
186
372
  //#endregion
187
- //#region src/utils/comment.ts
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
- * Check if the text starts with any of the given tags
197
- * @param text The text to check
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
- * 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
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 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$1 = createRule({
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] || { tags: DEFAULT_TAGS$1 };
279
- const tags = options.tags || DEFAULT_TAGS$1;
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
- if (loc && comment.loc) ctx.report({
286
- messageId: "tagComment",
287
- data: { tag },
288
- loc: {
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
- const comments = sourceCode.getAllComments();
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$1;
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] || { tags: DEFAULT_TAGS };
393
- const tags = options.tags || DEFAULT_TAGS;
394
- const directives = options.directives || DEFAULT_DIRECTIVES;
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
- if (!comment.loc) {
401
- ctx.report({
402
- messageId: "missingScope",
403
- data: { tag },
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
- const comments = sourceCode.getAllComments();
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$3]) => {
581
- if (rule$3.meta?.docs?.recommended) {
582
- const ruleId = rule$3.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
583
- acc[ruleId] = rule$3.meta?.docs?.defaultSeverity || "warn";
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$3]) => {
598
- const ruleId = rule$3.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
599
- rules$1[ruleId] = rule$3.meta?.docs?.defaultSeverity || "warn";
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.5.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.0.0",
59
- "@kazupon/eslint-config": "^0.32.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.8.1",
62
- "@types/node": "^22.16.5",
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.0",
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.0",
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.0",
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.0",
85
+ "tsdown": "^0.13.2",
84
86
  "tsx": "^4.20.3",
85
87
  "twoslash-eslint": "^0.3.3",
86
- "typescript": "^5.8.3",
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",