@yuu1111/comment-check 2.0.0 → 2.2.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.ja.md CHANGED
@@ -24,26 +24,35 @@ src/queue.ts:18:2 undocumented-directive TypeScript directive needs a descriptio
24
24
  Checked 42 files: 1 new, 0 resolved, 3 baselined
25
25
  ```
26
26
 
27
+ 日本語の文末の `。` も止めるProjectはopt-inのruleを指定する
28
+
29
+ ```bash
30
+ comment-check --enable japanese-period .
31
+ ```
32
+
33
+ 複数行のcommentを直前の行へ続けず空行で区切るProjectは、もう一つのopt-inのruleを指定する
34
+
35
+ ```bash
36
+ comment-check --enable cramped-comment .
37
+ ```
38
+
27
39
  ## Rules
28
40
 
29
41
  | Rule | 検出対象 |
30
42
  |------|---------|
31
43
  | `broad-suppression` | `biome-ignore-all`、`@ts-nocheck`、ruleを書いていない `eslint-disable` |
44
+ | `cramped-comment` | 直前の行へ空行なしで続く複数行comment(opt-in) |
32
45
  | `undocumented-directive` | 説明の無い `@ts-ignore` と `@ts-expect-error` |
33
46
  | `placeholder-comment` | `TODO`、`FIXME`、`XXX`、`HACK` |
34
47
  | `separator-comment` | 記号だけで作った装飾comment |
48
+ | `japanese-period` | 日本語の文を終える `。` を含むcomment(opt-in) |
35
49
 
36
50
  ## Options
37
51
 
38
52
  | Option | Description |
39
53
  |--------|-------------|
40
54
  | `--baseline <path>` | 読み書きするbaseline file(既定は `comment-baseline.json`) |
55
+ | `--enable <rule>` | opt-in ruleを実行する 複数指定できる 未知の名前は設定error |
41
56
  | `--ignore <path>` | 検査から外すpath 複数指定できる |
42
57
  | `--update-baseline` | baselineを現在の検出で置き換える |
43
58
  | `--json` | 新規と解消済みの検出をJSONで出力する |
44
-
45
- ## Notes
46
-
47
- 生成物のディレクトリなどProjectが持つpathは `--ignore` で検査から外す 例えば `--ignore src/generated`
48
-
49
- baselineはrule、file、comment本文をキーにするため、行が動いてもそのcommentを新規とは報告しない Biomeは `biome-ignore` の理由と未使用の抑制を既に扱うため、このcheckerはBiomeが読まないcommentの細部だけを担当する
package/README.md CHANGED
@@ -25,30 +25,37 @@ src/queue.ts:18:2 undocumented-directive TypeScript directive needs a descriptio
25
25
  Checked 42 files: 1 new, 0 resolved, 3 baselined
26
26
  ```
27
27
 
28
+ A project that also keeps Japanese sentences free of a trailing `。` names the
29
+ opt-in rule:
30
+
31
+ ```bash
32
+ comment-check --enable japanese-period .
33
+ ```
34
+
35
+ A project that keeps a blank line before every multi-line comment names the
36
+ other opt-in rule:
37
+
38
+ ```bash
39
+ comment-check --enable cramped-comment .
40
+ ```
41
+
28
42
  ## Rules
29
43
 
30
44
  | Rule | Detects |
31
45
  |------|---------|
32
46
  | `broad-suppression` | `biome-ignore-all`, `@ts-nocheck`, and rule-less `eslint-disable` |
47
+ | `cramped-comment` | a multi-line block comment written directly under the previous line (opt-in) |
33
48
  | `undocumented-directive` | `@ts-ignore` or `@ts-expect-error` without a description |
34
49
  | `placeholder-comment` | `TODO`, `FIXME`, `XXX`, `HACK` |
35
50
  | `separator-comment` | decorative comments made only of punctuation |
51
+ | `japanese-period` | a Japanese sentence in a comment that ends with `。` (opt-in) |
36
52
 
37
53
  ## Options
38
54
 
39
55
  | Option | Description |
40
56
  |--------|-------------|
41
57
  | `--baseline <path>` | Baseline file to read or write (default `comment-baseline.json`) |
58
+ | `--enable <rule>` | Run an opt-in rule, repeatable; an unknown name is a configuration error |
42
59
  | `--ignore <path>` | Path to leave out, repeatable |
