@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 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.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$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
373
+ //#region src/utils/options.ts
188
374
  /**
189
- * Remove JSDoc asterisk prefix if present
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
- * 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
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 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
- /**
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$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,124 @@ 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);
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 no_tag_comments_default = rule$1;
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] || { tags: DEFAULT_TAGS };
393
- const tags = options.tags || DEFAULT_TAGS;
394
- const directives = options.directives || DEFAULT_DIRECTIVES;
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
- 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;
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
- const comments = sourceCode.getAllComments();
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$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";
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$3]) => {
598
- const ruleId = rule$3.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
599
- rules$1[ruleId] = rule$3.meta?.docs?.defaultSeverity || "warn";
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.5.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.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.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.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",