@yuu1111/quality-check 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.ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @yuu1111/quality-check
4
4
 
5
- Projectごとのscriptから個別に呼んでいたBiomeKnip、comment-check、document-style-check、TSDoc checkerを1つのCLIへまとめる baselineの差分判定もここで行う
5
+ Projectごとのscriptから個別に呼んでいたBiome、型検査、Knip、comment-check、document-style-check、TSDoc checkerを1つのCLIへまとめる baselineの差分判定もここで行う
6
6
 
7
7
  ## Install
8
8
 
@@ -28,13 +28,14 @@ import { defineConfig } from "@yuu1111/quality-check";
28
28
  export default defineConfig({
29
29
  engines: {
30
30
  biome: true,
31
+ typecheck: true,
31
32
  knip: true,
32
33
  "comment-check": true,
33
34
  "document-style-check": true,
34
35
  "tsdoc-check": true,
35
36
  },
36
37
  config: {
37
- "comment-check": { ignore: ["FFXIVReplayAnalyzer"] },
38
+ "comment-check": { ignore: ["another-project"] },
38
39
  "tsdoc-check": { error: ["missing-doc"] },
39
40
  },
40
41
  });
@@ -64,17 +65,18 @@ quality-check: 1 of 3 engines failed
64
65
  | `config` | engineごとの起動条件 |
65
66
  | `baseline` | baseline fileのpath `false` なら差分判定を行わない |
66
67
 
67
- engineは `biome` → `knip` → `comment-check` → `document-style-check` → `tsdoc-check` の順に実行する
68
+ engineは `biome` → `typecheck` → `knip` → `comment-check` → `document-style-check` → `tsdoc-check` の順に実行する
68
69
 
69
70
  `engines` は起動の委任だけを表し、起動条件は `config` のengineの下へ置く engineが受け取る条件は次のとおり
70
71
 
71
- | engine | 受け取る条件 |
72
- |--------|--------------|
73
- | `biome` | `targets`、`args` 除外pathは `biome.json` が持つ |
74
- | `knip` | `args` 設定は `knip.ts` が持つ |
75
- | `comment-check` | `ignore`、`targets`、`args` |
76
- | `document-style-check` | `ignore`、`targets`、`args` |
77
- | `tsdoc-check` | `ignore`、`targets`、`args`、`error`(違反として扱うrule名) |
72
+ | engine | 起動するcommand | 受け取る条件 |
73
+ |--------|-----------------|--------------|
74
+ | `biome` | `biome check` | `targets`、`args` 除外pathは `biome.json` が持つ |
75
+ | `typecheck` | `tsc --noEmit` | `args` 設定は `tsconfig.json` が持つ |
76
+ | `knip` | `knip` | `args` 設定は `knip.ts` が持つ |
77
+ | `comment-check` | `comment-check --json` | `ignore`、`targets`、`args` |
78
+ | `document-style-check` | `document-style-check lint --json` | `ignore`、`targets`、`args` |
79
+ | `tsdoc-check` | `tsdoc-check --json` | `ignore`、`targets`、`args`、`error`(違反として扱うrule名) |
78
80
 
79
81
  `args` はengineの既定引数の後ろへ足す 設定fileで表せない起動条件や、engineの引数が変わったときの逃げ道として使う
80
82
 
@@ -94,6 +96,10 @@ engineは `biome` → `knip` → `comment-check` → `document-style-check` →
94
96
 
95
97
  終了codeは0が全engine成功、1が失敗したengineあり、2が設定またはengine起動の失敗
96
98
 
99
+ 色は標準出力が端末のときだけ付ける `NO_COLOR` で無効にし、`FORCE_COLOR` で強制できる `--json` の出力には付けない
100
+
101
+ 色を扱えるengineへは自身の出力の色も許可する Biomeは `--colors=force`、tscは `--pretty` を受け取る
102
+
97
103
  engineが受け取らない条件を書いた場合は渡さず、その旨をそのengineのsectionへ出す
