@kazupon/eslint-plugin 0.4.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.
Files changed (2) hide show
  1. package/lib/index.js +111 -8
  2. package/package.json +29 -29
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.4.0";
15
+ const version = "0.5.0";
16
16
  /**
17
17
  * The namespace for rules
18
18
  */
@@ -200,7 +200,7 @@ function stripJSDocPrefix(line) {
200
200
  */
201
201
  function detectTag(text, tags) {
202
202
  for (const tag of tags) {
203
- const tagRegex = new RegExp(`^${tag}\\b`);
203
+ const tagRegex = /* @__PURE__ */ new RegExp(`^${tag}\\b`);
204
204
  const match = text.match(tagRegex);
205
205
  if (match) {
206
206
  const afterTag = text.slice(tag.length);
@@ -350,6 +350,14 @@ const DEFAULT_TAGS = [
350
350
  "BUG",
351
351
  "NOTE"
352
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
+ ];
353
361
  const rule = createRule({
354
362
  name: "prefer-scope-on-tag-comment",
355
363
  meta: {
@@ -363,18 +371,27 @@ const rule = createRule({
363
371
  messages: { missingScope: "Tag comment '{{tag}}' is missing a scope. Use format: {{tag}}(scope)" },
364
372
  schema: [{
365
373
  type: "object",
366
- properties: { tags: {
367
- type: "array",
368
- items: { type: "string" },
369
- minItems: 1,
370
- uniqueItems: true
371
- } },
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
+ },
372
388
  additionalProperties: false
373
389
  }]
374
390
  },
375
391
  create(ctx) {
376
392
  const options = ctx.options[0] || { tags: DEFAULT_TAGS };
377
393
  const tags = options.tags || DEFAULT_TAGS;
394
+ const directives = options.directives || DEFAULT_DIRECTIVES;
378
395
  const sourceCode = ctx.sourceCode;
379
396
  /**
380
397
  * Report a missing scope violation
@@ -409,11 +426,59 @@ const rule = createRule({
409
426
  });
410
427
  }
411
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;
463
+ }
464
+ /**
412
465
  * Check a comment for missing scope
413
466
  */
414
467
  function checkComment(comment) {
415
468
  const { value, type } = comment;
416
469
  if (type === "Line") {
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
+ }
417
482
  const tagInfo = detectTag(value.trim(), tags);
418
483
  if (tagInfo && !tagInfo.hasScope) {
419
484
  const tagIndex = value.indexOf(tagInfo.tag);
@@ -425,6 +490,44 @@ const rule = createRule({
425
490
  return;
426
491
  }
427
492
  const lines = value.split("\n");
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
+ }
529
+ return;
530
+ }
428
531
  for (const [i, line] of lines.entries()) {
429
532
  const trimmedLine = line.trim();
430
533
  if (!trimmedLine) continue;
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.0",
4
+ "version": "0.5.0",
5
5
  "license": "MIT",
6
6
  "funding": "https://github.com/sponsors/kazupon",
7
7
  "bugs": {
@@ -47,48 +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.31.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-regexp": "^2.8.0",
73
- "eslint-plugin-unicorn": "^59.0.0",
72
+ "eslint-plugin-regexp": "^2.9.0",
73
+ "eslint-plugin-unicorn": "^60.0.0",
74
74
  "eslint-plugin-unused-imports": "^4.1.4",
75
75
  "eslint-plugin-yml": "^1.18.0",
76
76
  "eslint-vitest-rule-tester": "^2.2.0",
77
77
  "gh-changelogen": "^0.2.8",
78
- "knip": "^5.60.2",
79
- "lint-staged": "^16.0.0",
80
- "pkg-pr-new": "^0.0.51",
81
- "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",
82
82
  "publint": "^0.3.12",
83
- "tsdown": "^0.12.7",
84
- "tsx": "^4.19.4",
85
- "twoslash-eslint": "^0.3.1",
83
+ "tsdown": "^0.13.0",
84
+ "tsx": "^4.20.3",
85
+ "twoslash-eslint": "^0.3.3",
86
86
  "typescript": "^5.8.3",
87
- "typescript-eslint": "^8.33.1",
88
- "vite-plugin-eslint4b": "^0.5.1",
87
+ "typescript-eslint": "^8.38.0",
88
+ "vite-plugin-eslint4b": "^0.6.0",
89
89
  "vitepress": "^1.6.3",
90
- "vitepress-plugin-group-icons": "^1.6.0",
91
- "vitest": "^3.2.2"
90
+ "vitepress-plugin-group-icons": "^1.6.1",
91
+ "vitest": "^3.2.4"
92
92
  },
93
93
  "prettier": "@kazupon/prettier-config",
94
94
  "lint-staged": {
@@ -118,11 +118,11 @@
118
118
  "fix": "pnpm run --stream --color \"/^fix:/\"",
119
119
  "fix:eslint": "eslint . --fix",
120
120
  "fix:knip": "knip --fix --no-exit-code",
121
- "fix:prettier": "prettier . --write",
121
+ "fix:prettier": "prettier . --write --experimental-cli",
122
122
  "lint": "pnpm run --stream --color \"/^lint:/\"",
123
123
  "lint:eslint": "eslint .",
124
124
  "lint:knip": "knip",
125
- "lint:prettier": "prettier . --check",
125
+ "lint:prettier": "prettier . --check --experimental-cli",
126
126
  "release": "bumpp --commit \"release: v%s\" --all --push --tag",
127
127
  "test": "vitest run",
128
128
  "typecheck": "pnpm run --stream --color \"/^typecheck:/\"",