43
60
  | `--update-baseline` | Replace the baseline with the current findings |
44
61
  | `--json` | Print new and resolved findings as JSON |
45
-
46
- ## Notes
47
-
48
- Generated directories and other paths that the project owns stay out of the
49
- check through `--ignore`, for example `--ignore src/generated`.
50
-
51
- The baseline keys on the rule, the file, and the comment text, so moving a line
52
- does not report the comment as new. Biome already requires a reason on
53
- `biome-ignore` and reports unused suppressions, so this checker only covers the
54
- comment trivia that Biome does not read.
package/dist/cli.js CHANGED
@@ -286,9 +286,44 @@ function extractComments(source) {
286
286
  }
287
287
 
288
288
  // src/rules.ts
289
+ var OPT_IN_RULE_IDS = ["cramped-comment", "japanese-period"];
289
290
  var PLACEHOLDER_PATTERN = /\b(TODO|FIXME|XXX|HACK)\b/;
290
291
  var SEPARATOR_PATTERN = /^[-=*_#~+./\\|]{4,}$/;
291
292
  var DIRECTIVE_PATTERN = /^@ts-(?:ignore|expect-error)\b([\s\S]*)$/;
293
+ var JAPANESE_PERIOD = "\u3002";
294
+ var BLOCK_OPENER = /(?:=>|[{(,[])\s*$/;
295
+ var COMMENT_CONTINUATION = /^(?:\/\/|\*|\/\*)/;
296
+ function parseEnabledRules(values) {
297
+ const enabled = [];
298
+ for (const value of values) {
299
+ if (!OPT_IN_RULE_IDS.includes(value)) {
300
+ throw new Error(`unknown rule: ${value}`);
301
+ }
302
+ const rule = value;
303
+ if (!enabled.includes(rule)) {
304
+ enabled.push(rule);
305
+ }
306
+ }
307
+ return enabled;
308
+ }
309
+ function findJapanesePeriod(text) {
310
+ return text.indexOf(JAPANESE_PERIOD);
311
+ }
312
+ function isCrampedComment(source, start) {
313
+ const lineStart = source.lastIndexOf(`
314
+ `, start - 1) + 1;
315
+ if (lineStart === 0 || source.slice(lineStart, start).trim() !== "") {
316
+ return false;
317
+ }
318
+ const previousEnd = lineStart - 1;
319
+ const previousStart = source.lastIndexOf(`
320
+ `, previousEnd - 1) + 1;
321
+ const previous = source.slice(previousStart, previousEnd).trim();
322
+ if (previous === "" || previous.endsWith("*/")) {
323
+ return false;
324
+ }
325
+ return !(COMMENT_CONTINUATION.test(previous) || BLOCK_OPENER.test(previous));
326
+ }
292
327
  function normalizeComment(body) {
293
328
  return body.split(`
294
329
  `).map((line) => line.replace(/^\s*\*+\s?/, "")).join(" ").replace(/\s+/g, " ").trim();
@@ -337,31 +372,57 @@ function positionAt(source, offset) {
337
372
  }
338
373
  return { line, column: offset - lineStart + 1 };
339
374
  }
340
- function scanSource(source, file) {
375
+ function scanSource(source, file, enabled = []) {
341
376
  const findings = [];
342
377
  for (const comment of extractComments(source)) {
378
+ const text = normalizeComment(comment.text);
343
379
  const rule = classifyComment(comment.text);
344
- if (rule === null) {
380
+ if (rule !== null) {
381
+ const position2 = positionAt(source, comment.start);
382
+ findings.push({
383
+ column: position2.column,
384
+ file,
385
+ line: position2.line,
386
+ rule,
387
+ text
388
+ });
389
+ }
390
+ if (enabled.includes("cramped-comment") && comment.kind === "block" && source.slice(comment.start, comment.end).includes(`
391
+ `) && isCrampedComment(source, comment.start)) {
392
+ const position2 = positionAt(source, comment.start);
393
+ findings.push({
394
+ column: position2.column,
395
+ file,
396
+ line: position2.line,
397
+ rule: "cramped-comment",
398
+ text
399
+ });
400
+ }
401
+ if (!enabled.includes("japanese-period")) {
345
402
  continue;
346
403
  }
347
- const position = positionAt(source, comment.start);
404
+ const offset = findJapanesePeriod(comment.text);
405
+ if (offset < 0) {
406
+ continue;
407
+ }
408
+ const position = positionAt(source, comment.start + 2 + offset);
348
409
  findings.push({
349
410
  column: position.column,
350
411
  file,
351
412
  line: position.line,
352
- rule,
353
- text: normalizeComment(comment.text)
413
+ rule: "japanese-period",
414
+ text
354
415
  });
355
416
  }
356
417
  return findings;
357
418
  }
358
- function scanFile(file, cwd = process.cwd()) {
359
- return scanSource(readFileSync(file, "utf8"), normalizePath(relative2(cwd, file)));
419
+ function scanFile(file, cwd = process.cwd(), enabled = []) {
420
+ return scanSource(readFileSync(file, "utf8"), normalizePath(relative2(cwd, file)), enabled);
360
421
  }
361
- function scanFiles(files, cwd = process.cwd()) {
422
+ function scanFiles(files, cwd = process.cwd(), enabled = []) {
362
423
  const findings = [];
363
424
  for (const file of files) {
364
- findings.push(...scanFile(file, cwd));
425
+ findings.push(...scanFile(file, cwd, enabled));
365
426
  }
366
427
  return findings.sort(compareFindings);
367
428
  }
@@ -531,18 +592,21 @@ function runCli(main) {
531
592
  var DEFAULT_BASELINE = "comment-baseline.json";
532
593
  var RULE_MESSAGES = {
533
594
  "broad-suppression": "file-wide suppression hides too much",
595
+ "cramped-comment": "a multi-line comment needs a blank line before it",
596
+ "japanese-period": "a Japanese sentence in a comment does not end with a period",
534
597
  "placeholder-comment": "placeholder comment should be resolved or tracked",
535
598
  "separator-comment": "decorative separator comment adds no information",
536
599
  "undocumented-directive": "TypeScript directive needs a description"
537
600
  };
538
- var USAGE = "Usage: comment-check [--baseline <path>] [--ignore <path>] [--update-baseline] [--json] [path...]";
601
+ var USAGE = "Usage: comment-check [--baseline <path>] [--enable <rule>] [--ignore <path>] [--update-baseline] [--json] [path...]";
539
602
  function parseArguments(argv) {
540
603
  const parsed = parseArgv(argv, {
541
604
  flags: ["json", "update-baseline"],
542
- values: ["baseline", "ignore"]
605
+ values: ["baseline", "enable", "ignore"]
543
606
  });
544
607
  return {
545
608
  baselinePath: parsed.values.get("baseline")?.at(-1) ?? DEFAULT_BASELINE,
609
+ enabled: parseEnabledRules(parsed.values.get("enable") ?? []),
546
610
  ignores: (parsed.values.get("ignore") ?? []).map(normalizePath),
547
611
  json: parsed.flags.has("json"),
548
612
  targets: parsed.targets.length > 0 ? parsed.targets : ["."],
@@ -564,7 +628,7 @@ function main(argv) {
564
628
  extensions: SUPPORTED_EXTENSIONS,
565
629
  ignores: options.ignores
566
630
  });
567
- const findings = scanFiles(files);
631
+ const findings = scanFiles(files, process.cwd(), options.enabled);
568
632
  if (options.update) {
569
633
  const baseline2 = createBaseline(findings);
570
634
  writeBaseline(options.baselinePath, baseline2);
@@ -583,4 +647,10 @@ function main(argv) {
583
647
  }
584
648
  return comparison.added.length > 0 ? 1 : 0;
585
649
  }
586
- runCli(main);
650
+ if (import.meta.main) {
651
+ runCli(main);
652
+ }
653
+ export {
654
+ parseArguments,
655
+ main
656
+ };
package/dist/scan.js CHANGED
@@ -284,9 +284,44 @@ function extractComments(source) {
284
284
  }
285
285
 
286
286
  // src/rules.ts
287
+ var OPT_IN_RULE_IDS = ["cramped-comment", "japanese-period"];
287
288
  var PLACEHOLDER_PATTERN = /\b(TODO|FIXME|XXX|HACK)\b/;
288
289
  var SEPARATOR_PATTERN = /^[-=*_#~+./\\|]{4,}$/;
289
290
  var DIRECTIVE_PATTERN = /^@ts-(?:ignore|expect-error)\b([\s\S]*)$/;
291
+ var JAPANESE_PERIOD = "\u3002";
292
+ var BLOCK_OPENER = /(?:=>|[{(,[])\s*$/;
293
+ var COMMENT_CONTINUATION = /^(?:\/\/|\*|\/\*)/;
294
+ function parseEnabledRules(values) {
295
+ const enabled = [];
296
+ for (const value of values) {
297
+ if (!OPT_IN_RULE_IDS.includes(value)) {
298
+ throw new Error(`unknown rule: ${value}`);
299
+ }
300
+ const rule = value;
301
+ if (!enabled.includes(rule)) {
302
+ enabled.push(rule);
303
+ }
304
+ }
305
+ return enabled;
306
+ }
307
+ function findJapanesePeriod(text) {
308
+ return text.indexOf(JAPANESE_PERIOD);
309
+ }
310
+ function isCrampedComment(source, start) {
311
+ const lineStart = source.lastIndexOf(`
312
+ `, start - 1) + 1;
313
+ if (lineStart === 0 || source.slice(lineStart, start).trim() !== "") {
314
+ return false;
315
+ }
316
+ const previousEnd = lineStart - 1;
317
+ const previousStart = source.lastIndexOf(`
318
+ `, previousEnd - 1) + 1;
319
+ const previous = source.slice(previousStart, previousEnd).trim();
320
+ if (previous === "" || previous.endsWith("*/")) {
321
+ return false;
322
+ }
323
+ return !(COMMENT_CONTINUATION.test(previous) || BLOCK_OPENER.test(previous));
324
+ }
290
325
  function normalizeComment(body) {
291
326
  return body.split(`
292
327
  `).map((line) => line.replace(/^\s*\*+\s?/, "")).join(" ").replace(/\s+/g, " ").trim();
@@ -335,31 +370,57 @@ function positionAt(source, offset) {
335
370
  }
336
371
  return { line, column: offset - lineStart + 1 };
337
372
  }
338
- function scanSource(source, file) {
373
+ function scanSource(source, file, enabled = []) {
339
374
  const findings = [];
340
375
  for (const comment of extractComments(source)) {
376
+ const text = normalizeComment(comment.text);
341
377
  const rule = classifyComment(comment.text);
342
- if (rule === null) {
378
+ if (rule !== null) {
379
+ const position2 = positionAt(source, comment.start);
380
+ findings.push({
381
+ column: position2.column,
382
+ file,
383
+ line: position2.line,
384
+ rule,
385
+ text
386
+ });
387
+ }
388
+ if (enabled.includes("cramped-comment") && comment.kind === "block" && source.slice(comment.start, comment.end).includes(`
389
+ `) && isCrampedComment(source, comment.start)) {
390
+ const position2 = positionAt(source, comment.start);
391
+ findings.push({
392
+ column: position2.column,
393
+ file,
394
+ line: position2.line,
395
+ rule: "cramped-comment",
396
+ text
397
+ });
398
+ }
399
+ if (!enabled.includes("japanese-period")) {
400
+ continue;
401
+ }
402
+ const offset = findJapanesePeriod(comment.text);
403
+ if (offset < 0) {
343
404
  continue;
344
405
  }
345
- const position = positionAt(source, comment.start);
406
+ const position = positionAt(source, comment.start + 2 + offset);
346
407
  findings.push({
347
408
  column: position.column,
348
409
  file,
349
410
  line: position.line,
350
- rule,
351
- text: normalizeComment(comment.text)
411
+ rule: "japanese-period",
412
+ text
352
413
  });
353
414
  }
354
415
  return findings;
355
416
  }
356
- function scanFile(file, cwd = process.cwd()) {
357
- return scanSource(readFileSync(file, "utf8"), normalizePath(relative2(cwd, file)));
417
+ function scanFile(file, cwd = process.cwd(), enabled = []) {
418
+ return scanSource(readFileSync(file, "utf8"), normalizePath(relative2(cwd, file)), enabled);
358
419
  }
359
- function scanFiles(files, cwd = process.cwd()) {
420
+ function scanFiles(files, cwd = process.cwd(), enabled = []) {
360
421
  const findings = [];
361
422
  for (const file of files) {
362
- findings.push(...scanFile(file, cwd));
423
+ findings.push(...scanFile(file, cwd, enabled));
363
424
  }
364
425
  return findings.sort(compareFindings);
365
426
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuu1111/comment-check",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Shared comment and suppression checker",
5
5
  "repository": {
6
6
  "type": "git",