98
104
 
99
105
  ```text
@@ -101,10 +107,12 @@ engineが受け取らない条件を書いた場合は渡さず、その旨を
101
107
  biome: ignore skipped (biome.json holds its settings)
102
108
  ```
103
109
 
104
- BiomeKnipの除外pathは `biome.json` と `knip.ts` が持つ このCLIは同じfileからengineの起動と結果の集約だけを行う
110
+ Biome、型検査、Knipの設定は `biome.json`、`tsconfig.json`、`knip.ts` が持つ このCLIは同じfileからengineの起動と結果の集約だけを行う
105
111
 
106
112
  `comment-check` はbaseline差分を無効化する未作成のpathを渡して起動する 新規と解消済みの判定はengineごとではなく統合CLIが1つのbaseline fileで行うため、既存の `comment-baseline.json` がある場合は `--update-baseline` で移す
107
113
 
114
+ `typecheck` は `tsc --noEmit` を回すだけなので、tsconfigを複数持つProjectは対象のtsconfigごとに起動する必要がある
115
+
108
116
  ## License
109
117
 
110
118
  MIT
package/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  # @yuu1111/quality-check
4
4
 
5
- Runs Biome, Knip, comment-check, document-style-check, and the TSDoc checker
6
- from one CLI instead of one script per project. The baseline diff lives here too.
5
+ Runs Biome, the type checker, Knip, comment-check, document-style-check, and the
6
+ TSDoc checker from one CLI instead of one script per project. The baseline diff
7
+ lives here too.
7
8
 
8
9
  ## Install
9
10
 
@@ -30,13 +31,14 @@ import { defineConfig } from "@yuu1111/quality-check";
30
31
  export default defineConfig({
31
32
  engines: {
32
33
  biome: true,
34
+ typecheck: true,
33
35
  knip: true,
34
36
  "comment-check": true,
35
37
  "document-style-check": true,
36
38
  "tsdoc-check": true,
37
39
  },
38
40
  config: {
39
- "comment-check": { ignore: ["FFXIVReplayAnalyzer"] },
41
+ "comment-check": { ignore: ["another-project"] },
40
42
  "tsdoc-check": { error: ["missing-doc"] },
41
43
  },
42
44
  });
@@ -67,19 +69,20 @@ quality-check: 1 of 3 engines failed
67
69
  | `config` | Conditions of each engine |
68
70
  | `baseline` | Baseline file path; `false` disables the diff |
69
71
 
70
- Engines run in the order `biome`, `knip`, `comment-check`,
72
+ Engines run in the order `biome`, `typecheck`, `knip`, `comment-check`,
71
73
  `document-style-check`, `tsdoc-check`.
72
74
 
73
75
  `engines` only delegates the start-up, and the conditions belong under the
74
- engine in `config`. Each engine takes:
76
+ engine in `config`. Each engine runs:
75
77
 
76
- | Engine | Conditions |
77
- |--------|------------|
78
- | `biome` | `targets`, `args`; `biome.json` holds the excluded paths |
79
- | `knip` | `args`; `knip.ts` holds the settings |
80
- | `comment-check` | `ignore`, `targets`, `args` |
81
- | `document-style-check` | `ignore`, `targets`, `args` |
82
- | `tsdoc-check` | `ignore`, `targets`, `args`, `error` (rule names to fail on) |
78
+ | Engine | Command | Conditions |
79
+ |--------|---------|------------|
80
+ | `biome` | `biome check` | `targets`, `args`; `biome.json` holds the excluded paths |
81
+ | `typecheck` | `tsc --noEmit` | `args`; `tsconfig.json` holds the settings |
82
+ | `knip` | `knip` | `args`; `knip.ts` holds the settings |
83
+ | `comment-check` | `comment-check --json` | `ignore`, `targets`, `args` |
84
+ | `document-style-check` | `document-style-check lint --json` | `ignore`, `targets`, `args` |
85
+ | `tsdoc-check` | `tsdoc-check --json` | `ignore`, `targets`, `args`, `error` (rule names to fail on) |
83
86
 
