@kazupon/eslint-plugin 0.3.0 → 0.5.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,10 +61,11 @@ 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 | | |
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 | | ⭐ |
68
69
 
69
70
  <!--RULES_TABLE_END-->
70
71
 
package/lib/index.d.ts CHANGED
@@ -8,9 +8,10 @@ type PluginConfigs = {
8
8
  declare const plugin: Omit<ESLint.Plugin, "configs"> & {
9
9
  configs: PluginConfigs;
10
10
  };
11
+ declare const recommendedConfig: Linter.Config[];
11
12
  declare const commentConfig: Linter.Config[];
12
13
  declare const configs: {
13
- recommended: typeof commentConfig;
14
+ recommended: typeof recommendedConfig;
14
15
  comment: typeof commentConfig;
15
16
  };
16
17
  /** @alias */
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@ const name = "@kazupon/eslint-plugin";
12
12
  /**
13
13
  * The plugin version.
14
14
  */
15
- const version = "0.3.0";
15
+ const version = "0.5.0";
16
16
  /**
17
17
  * The namespace for rules
18
18
  */
@@ -22,7 +22,7 @@ const namespace = "@kazupon";
22
22
  //#region src/utils/rule.ts
23
23
  const BLOB_URL = "https://eslint-plugin.kazupon.dev/rules";
24
24
  function RuleCreator(urlCreator, namespace$1 = "") {
25
- return function createNamedRule({ meta, name: name$1,...rule$2 }) {
25
+ return function createNamedRule({ meta, name: name$1,...rule$3 }) {
26
26
  const ruleId = namespace$1 ? `${namespace$1}/${name$1}` : name$1;
27
27
  return {
28
28
  meta: {
@@ -34,7 +34,7 @@ function RuleCreator(urlCreator, namespace$1 = "") {
34
34
  ruleId
35
35
  }
36
36
  },
37
- ...rule$2
37
+ ...rule$3
38
38
  };
39
39
  };
40
40
  }
@@ -53,7 +53,7 @@ function initializeTagDiagnosis(tags) {
53
53
  function validTagDiagnosis(tagDiagnosis) {
54
54
  return Object.keys(tagDiagnosis).every((tag) => tagDiagnosis[tag] === "ok");
55
55
  }
56
- const rule$1 = createRule({
56
+ const rule$2 = createRule({
57
57
  name: "enforce-header-comment",
58
58
  meta: {
59
59
  type: "suggestion",
@@ -181,11 +181,10 @@ const rule$1 = createRule({
181
181
  };
182
182
  }
183
183
  });
184
- var enforce_header_comment_default = rule$1;
184
+ var enforce_header_comment_default = rule$2;
185
185
 
186
186
  //#endregion
187
- //#region src/rules/no-tag-comments.ts
188
- const DEFAULT_TAGS = ["FIXME", "BUG"];
187
+ //#region src/utils/comment.ts
189
188
  /**
190
189
  * Remove JSDoc asterisk prefix if present
191
190
  */
@@ -193,17 +192,77 @@ function stripJSDocPrefix(line) {
193
192
  const trimmed = line.trim();
194
193
  return trimmed.startsWith("*") ? trimmed.slice(1).trim() : trimmed;
195
194
  }
196
- const rule = createRule({
195
+ /**
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
200
+ */
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
+ };
250
+ }
251
+
252
+ //#endregion
253
+ //#region src/rules/no-tag-comments.ts
254
+ const DEFAULT_TAGS$1 = ["FIXME", "BUG"];
255
+ const rule$1 = createRule({
197
256
  name: "no-tag-comments",
198
257
  meta: {
199
258
  type: "problem",
200
259
  docs: {
201
260
  description: "disallow tag comments",
202
261
  category: "Comment",
203
- recommended: false,
262
+ recommended: true,
204
263
  defaultSeverity: "warn"
205
264
  },
206
- messages: { avoidTagComment: "Fix '{{tag}}' tag comment" },
265
+ messages: { tagComment: "Exist '{{tag}}' tag comment" },
207
266
  schema: [{
208
267
  type: "object",
209
268
  properties: { tags: {
@@ -216,59 +275,271 @@ const rule = createRule({
216
275
  }]
217
276
  },
218
277
  create(ctx) {
219
- const options = ctx.options[0] || { tags: DEFAULT_TAGS };
220
- const tags = options.tags || DEFAULT_TAGS;
278
+ const options = ctx.options[0] || { tags: DEFAULT_TAGS$1 };
279
+ const tags = options.tags || DEFAULT_TAGS$1;
221
280
  const sourceCode = ctx.sourceCode;
222
281
  /**
223
- * Check if the text starts with a tag followed by valid delimiter
282
+ * Report a tag comment violation
224
283
  */
225
- function hasTag(text) {
226
- for (const tag of tags) if (text.startsWith(tag)) {
227
- const afterTag = text.slice(tag.length);
228
- if (afterTag === "" || afterTag.startsWith(":") || afterTag.startsWith(" ")) return tag;
284
+ 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
+ });
304
+ }
305
+ /**
306
+ * Check a comment for tag violations
307
+ */
308
+ function checkComment(comment) {
309
+ const { value, type } = comment;
310
+ if (type === "Line") {
311
+ const tagInfo = detectTag(value.trim(), tags);
312
+ if (tagInfo) {
313
+ const tagIndex = value.indexOf(tagInfo.tag);
314
+ if (tagIndex !== -1) reportTag(comment, tagInfo.tag, {
315
+ line: comment.loc.start.line,
316
+ column: comment.loc.start.column + 2 + tagIndex
317
+ });
318
+ }
319
+ return;
320
+ }
321
+ const lines = value.split("\n");
322
+ for (const [i, line] of lines.entries()) {
323
+ const trimmedLine = line.trim();
324
+ if (!trimmedLine) continue;
325
+ const contentToCheck = stripJSDocPrefix(line);
326
+ const tagInfo = detectTag(contentToCheck, tags);
327
+ if (tagInfo) {
328
+ const location = calculateTagLocation(comment, line, i, tagInfo.tag);
329
+ if (location) {
330
+ reportTag(comment, tagInfo.tag, location);
331
+ break;
332
+ }
333
+ }
229
334
  }
230
- return null;
231
335
  }
336
+ return { Program() {
337
+ const comments = sourceCode.getAllComments();
338
+ for (const comment of comments) checkComment(comment);
339
+ } };
340
+ }
341
+ });
342
+ var no_tag_comments_default = rule$1;
343
+
344
+ //#endregion
345
+ //#region src/rules/prefer-scope-on-tag-comment.ts
346
+ const DEFAULT_TAGS = [
347
+ "TODO",
348
+ "FIXME",
349
+ "HACK",
350
+ "BUG",
351
+ "NOTE"
352
+ ];
353
+ const DEFAULT_DIRECTIVES = [
354
+ "eslint-disable",
355
+ "eslint-disable-next-line",
356
+ "eslint-disable-line",
357
+ "@ts-expect-error",
358
+ "@ts-ignore",
359
+ "@ts-nocheck"
360
+ ];
361
+ const rule = createRule({
362
+ name: "prefer-scope-on-tag-comment",
363
+ meta: {
364
+ type: "suggestion",
365
+ docs: {
366
+ description: "enforce adding a scope to tag comments",
367
+ category: "Comment",
368
+ recommended: true,
369
+ defaultSeverity: "warn"
370
+ },
371
+ messages: { missingScope: "Tag comment '{{tag}}' is missing a scope. Use format: {{tag}}(scope)" },
372
+ schema: [{
373
+ type: "object",
374
+ properties: {
375
+ tags: {
376
+ type: "array",
377
+ items: { type: "string" },
378
+ minItems: 1,
379
+ uniqueItems: true
380
+ },
381
+ directives: {
382
+ type: "array",
383
+ items: { type: "string" },
384
+ minItems: 1,
385
+ uniqueItems: true
386
+ }
387
+ },
388
+ additionalProperties: false
389
+ }]
390
+ },
391
+ 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;
395
+ const sourceCode = ctx.sourceCode;
232
396
  /**
233
- * Report a tag comment violation
397
+ * Report a missing scope violation
234
398
  */
235
- function reportTag(comment, tag) {
236
- ctx.report({
237
- messageId: "avoidTagComment",
399
+ 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",
238
424
  data: { tag },
239
425
  loc: comment.loc
240
426
  });
241
427
  }
242
428
  /**
243
- * Process a single line of text for tags
429
+ * Check if comment starts with a directive and extract the description
430
+ * @returns Object with directive match info or null
244
431
  */
245
- function checkLine(text, comment) {
246
- const tag = hasTag(text);
247
- if (tag) {
248
- reportTag(comment, tag);
249
- return true;
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
+ }
250
461
  }
251
- return false;
462
+ return null;
252
463
  }
253
464
  /**
254
- * Check a comment for tag violations
465
+ * Check a comment for missing scope
255
466
  */
256
467
  function checkComment(comment) {
257
468
  const { value, type } = comment;
258
469
  if (type === "Line") {
259
- checkLine(value.trim(), comment);
470
+ const directiveInfo$1 = parseDirectiveComment(value);
471
+ if (directiveInfo$1) {
472
+ const tagInfo$1 = detectTag(directiveInfo$1.description, tags);
473
+ if (tagInfo$1 && !tagInfo$1.hasScope) {
474
+ const tagIndex = directiveInfo$1.description.indexOf(tagInfo$1.tag);
475
+ if (tagIndex !== -1) reportMissingScope(comment, tagInfo$1.tag, {
476
+ line: comment.loc.start.line,
477
+ column: comment.loc.start.column + 2 + directiveInfo$1.descriptionStart + tagIndex
478
+ });
479
+ }
480
+ return;
481
+ }
482
+ const tagInfo = detectTag(value.trim(), tags);
483
+ if (tagInfo && !tagInfo.hasScope) {
484
+ const tagIndex = value.indexOf(tagInfo.tag);
485
+ if (tagIndex !== -1) reportMissingScope(comment, tagInfo.tag, {
486
+ line: comment.loc.start.line,
487
+ column: comment.loc.start.column + 2 + tagIndex
488
+ });
489
+ }
260
490
  return;
261
491
  }
262
492
  const lines = value.split("\n");
263
- if (lines.length === 1) {
264
- checkLine(value.trim(), comment);
493
+ const directiveInfo = parseDirectiveComment(value);
494
+ if (directiveInfo) {
495
+ const tagInfo = detectTag(directiveInfo.description, tags);
496
+ if (tagInfo && !tagInfo.hasScope) if (lines.length === 1) {
497
+ const tagIndexInDesc = directiveInfo.description.indexOf(tagInfo.tag);
498
+ if (tagIndexInDesc !== -1) {
499
+ const tagIndexInValue = directiveInfo.descriptionStart + tagIndexInDesc;
500
+ const location = {
501
+ line: comment.loc.start.line,
502
+ column: comment.loc.start.column + 2 + tagIndexInValue
503
+ };
504
+ reportMissingScope(comment, tagInfo.tag, location);
505
+ }
506
+ } else {
507
+ const descLines = directiveInfo.description.split("\n");
508
+ for (const [descLineIndex, descLine] of descLines.entries()) {
509
+ const lineTagInfo = detectTag(descLine.trim(), tags);
510
+ if (lineTagInfo && !lineTagInfo.hasScope) {
511
+ const descStartInComment = value.indexOf(directiveInfo.description);
512
+ const linesBeforeDesc = value.slice(0, descStartInComment).split("\n").length - 1;
513
+ const actualLineIndex = linesBeforeDesc + descLineIndex;
514
+ if (actualLineIndex < lines.length) {
515
+ const actualLine = lines[actualLineIndex];
516
+ const tagIndex = actualLine.indexOf(lineTagInfo.tag);
517
+ if (tagIndex !== -1) {
518
+ const location = {
519
+ line: comment.loc.start.line + actualLineIndex,
520
+ column: actualLineIndex === 0 ? comment.loc.start.column + 2 + tagIndex : tagIndex
521
+ };
522
+ reportMissingScope(comment, lineTagInfo.tag, location);
523
+ break;
524
+ }
525
+ }
526
+ }
527
+ }
528
+ }
265
529
  return;
266
530
  }
267
- for (const line of lines) {
531
+ for (const [i, line] of lines.entries()) {
268
532
  const trimmedLine = line.trim();
269
533
  if (!trimmedLine) continue;
270
534
  const contentToCheck = stripJSDocPrefix(line);
271
- if (checkLine(contentToCheck, comment)) break;
535
+ const tagInfo = detectTag(contentToCheck, tags);
536
+ if (tagInfo && !tagInfo.hasScope) {
537
+ const location = calculateTagLocation(comment, line, i, tagInfo.tag);
538
+ if (location) {
539
+ reportMissingScope(comment, tagInfo.tag, location);
540
+ break;
541
+ }
542
+ }
272
543
  }
273
544
  }
274
545
  return { Program() {
@@ -277,13 +548,14 @@ const rule = createRule({
277
548
  } };
278
549
  }
279
550
  });
280
- var no_tag_comments_default = rule;
551
+ var prefer_scope_on_tag_comment_default = rule;
281
552
 
282
553
  //#endregion
283
554
  //#region src/rules/index.ts
284
555
  const rules = {
285
556
  "enforce-header-comment": enforce_header_comment_default,
286
- "no-tag-comments": no_tag_comments_default
557
+ "no-tag-comments": no_tag_comments_default,
558
+ "prefer-scope-on-tag-comment": prefer_scope_on_tag_comment_default
287
559
  };
288
560
 
289
561
  //#endregion
@@ -296,6 +568,23 @@ const plugin = {
296
568
  rules,
297
569
  configs: {}
298
570
  };
571
+ const recommendedConfig = [{
572
+ name: "@kazupon/eslint-plugin/recommended",
573
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
574
+ ignores: [
575
+ "**/*.md",
576
+ "**/*.md/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
577
+ "**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
578
+ ],
579
+ 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";
584
+ }
585
+ return acc;
586
+ }, Object.create(null))
587
+ }];
299
588
  const commentConfig = [{
300
589
  name: "@kazupon/eslint-plugin/comment",
301
590
  files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
@@ -305,14 +594,14 @@ const commentConfig = [{
305
594
  "**/*.config.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
306
595
  ],
307
596
  plugins: { [namespace]: plugin },
308
- rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$2]) => {
309
- const ruleId = rule$2.meta?.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
310
- rules$1[ruleId] = rule$2.meta?.docs?.defaultSeverity || "warn";
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";
311
600
  return rules$1;
312
601
  }, Object.create(null))
313
602
  }];
314
603
  const configs = {
315
- recommended: [...commentConfig],
604
+ recommended: recommendedConfig,
316
605
  comment: commentConfig
317
606
  };
318
607
  plugin.configs = configs;
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.3.0",
4
+ "version": "0.5.0",
5
5
  "license": "MIT",
6
6
  "funding": "https://github.com/sponsors/kazupon",
7
7
  "bugs": {
@@ -47,47 +47,48 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@es-joy/jsdoccomment": "^0.50.2",
51
- "@eslint/core": "^0.14.0"
50
+ "@es-joy/jsdoccomment": "^0.52.0",
51
+ "@eslint/core": "^0.15.1"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "eslint": "^9.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@eslint/compat": "^1.2.9",
58
- "@eslint/markdown": "^6.5.0",
59
- "@kazupon/eslint-config": "^0.30.0",
57
+ "@eslint/compat": "^1.3.1",
58
+ "@eslint/markdown": "^7.0.0",
59
+ "@kazupon/eslint-config": "^0.32.0",
60
60
  "@kazupon/prettier-config": "^0.1.1",
61
- "@shikijs/vitepress-twoslash": "^3.6.0",
62
- "@types/node": "^22.15.30",
63
- "@vitest/eslint-plugin": "^1.2.1",
64
- "bumpp": "^10.1.1",
65
- "eslint": "^9.28.0",
66
- "eslint-config-prettier": "^10.1.5",
67
- "eslint-import-resolver-typescript": "^4.4.3",
68
- "eslint-plugin-import": "^2.31.0",
61
+ "@shikijs/vitepress-twoslash": "^3.8.1",
62
+ "@types/node": "^22.16.5",
63
+ "@vitest/eslint-plugin": "^1.3.4",
64
+ "bumpp": "^10.2.0",
65
+ "eslint": "^9.32.0",
66
+ "eslint-config-prettier": "^10.1.8",
67
+ "eslint-import-resolver-typescript": "^4.4.4",
68
+ "eslint-plugin-import": "^2.32.0",
69
69
  "eslint-plugin-jsonc": "^2.20.1",
70
70
  "eslint-plugin-module-interop": "^0.3.1",
71
71
  "eslint-plugin-promise": "^7.2.1",
72
- "eslint-plugin-unicorn": "^58.0.0",
72
+ "eslint-plugin-regexp": "^2.9.0",
73
+ "eslint-plugin-unicorn": "^60.0.0",
73
74
  "eslint-plugin-unused-imports": "^4.1.4",
74
75
  "eslint-plugin-yml": "^1.18.0",
75
76
  "eslint-vitest-rule-tester": "^2.2.0",
76
77
  "gh-changelogen": "^0.2.8",
77
- "knip": "^5.60.2",
78
- "lint-staged": "^16.0.0",
79
- "pkg-pr-new": "^0.0.51",
80
- "prettier": "^3.5.3",
78
+ "knip": "^5.62.0",
79
+ "lint-staged": "^16.1.2",
80
+ "pkg-pr-new": "^0.0.54",
81
+ "prettier": "^3.6.2",
81
82
  "publint": "^0.3.12",
82
- "tsdown": "^0.12.7",
83
- "tsx": "^4.19.4",
84
- "twoslash-eslint": "^0.3.1",
83
+ "tsdown": "^0.13.0",
84
+ "tsx": "^4.20.3",
85
+ "twoslash-eslint": "^0.3.3",
85
86
  "typescript": "^5.8.3",
86
- "typescript-eslint": "^8.33.1",
87
- "vite-plugin-eslint4b": "^0.5.1",
87
+ "typescript-eslint": "^8.38.0",
88
+ "vite-plugin-eslint4b": "^0.6.0",
88
89
  "vitepress": "^1.6.3",
89
- "vitepress-plugin-group-icons": "^1.6.0",
90
- "vitest": "^3.2.2"
90
+ "vitepress-plugin-group-icons": "^1.6.1",
91
+ "vitest": "^3.2.4"
91
92
  },
92
93
  "prettier": "@kazupon/prettier-config",
93
94
  "lint-staged": {
@@ -117,11 +118,11 @@
117
118
  "fix": "pnpm run --stream --color \"/^fix:/\"",
118
119
  "fix:eslint": "eslint . --fix",
119
120
  "fix:knip": "knip --fix --no-exit-code",
120
- "fix:prettier": "prettier . --write",
121
+ "fix:prettier": "prettier . --write --experimental-cli",
121
122
  "lint": "pnpm run --stream --color \"/^lint:/\"",
122
123
  "lint:eslint": "eslint .",
123
124
  "lint:knip": "knip",
124
- "lint:prettier": "prettier . --check",
125
+ "lint:prettier": "prettier . --check --experimental-cli",
125
126
  "release": "bumpp --commit \"release: v%s\" --all --push --tag",
126
127
  "test": "vitest run",
127
128
  "typecheck": "pnpm run --stream --color \"/^typecheck:/\"",