84
87
  `args` is appended after the engine defaults, for conditions the config cannot
85
88
  express and for the case where an engine changes its arguments.
@@ -101,6 +104,12 @@ express and for the case where an engine changes its arguments.
101
104
  Exit code 0 means every engine passed, 1 that at least one failed, and 2 that the
102
105
  configuration or an engine could not start.
103
106
 
107
+ Color is added only when stdout is a terminal. `NO_COLOR` turns it off and
108
+ `FORCE_COLOR` turns it on; the `--json` output stays plain.
109
+
110
+ The engines that can color their own output receive the same permission: Biome
111
+ gets `--colors=force` and tsc gets `--pretty`.
112
+
104
113
  A condition that an engine does not take is not passed on, and the section says
105
114
  so:
106
115
 
@@ -109,14 +118,18 @@ so:
109
118
  biome: ignore skipped (biome.json holds its settings)
110
119
  ```
111
120
 
112
- Biome and Knip keep their excluded paths in `biome.json` and `knip.ts`. This
113
- CLI starts the engines from one file and aggregates the results.
121
+ Biome, the type checker, and Knip keep their settings in `biome.json`,
122
+ `tsconfig.json`, and `knip.ts`. This CLI starts the engines from one file and
123
+ aggregates the results.
114
124
 
115
125
  `comment-check` runs with an unwritten baseline path so that it reports every
116
126
  finding. The new-and-resolved diff is done by this CLI from a single baseline
117
127
  file, so an existing `comment-baseline.json` is moved over with
118
128
  `--update-baseline`.
119
129
 
130
+ `typecheck` only runs `tsc --noEmit`, so a project with more than one tsconfig
131
+ has to start it once per target.
132
+
120
133
  ## License
121
134
 
122
135
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuu1111/quality-check",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Integrated quality check runner",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
4
  import { createBaseline, readBaseline, writeBaseline } from "./baseline";
5
+ import { ansiPainter, colorEnabled, plainPainter } from "./color";
5
6
  import {
6
7
  DEFAULT_BASELINE_FILE,
7
8
  findConfigFile,
@@ -125,6 +126,8 @@ async function main(argv: string[]): Promise<number> {
125
126
  return 0;
126
127
  }
127
128
  const options = parseArguments(argv);
129
+ const color = colorEnabled(process.stdout, process.env);
130
+ const paint = color ? ansiPainter() : plainPainter;
128
131
  const cwd = process.cwd();
129
132
  const configPath = resolveConfigPath(options, cwd);
130
133
  if (configPath === null) {
@@ -138,6 +141,7 @@ async function main(argv: string[]): Promise<number> {
138
141
  options.update || baselinePath === null
139
142
  ? null
140
143
  : readBaseline(baselinePath),
144
+ color,
141
145
  config,
142
146
  cwd,
143
147
  overrides: { ignore: options.ignores, targets: options.targets },
@@ -153,7 +157,10 @@ async function main(argv: string[]): Promise<number> {
153
157
  );
154
158
  writeBaseline(baselinePath, baseline);
155
159
  console.log(
156
- `Recorded ${baseline.entries.length} entries in ${baselinePath}`,
160
+ paint(
161
+ `Recorded ${baseline.entries.length} entries in ${baselinePath}`,
162
+ "pass",
163
+ ),
157
164
  );
158
165
  return 0;
159
166
  }
@@ -161,9 +168,9 @@ async function main(argv: string[]): Promise<number> {
161
168
  console.log(JSON.stringify(toJsonReport(results), null, "\t"));
162
169
  } else {
163
170
  for (const result of results) {
164
- console.log(formatEngineSection(result));
171
+ console.log(formatEngineSection(result, paint));
165
172
  }
166
- console.log(formatSummary(results));
173
+ console.log(formatSummary(results, paint));
167
174
  }
168
175
  return results.some((result) => result.status !== "passed") ? 1 : 0;
169
176
  }
package/src/color.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 出力行へ割り当てる装飾の種類
3
+ */
4
+ export type Tone = "error" | "header" | "muted" | "pass" | "warn";
5
+
6
+ /**
7
+ * toneに応じて文字列を装飾する関数
8
+ */
9
+ export type Painter = (text: string, tone: Tone) => string;
10
+
11
+ const ANSI_CODES: Record<Tone, string> = {
12
+ error: "31",
13
+ header: "36",
14
+ muted: "2",
15
+ pass: "32",
16
+ warn: "33",
17
+ };
18
+
19
+ /**
20
+ * 装飾を付けずにそのまま返すpainter
21
+ */
22
+ export function plainPainter(text: string, _tone: Tone): string {
23
+ return text;
24
+ }
25
+
26
+ /**
27
+ * ANSI escapeでtoneを色へ割り当てるpainterを作る
28
+ */
29
+ export function ansiPainter(): Painter {
30
+ return (text, tone) => `\u001b[${ANSI_CODES[tone]}m${text}\u001b[0m`;
31
+ }
32
+
33
+ /**
34
+ * 出力先と環境変数から装飾の可否を決める
35
+ */
36
+ export function colorEnabled(
37
+ stream: { isTTY?: boolean | undefined },
38
+ env: Record<string, string | undefined>,
39
+ ): boolean {
40
+ if (env.NO_COLOR !== undefined && env.NO_COLOR !== "") {
41
+ return false;
42
+ }
43
+ if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "") {
44
+ return env.FORCE_COLOR !== "0";
45
+ }
46
+ return stream.isTTY === true;
47
+ }
package/src/config.ts CHANGED
@@ -7,6 +7,7 @@ import { pathToFileURL } from "node:url";
7
7
  */
8
8
  export const ENGINE_NAMES = [
9
9
  "biome",
10
+ "typecheck",
10
11
  "knip",
11
12
  "comment-check",
12
13
  "document-style-check",
@@ -43,6 +44,7 @@ export interface TsdocCheckOptions extends EngineOptions {
43
44
  */
44
45
  export interface EngineConfigMap {
45
46
  biome: EngineOptions;
47
+ typecheck: EngineOptions;
46
48
  knip: EngineOptions;
47
49
  "comment-check": EngineOptions;
48
50
  "document-style-check": EngineOptions;
@@ -95,6 +97,7 @@ function isEngineName(value: string): value is EngineName {
95
97
 
96
98
  const ENGINE_OPTION_KEYS: Record<EngineName, readonly string[]> = {
97
99
  biome: ["args", "ignore", "targets"],
100
+ typecheck: ["args", "ignore", "targets"],
98
101
  knip: ["args", "ignore", "targets"],
99
102
  "comment-check": ["args", "ignore", "targets"],
100
103
  "document-style-check": ["args", "ignore", "targets"],
package/src/engines.ts CHANGED
@@ -33,6 +33,7 @@ export const ENGINE_BINS: Record<EngineName, string> = {
33
33
  "document-style-check": "document-style-check",
34
34
  knip: "knip",
35
35
  "tsdoc-check": "tsdoc-check",
36
+ typecheck: "tsc",
36
37
  };
37
38
 
38
39
  const WINDOWS_SHIMS = [".exe", ".cmd", ".bat", ""];
@@ -51,6 +52,8 @@ export interface RunOverrides {
51
52
  */
52
53
  export interface EngineCommandContext {
53
54
  config: QualityConfig;
55
+ /** engine自身の出力へ色を付けるか */
56
+ color: boolean;
54
57
  /** 対応するengineへだけ足す上書き */
55
58
  overrides: RunOverrides;
56
59
  /** comment-checkのbaseline差分を無効化するために渡す未作成のpath */
@@ -65,6 +68,10 @@ export const ENGINE_LIMITS: Record<
65
68
  Partial<Record<"ignore" | "targets", string>>
66
69
  > = {
67
70
  biome: { ignore: "biome.json holds its settings" },
71
+ typecheck: {
72
+ ignore: "tsconfig.json holds its settings",
73
+ targets: "tsc checks the project named by tsconfig.json",
74
+ },
68
75
  knip: {
69
76
  ignore: "knip.ts holds its settings",
70
77
  targets: "knip analyzes the whole project",
@@ -138,6 +145,22 @@ export function resolveExecutable(
138
145
  return onPath ?? null;
139
146
  }
140
147
 
148
+ /**
149
+ * engine自身の出力へ色を付けるための引数を返す
150
+ */
151
+ function colorArguments(name: EngineName, color: boolean): string[] {
152
+ if (!color) {
153
+ return [];
154
+ }
155
+ if (name === "biome") {
156
+ return ["--colors=force"];
157
+ }
158
+ if (name === "typecheck") {
159
+ return ["--pretty"];
160
+ }
161
+ return [];
162
+ }
163
+
141
164
  /**
142
165
  * engineへ渡すコマンドを組み立てる
143
166
  */
@@ -162,7 +185,21 @@ export function buildEngineCommand(
162
185
  const rules = engineConfig(context.config, "tsdoc-check")?.error ?? [];
163
186
  const errorArguments = rules.flatMap((rule) => ["--error", rule]);
164
187
  if (name === "biome") {
165
- return [executable, "check", ...targets, ...extra];
188
+ return [
189
+ executable,
190
+ "check",
191
+ ...colorArguments(name, context.color),
192
+ ...targets,
193
+ ...extra,
194
+ ];
195
+ }
196
+ if (name === "typecheck") {
197
+ return [
198
+ executable,
199
+ "--noEmit",
200
+ ...colorArguments(name, context.color),
201
+ ...extra,
202
+ ];
166
203
  }
167
204
  if (name === "knip") {
168
205
  return [executable, ...extra];
package/src/report.ts CHANGED
@@ -1,9 +1,14 @@
1
+ import { type Painter, plainPainter } from "./color";
1
2
  import { isFindingEngine } from "./engines";
2
3
  import type { NormalizedFinding } from "./findings";
3
4
  import type { EngineResult } from "./run";
4
5
 
5
- function describeFinding(finding: NormalizedFinding): string {
6
- return `${finding.file}:${finding.line}:${finding.column} ${finding.rule} ${finding.severity} ${finding.text}`;
6
+ function describeFinding(
7
+ finding: NormalizedFinding,
8
+ paint: Painter,
9
+ tone: "error" | "warn",
10
+ ): string {
11
+ return `${finding.file}:${finding.line}:${finding.column} ${finding.rule} ${paint(finding.severity, tone)} ${finding.text}`;
7
12
  }
8
13
 
9
14
  function describeCounts(result: EngineResult): string {
@@ -13,38 +18,48 @@ function describeCounts(result: EngineResult): string {
13
18
  return `${result.reported.length} new, ${result.resolved} resolved, ${result.warnings.length} warnings`;
14
19
  }
15
20
 
16
- function describeStatus(result: EngineResult): string {
21
+ function describeStatus(result: EngineResult, paint: Painter): string {
17
22
  if (result.status === "error") {
18
- return "error";
23
+ return paint("error", "error");
19
24
  }
20
- return `${result.status} (${describeCounts(result)})`;
25
+ const tone = result.status === "passed" ? "pass" : "error";
26
+ return `${paint(result.status, tone)} (${describeCounts(result)})`;
21
27
  }
22
28
 
23
29
  /**
24
30
  * engine1つ分の出力sectionを組み立てる
25
31
  */
26
- export function formatEngineSection(result: EngineResult): string {
27
- const lines = [`== ${result.name} ==`];
32
+ export function formatEngineSection(
33
+ result: EngineResult,
34
+ paint: Painter = plainPainter,
35
+ ): string {
36
+ const lines = [paint(`== ${result.name} ==`, "header")];
28
37
  for (const skipped of result.skipped) {
29
- lines.push(`${result.name}: ${skipped}`);
38
+ lines.push(paint(`${result.name}: ${skipped}`, "muted"));
30
39
  }
31
40
  if (result.output !== "" && !isFindingEngine(result.name)) {
32
41
  lines.push(result.output);
33
42
  }
34
- for (const finding of [...result.reported, ...result.warnings]) {
35
- lines.push(describeFinding(finding));
43
+ for (const finding of result.reported) {
44
+ lines.push(describeFinding(finding, paint, "error"));
45
+ }
46
+ for (const finding of result.warnings) {
47
+ lines.push(describeFinding(finding, paint, "warn"));
36
48
  }
37
49
  if (result.message !== undefined) {
38
- lines.push(result.message);
50
+ lines.push(paint(result.message, "error"));
39
51
  }
40
- lines.push(`${result.name}: ${describeStatus(result)}`);
52
+ lines.push(`${result.name}: ${describeStatus(result, paint)}`);
41
53
  return lines.join("\n");
42
54
  }
43
55
 
44
56
  /**
45
57
  * 失敗したengineを列挙した集約summaryを組み立てる
46
58
  */
47
- export function formatSummary(results: EngineResult[]): string {
59
+ export function formatSummary(
60
+ results: EngineResult[],
61
+ paint: Painter = plainPainter,
62
+ ): string {
48
63
  const failed = results
49
64
  .filter((result) => result.status !== "passed")
50
65
  .map((result) => result.name);
@@ -52,14 +67,17 @@ export function formatSummary(results: EngineResult[]): string {
52
67
  .filter((result) => result.status === "passed")
53
68
  .map((result) => result.name);
54
69
  if (failed.length === 0) {
55
- return `quality-check: ${results.length} engines passed`;
70
+ return paint(`quality-check: ${results.length} engines passed`, "pass");
56
71
  }
57
72
  const lines = [
58
- `quality-check: ${failed.length} of ${results.length} engines failed`,
59
- ` failed: ${failed.join(", ")}`,
73
+ paint(
74
+ `quality-check: ${failed.length} of ${results.length} engines failed`,
75
+ "error",
76
+ ),
77
+ ` ${paint("failed:", "error")} ${failed.join(", ")}`,
60
78
  ];
61
79
  if (passed.length > 0) {
62
- lines.push(` passed: ${passed.join(", ")}`);
80
+ lines.push(` ${paint("passed:", "pass")} ${passed.join(", ")}`);
63
81
  }
64
82
  return lines.join("\n");
65
83
  }
package/src/run.ts CHANGED
@@ -44,6 +44,8 @@ export interface EngineResult {
44
44
  export interface RunOptions {
45
45
  /** 適用するbaseline nullなら差分判定を行わない */
46
46
  baseline: BaselineFile | null;
47
+ /** engine自身の出力へ色を付けるか */
48
+ color: boolean;
47
49
  config: QualityConfig;
48
50
  cwd: string;
49
51
  /** コマンドラインから渡された起動条件の上書き */
@@ -188,6 +190,7 @@ async function runEngine(
188
190
  export async function runEngines(options: RunOptions): Promise<EngineResult[]> {
189
191
  const runner = options.runner ?? runEngineProcess;
190
192
  const context: EngineCommandContext = {
193
+ color: options.color,
191
194
  config: options.config,
192
195
  overrides: options.overrides,
193
196
  rawBaseline: options.rawBaseline,