@yuu1111/quality-check 0.2.0 → 0.4.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 +42 -16
- package/README.md +52 -21
- package/package.json +1 -1
- package/src/cli.ts +2 -10
- package/src/config.ts +132 -47
- package/src/engines.ts +82 -12
- package/src/report.ts +4 -0
- package/src/run.ts +22 -5
package/README.ja.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @yuu1111/quality-check
|
|
4
4
|
|
|
5
|
-
Projectごとのscriptから個別に呼んでいたBiome
|
|
5
|
+
Projectごとのscriptから個別に呼んでいたBiome、型検査、Knip、comment-check、document-style-check、TSDoc checkerを1つのCLIへまとめる baselineの差分判定もここで行う
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ bun add -D @yuu1111/quality-check
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
`quality.config.ts`
|
|
15
|
+
`quality.config.ts` で起動するengineとengineごとの起動条件を指定し、scriptから起動する
|
|
16
16
|
|
|
17
17
|
```json
|
|
18
18
|
{
|
|
@@ -26,14 +26,18 @@ bun add -D @yuu1111/quality-check
|
|
|
26
26
|
import { defineConfig } from "@yuu1111/quality-check";
|
|
27
27
|
|
|
28
28
|
export default defineConfig({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
engines: {
|
|
30
|
+
biome: true,
|
|
31
|
+
typecheck: true,
|
|
32
|
+
knip: true,
|
|
33
|
+
"comment-check": true,
|
|
34
|
+
"document-style-check": true,
|
|
35
|
+
"tsdoc-check": true,
|
|
36
|
+
},
|
|
37
|
+
config: {
|
|
38
|
+
"comment-check": { ignore: ["FFXIVReplayAnalyzer"] },
|
|
39
|
+
"tsdoc-check": { error: ["missing-doc"] },
|
|
40
|
+
},
|
|
37
41
|
});
|
|
38
42
|
```
|
|
39
43
|
|
|
@@ -57,13 +61,24 @@ quality-check: 1 of 3 engines failed
|
|
|
57
61
|
|
|
58
62
|
| Field | Description |
|
|
59
63
|
|-------|-------------|
|
|
60
|
-
| `engines` | 起動するengine 値は `true` または `
|
|
61
|
-
| `
|
|
64
|
+
| `engines` | 起動するengine 値は `true` または `false` |
|
|
65
|
+
| `config` | engineごとの起動条件 |
|
|
62
66
|
| `baseline` | baseline fileのpath `false` なら差分判定を行わない |
|
|
63
|
-
| `targets` | file走査engineへ渡す対象path |
|
|
64
67
|
|
|
65
|
-
engineは `biome` → `knip` → `comment-check` → `document-style-check` → `tsdoc-check` の順に実行する
|
|
66
|
-
|
|
68
|
+
engineは `biome` → `typecheck` → `knip` → `comment-check` → `document-style-check` → `tsdoc-check` の順に実行する
|
|
69
|
+
|
|
70
|
+
`engines` は起動の委任だけを表し、起動条件は `config` のengineの下へ置く engineが受け取る条件は次のとおり
|
|
71
|
+
|
|
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名) |
|
|
80
|
+
|
|
81
|
+
`args` はengineの既定引数の後ろへ足す 設定fileで表せない起動条件や、engineの引数が変わったときの逃げ道として使う
|
|
67
82
|
|
|
68
83
|
## Options
|
|
69
84
|
|
|
@@ -75,14 +90,25 @@ engineは `biome` → `knip` → `comment-check` → `document-style-check` →
|
|
|
75
90
|
| `--update-baseline` | 現在の検出でbaselineを置き換える |
|
|
76
91
|
| `--json` | engineごとの結果をJSONで出力する |
|
|
77
92
|
|
|
93
|
+
`--ignore` と位置引数の対象pathは、その条件を受け取るengineへだけ渡す
|
|
94
|
+
|
|
78
95
|
## Notes
|
|
79
96
|
|
|
80
97
|
終了codeは0が全engine成功、1が失敗したengineあり、2が設定またはengine起動の失敗
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
engineが受け取らない条件を書いた場合は渡さず、その旨をそのengineのsectionへ出す
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
== biome ==
|
|
103
|
+
biome: ignore skipped (biome.json holds its settings)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Biome、型検査、Knipの設定は `biome.json`、`tsconfig.json`、`knip.ts` が持つ このCLIは同じfileからengineの起動と結果の集約だけを行う
|
|
83
107
|
|
|
84
108
|
`comment-check` はbaseline差分を無効化する未作成のpathを渡して起動する 新規と解消済みの判定はengineごとではなく統合CLIが1つのbaseline fileで行うため、既存の `comment-baseline.json` がある場合は `--update-baseline` で移す
|
|
85
109
|
|
|
110
|
+
`typecheck` は `tsc --noEmit` を回すだけなので、tsconfigを複数持つProjectは対象のtsconfigごとに起動する必要がある
|
|
111
|
+
|
|
86
112
|
## License
|
|
87
113
|
|
|
88
114
|
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
|
|
6
|
-
from one CLI instead of one script per project. The baseline diff
|
|
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
|
|
|
@@ -13,8 +14,8 @@ bun add -D @yuu1111/quality-check
|
|
|
13
14
|
|
|
14
15
|
## Usage
|
|
15
16
|
|
|
16
|
-
Declare the engines and the
|
|
17
|
-
CLI from a script:
|
|
17
|
+
Declare the engines to run and the conditions of each engine in
|
|
18
|
+
`quality.config.ts`, then call the CLI from a script:
|
|
18
19
|
|
|
19
20
|
```json
|
|
20
21
|
{
|
|
@@ -28,14 +29,18 @@ CLI from a script:
|
|
|
28
29
|
import { defineConfig } from "@yuu1111/quality-check";
|
|
29
30
|
|
|
30
31
|
export default defineConfig({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
engines: {
|
|
33
|
+
biome: true,
|
|
34
|
+
typecheck: true,
|
|
35
|
+
knip: true,
|
|
36
|
+
"comment-check": true,
|
|
37
|
+
"document-style-check": true,
|
|
38
|
+
"tsdoc-check": true,
|
|
39
|
+
},
|
|
40
|
+
config: {
|
|
41
|
+
"comment-check": { ignore: ["FFXIVReplayAnalyzer"] },
|
|
42
|
+
"tsdoc-check": { error: ["missing-doc"] },
|
|
43
|
+
},
|
|
39
44
|
});
|
|
40
45
|
```
|
|
41
46
|
|
|
@@ -60,14 +65,27 @@ quality-check: 1 of 3 engines failed
|
|
|
60
65
|
|
|
61
66
|
| Field | Description |
|
|
62
67
|
|-------|-------------|
|
|
63
|
-
| `engines` | Engines to run, as `true` or `
|
|
64
|
-
| `
|
|
68
|
+
| `engines` | Engines to run, as `true` or `false` |
|
|
69
|
+
| `config` | Conditions of each engine |
|
|
65
70
|
| `baseline` | Baseline file path; `false` disables the diff |
|
|
66
|
-
| `targets` | Targets passed to the file-scanning engines |
|
|
67
71
|
|
|
68
|
-
Engines run in the order `biome`, `
|
|
69
|
-
`
|
|
70
|
-
|
|
72
|
+
Engines run in the order `biome`, `typecheck`, `knip`, `comment-check`,
|
|
73
|
+
`document-style-check`, `tsdoc-check`.
|
|
74
|
+
|
|
75
|
+
`engines` only delegates the start-up, and the conditions belong under the
|
|
76
|
+
engine in `config`. Each engine runs:
|
|
77
|
+
|
|
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) |
|
|
86
|
+
|
|
87
|
+
`args` is appended after the engine defaults, for conditions the config cannot
|
|
88
|
+
express and for the case where an engine changes its arguments.
|
|
71
89
|
|
|
72
90
|
## Options
|
|
73
91
|
|
|
@@ -79,20 +97,33 @@ Engines run in the order `biome`, `knip`, `comment-check`, `document-style-check
|
|
|
79
97
|
| `--update-baseline` | Replace the baseline with the current findings |
|
|
80
98
|
| `--json` | Print the per-engine results as JSON |
|
|
81
99
|
|
|
100
|
+
`--ignore` and the positional targets reach only the engines that take them.
|
|
101
|
+
|
|
82
102
|
## Notes
|
|
83
103
|
|
|
84
104
|
Exit code 0 means every engine passed, 1 that at least one failed, and 2 that the
|
|
85
105
|
configuration or an engine could not start.
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
A condition that an engine does not take is not passed on, and the section says
|
|
108
|
+
so:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
== biome ==
|
|
112
|
+
biome: ignore skipped (biome.json holds its settings)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Biome, the type checker, and Knip keep their settings in `biome.json`,
|
|
116
|
+
`tsconfig.json`, and `knip.ts`. This CLI starts the engines from one file and
|
|
117
|
+
aggregates the results.
|
|
90
118
|
|
|
91
119
|
`comment-check` runs with an unwritten baseline path so that it reports every
|
|
92
120
|
finding. The new-and-resolved diff is done by this CLI from a single baseline
|
|
93
121
|
file, so an existing `comment-baseline.json` is moved over with
|
|
94
122
|
`--update-baseline`.
|
|
95
123
|
|
|
124
|
+
`typecheck` only runs `tsc --noEmit`, so a project with more than one tsconfig
|
|
125
|
+
has to start it once per target.
|
|
126
|
+
|
|
96
127
|
## License
|
|
97
128
|
|
|
98
129
|
MIT
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -98,13 +98,6 @@ function parseArguments(argv: string[]): Options {
|
|
|
98
98
|
return options;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function mergeIgnores(config: QualityConfig, ignores: string[]): QualityConfig {
|
|
102
|
-
if (ignores.length === 0) {
|
|
103
|
-
return config;
|
|
104
|
-
}
|
|
105
|
-
return { ...config, ignore: [...(config.ignore ?? []), ...ignores] };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
101
|
function resolveBaselinePath(
|
|
109
102
|
options: Options,
|
|
110
103
|
config: QualityConfig,
|
|
@@ -138,7 +131,7 @@ async function main(argv: string[]): Promise<number> {
|
|
|
138
131
|
console.error(`quality.config.ts not found in ${cwd}`);
|
|
139
132
|
return 2;
|
|
140
133
|
}
|
|
141
|
-
const config =
|
|
134
|
+
const config = await loadConfig(configPath);
|
|
142
135
|
const baselinePath = resolveBaselinePath(options, config, cwd);
|
|
143
136
|
const results = await runEngines({
|
|
144
137
|
baseline:
|
|
@@ -147,9 +140,8 @@ async function main(argv: string[]): Promise<number> {
|
|
|
147
140
|
: readBaseline(baselinePath),
|
|
148
141
|
config,
|
|
149
142
|
cwd,
|
|
143
|
+
overrides: { ignore: options.ignores, targets: options.targets },
|
|
150
144
|
rawBaseline: join(tmpdir(), `quality-check-raw-${process.pid}.json`),
|
|
151
|
-
targets:
|
|
152
|
-
options.targets.length > 0 ? options.targets : (config.targets ?? ["."]),
|
|
153
145
|
});
|
|
154
146
|
if (options.update) {
|
|
155
147
|
if (baselinePath === null) {
|
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",
|
|
@@ -19,25 +20,47 @@ export const ENGINE_NAMES = [
|
|
|
19
20
|
export type EngineName = (typeof ENGINE_NAMES)[number];
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* engine
|
|
23
|
+
* engineへ渡す起動条件
|
|
23
24
|
*/
|
|
24
25
|
export interface EngineOptions {
|
|
26
|
+
/** 検査から外すpath engineが受け取れないときはreportへ出す */
|
|
27
|
+
ignore?: string[];
|
|
28
|
+
/** 検査する対象path engineが受け取れないときはreportへ出す */
|
|
29
|
+
targets?: string[];
|
|
25
30
|
/** engineの既定引数の後ろへ足す引数 */
|
|
26
31
|
args?: string[];
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
/**
|
|
35
|
+
* TSDoc検査へ渡す起動条件
|
|
36
|
+
*/
|
|
37
|
+
export interface TsdocCheckOptions extends EngineOptions {
|
|
38
|
+
/** 違反として扱うrule名 */
|
|
39
|
+
error?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* engine名ごとの起動条件
|
|
44
|
+
*/
|
|
45
|
+
export interface EngineConfigMap {
|
|
46
|
+
biome: EngineOptions;
|
|
47
|
+
typecheck: EngineOptions;
|
|
48
|
+
knip: EngineOptions;
|
|
49
|
+
"comment-check": EngineOptions;
|
|
50
|
+
"document-style-check": EngineOptions;
|
|
51
|
+
"tsdoc-check": TsdocCheckOptions;
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
/**
|
|
30
55
|
* quality.config.tsが受け付ける統合検査の設定
|
|
31
56
|
*/
|
|
32
57
|
export interface QualityConfig {
|
|
33
58
|
/** 起動するengine 値がfalseまたは未指定のengineは起動しない */
|
|
34
|
-
engines: Partial<Record<EngineName, boolean
|
|
35
|
-
/**
|
|
36
|
-
|
|
59
|
+
engines: Partial<Record<EngineName, boolean>>;
|
|
60
|
+
/** engineごとの起動条件 省略したengineは既定値で起動する */
|
|
61
|
+
config?: Partial<EngineConfigMap>;
|
|
37
62
|
/** baseline fileのpath falseなら差分判定を行わない */
|
|
38
63
|
baseline?: string | false;
|
|
39
|
-
/** file走査engineへ渡す対象path */
|
|
40
|
-
targets?: string[];
|
|
41
64
|
}
|
|
42
65
|
|
|
43
66
|
/**
|
|
@@ -72,6 +95,15 @@ function isEngineName(value: string): value is EngineName {
|
|
|
72
95
|
return (ENGINE_NAMES as readonly string[]).includes(value);
|
|
73
96
|
}
|
|
74
97
|
|
|
98
|
+
const ENGINE_OPTION_KEYS: Record<EngineName, readonly string[]> = {
|
|
99
|
+
biome: ["args", "ignore", "targets"],
|
|
100
|
+
typecheck: ["args", "ignore", "targets"],
|
|
101
|
+
knip: ["args", "ignore", "targets"],
|
|
102
|
+
"comment-check": ["args", "ignore", "targets"],
|
|
103
|
+
"document-style-check": ["args", "ignore", "targets"],
|
|
104
|
+
"tsdoc-check": ["args", "error", "ignore", "targets"],
|
|
105
|
+
};
|
|
106
|
+
|
|
75
107
|
function readStringArray(value: unknown, field: string): string[] | undefined {
|
|
76
108
|
if (value === undefined) {
|
|
77
109
|
return undefined;
|
|
@@ -98,27 +130,96 @@ function readBaseline(
|
|
|
98
130
|
throw new Error(`${source}: baseline must be a path string or false`);
|
|
99
131
|
}
|
|
100
132
|
|
|
101
|
-
function
|
|
133
|
+
function parseEngines(
|
|
102
134
|
value: unknown,
|
|
103
135
|
source: string,
|
|
104
|
-
|
|
105
|
-
): boolean | EngineOptions {
|
|
106
|
-
if (typeof value === "boolean") {
|
|
107
|
-
return value;
|
|
108
|
-
}
|
|
136
|
+
): Partial<Record<EngineName, boolean>> {
|
|
109
137
|
if (!isJsonObject(value)) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`${source}: engines.${name} must be a boolean or an object`,
|
|
112
|
-
);
|
|
138
|
+
throw new Error(`${source} must export an engines object`);
|
|
113
139
|
}
|
|
114
|
-
const
|
|
140
|
+
const engines: Partial<Record<EngineName, boolean>> = {};
|
|
141
|
+
for (const [name, enabled] of Object.entries(value)) {
|
|
142
|
+
if (!isEngineName(name)) {
|
|
143
|
+
throw new Error(`${source}: unknown engine: ${name}`);
|
|
144
|
+
}
|
|
145
|
+
if (typeof enabled !== "boolean") {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`${source}: engines.${name} must be a boolean 起動条件はconfigへ置く`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
engines[name] = enabled;
|
|
151
|
+
}
|
|
152
|
+
if (ENGINE_NAMES.every((name) => !engines[name])) {
|
|
153
|
+
throw new Error(`${source} must enable at least one engine`);
|
|
154
|
+
}
|
|
155
|
+
return engines;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function parseEngineOptions(
|
|
159
|
+
value: JsonObject,
|
|
160
|
+
source: string,
|
|
161
|
+
name: EngineName,
|
|
162
|
+
): TsdocCheckOptions {
|
|
163
|
+
const unknown = Object.keys(value).filter(
|
|
164
|
+
(key) => !ENGINE_OPTION_KEYS[name].includes(key),
|
|
165
|
+
);
|
|
115
166
|
if (unknown.length > 0) {
|
|
116
167
|
throw new Error(
|
|
117
|
-
`${source}:
|
|
168
|
+
`${source}: config.${name} has an unknown option: ${unknown[0]}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const options: TsdocCheckOptions = {};
|
|
172
|
+
const ignore = readStringArray(
|
|
173
|
+
value.ignore,
|
|
174
|
+
`${source}: config.${name}.ignore`,
|
|
175
|
+
);
|
|
176
|
+
if (ignore !== undefined) {
|
|
177
|
+
options.ignore = ignore;
|
|
178
|
+
}
|
|
179
|
+
const targets = readStringArray(
|
|
180
|
+
value.targets,
|
|
181
|
+
`${source}: config.${name}.targets`,
|
|
182
|
+
);
|
|
183
|
+
if (targets !== undefined) {
|
|
184
|
+
options.targets = targets;
|
|
185
|
+
}
|
|
186
|
+
const args = readStringArray(value.args, `${source}: config.${name}.args`);
|
|
187
|
+
if (args !== undefined) {
|
|
188
|
+
options.args = args;
|
|
189
|
+
}
|
|
190
|
+
if (name === "tsdoc-check") {
|
|
191
|
+
const error = readStringArray(
|
|
192
|
+
value.error,
|
|
193
|
+
`${source}: config.${name}.error`,
|
|
118
194
|
);
|
|
195
|
+
if (error !== undefined) {
|
|
196
|
+
options.error = error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return options;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function parseEngineConfig(
|
|
203
|
+
value: unknown,
|
|
204
|
+
source: string,
|
|
205
|
+
): Partial<EngineConfigMap> | undefined {
|
|
206
|
+
if (value === undefined) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
if (!isJsonObject(value)) {
|
|
210
|
+
throw new Error(`${source}: config must be an object`);
|
|
211
|
+
}
|
|
212
|
+
const config: Partial<EngineConfigMap> = {};
|
|
213
|
+
for (const [name, options] of Object.entries(value)) {
|
|
214
|
+
if (!isEngineName(name)) {
|
|
215
|
+
throw new Error(`${source}: unknown engine in config: ${name}`);
|
|
216
|
+
}
|
|
217
|
+
if (!isJsonObject(options)) {
|
|
218
|
+
throw new Error(`${source}: config.${name} must be an object`);
|
|
219
|
+
}
|
|
220
|
+
config[name] = parseEngineOptions(options, source, name);
|
|
119
221
|
}
|
|
120
|
-
|
|
121
|
-
return args === undefined ? {} : { args };
|
|
222
|
+
return config;
|
|
122
223
|
}
|
|
123
224
|
|
|
124
225
|
/**
|
|
@@ -128,32 +229,17 @@ export function parseConfig(value: unknown, source: string): QualityConfig {
|
|
|
128
229
|
if (!isJsonObject(value)) {
|
|
129
230
|
throw new Error(`${source} must export a config object`);
|
|
130
231
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
throw new Error(`${source}: unknown engine: ${name}`);
|
|
138
|
-
}
|
|
139
|
-
engines[name] = parseEngineOptions(options, source, name);
|
|
232
|
+
const config: QualityConfig = {
|
|
233
|
+
engines: parseEngines(value.engines, source),
|
|
234
|
+
};
|
|
235
|
+
const engineConfig = parseEngineConfig(value.config, source);
|
|
236
|
+
if (engineConfig !== undefined) {
|
|
237
|
+
config.config = engineConfig;
|
|
140
238
|
}
|
|
141
|
-
if (ENGINE_NAMES.every((name) => !engines[name])) {
|
|
142
|
-
throw new Error(`${source} must enable at least one engine`);
|
|
143
|
-
}
|
|
144
|
-
const config: QualityConfig = { engines };
|
|
145
239
|
const baseline = readBaseline(value.baseline, source);
|
|
146
240
|
if (baseline !== undefined) {
|
|
147
241
|
config.baseline = baseline;
|
|
148
242
|
}
|
|
149
|
-
const ignore = readStringArray(value.ignore, `${source}: ignore`);
|
|
150
|
-
if (ignore !== undefined) {
|
|
151
|
-
config.ignore = ignore;
|
|
152
|
-
}
|
|
153
|
-
const targets = readStringArray(value.targets, `${source}: targets`);
|
|
154
|
-
if (targets !== undefined) {
|
|
155
|
-
config.targets = targets;
|
|
156
|
-
}
|
|
157
243
|
return config;
|
|
158
244
|
}
|
|
159
245
|
|
|
@@ -165,17 +251,16 @@ export function enabledEngines(config: QualityConfig): EngineName[] {
|
|
|
165
251
|
}
|
|
166
252
|
|
|
167
253
|
/**
|
|
168
|
-
* engine
|
|
254
|
+
* engineの起動条件を返す 無効なengineにはnullを返す
|
|
169
255
|
*/
|
|
170
|
-
export function
|
|
256
|
+
export function engineConfig<K extends EngineName>(
|
|
171
257
|
config: QualityConfig,
|
|
172
|
-
name:
|
|
173
|
-
):
|
|
174
|
-
|
|
175
|
-
if (!value) {
|
|
258
|
+
name: K,
|
|
259
|
+
): EngineConfigMap[K] | null {
|
|
260
|
+
if (!config.engines[name]) {
|
|
176
261
|
return null;
|
|
177
262
|
}
|
|
178
|
-
return
|
|
263
|
+
return config.config?.[name] ?? ({} as EngineConfigMap[K]);
|
|
179
264
|
}
|
|
180
265
|
|
|
181
266
|
/**
|
package/src/engines.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type EngineName,
|
|
5
|
+
type EngineOptions,
|
|
6
|
+
engineConfig,
|
|
7
|
+
type QualityConfig,
|
|
8
|
+
} from "./config";
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* 子プロセスとして起動したengineの生の結果
|
|
@@ -28,20 +33,71 @@ export const ENGINE_BINS: Record<EngineName, string> = {
|
|
|
28
33
|
"document-style-check": "document-style-check",
|
|
29
34
|
knip: "knip",
|
|
30
35
|
"tsdoc-check": "tsdoc-check",
|
|
36
|
+
typecheck: "tsc",
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
const WINDOWS_SHIMS = [".exe", ".cmd", ".bat", ""];
|
|
34
40
|
const POSIX_SHIMS = [""];
|
|
35
41
|
|
|
42
|
+
/**
|
|
43
|
+
* コマンドラインから渡された起動条件の上書き
|
|
44
|
+
*/
|
|
45
|
+
export interface RunOverrides {
|
|
46
|
+
ignore: string[];
|
|
47
|
+
targets: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
/**
|
|
37
51
|
* engineのコマンドを組み立てるための実行条件
|
|
38
52
|
*/
|
|
39
53
|
export interface EngineCommandContext {
|
|
40
54
|
config: QualityConfig;
|
|
41
|
-
|
|
55
|
+
/** 対応するengineへだけ足す上書き */
|
|
56
|
+
overrides: RunOverrides;
|
|
42
57
|
/** comment-checkのbaseline差分を無効化するために渡す未作成のpath */
|
|
43
58
|
rawBaseline: string;
|
|
44
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* engineが受け取らない起動条件と、その設定の持ち主
|
|
63
|
+
*/
|
|
64
|
+
export const ENGINE_LIMITS: Record<
|
|
65
|
+
EngineName,
|
|
66
|
+
Partial<Record<"ignore" | "targets", string>>
|
|
67
|
+
> = {
|
|
68
|
+
biome: { ignore: "biome.json holds its settings" },
|
|
69
|
+
typecheck: {
|
|
70
|
+
ignore: "tsconfig.json holds its settings",
|
|
71
|
+
targets: "tsc checks the project named by tsconfig.json",
|
|
72
|
+
},
|
|
73
|
+
knip: {
|
|
74
|
+
ignore: "knip.ts holds its settings",
|
|
75
|
+
targets: "knip analyzes the whole project",
|
|
76
|
+
},
|
|
77
|
+
"comment-check": {},
|
|
78
|
+
"document-style-check": {},
|
|
79
|
+
"tsdoc-check": {},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* engineが受け取らないため渡さなかった起動条件を返す
|
|
84
|
+
*/
|
|
85
|
+
export function skippedEngineOptions(
|
|
86
|
+
name: EngineName,
|
|
87
|
+
options: EngineOptions | undefined,
|
|
88
|
+
): string[] {
|
|
89
|
+
if (options === undefined) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const limits = ENGINE_LIMITS[name];
|
|
93
|
+
const skipped: string[] = [];
|
|
94
|
+
for (const key of ["ignore", "targets"] as const) {
|
|
95
|
+
const reason = limits[key];
|
|
96
|
+
if (reason !== undefined && options[key] !== undefined) {
|
|
97
|
+
skipped.push(`${key} skipped (${reason})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return skipped;
|
|
45
101
|
}
|
|
46
102
|
|
|
47
103
|
/**
|
|
@@ -95,24 +151,37 @@ export function buildEngineCommand(
|
|
|
95
151
|
executable: string,
|
|
96
152
|
context: EngineCommandContext,
|
|
97
153
|
): string[] {
|
|
98
|
-
const
|
|
154
|
+
const options: EngineOptions = engineConfig(context.config, name) ?? {};
|
|
155
|
+
const limits = ENGINE_LIMITS[name];
|
|
156
|
+
const extra = options.args ?? [];
|
|
157
|
+
const ignores =
|
|
158
|
+
limits.ignore === undefined
|
|
159
|
+
? [...(options.ignore ?? []), ...context.overrides.ignore]
|
|
160
|
+
: [];
|
|
161
|
+
const requested =
|
|
162
|
+
context.overrides.targets.length > 0
|
|
163
|
+
? context.overrides.targets
|
|
164
|
+
: (options.targets ?? ["."]);
|
|
165
|
+
const targets = limits.targets === undefined ? requested : [];
|
|
166
|
+
const ignoreArguments = ignores.flatMap((ignore) => ["--ignore", ignore]);
|
|
167
|
+
const rules = engineConfig(context.config, "tsdoc-check")?.error ?? [];
|
|
168
|
+
const errorArguments = rules.flatMap((rule) => ["--error", rule]);
|
|
99
169
|
if (name === "biome") {
|
|
100
|
-
return [executable, "check", ...
|
|
170
|
+
return [executable, "check", ...targets, ...extra];
|
|
171
|
+
}
|
|
172
|
+
if (name === "typecheck") {
|
|
173
|
+
return [executable, "--noEmit", ...extra];
|
|
101
174
|
}
|
|
102
175
|
if (name === "knip") {
|
|
103
176
|
return [executable, ...extra];
|
|
104
177
|
}
|
|
105
|
-
const ignoreArguments = context.ignores.flatMap((ignore) => [
|
|
106
|
-
"--ignore",
|
|
107
|
-
ignore,
|
|
108
|
-
]);
|
|
109
178
|
if (name === "comment-check") {
|
|
110
179
|
return [
|
|
111
180
|
executable,
|
|
112
181
|
"--json",
|
|
113
182
|
"--baseline",
|
|
114
183
|
context.rawBaseline,
|
|
115
|
-
...
|
|
184
|
+
...targets,
|
|
116
185
|
...ignoreArguments,
|
|
117
186
|
...extra,
|
|
118
187
|
];
|
|
@@ -122,7 +191,7 @@ export function buildEngineCommand(
|
|
|
122
191
|
executable,
|
|
123
192
|
"lint",
|
|
124
193
|
"--json",
|
|
125
|
-
...
|
|
194
|
+
...targets,
|
|
126
195
|
...ignoreArguments,
|
|
127
196
|
...extra,
|
|
128
197
|
];
|
|
@@ -130,7 +199,8 @@ export function buildEngineCommand(
|
|
|
130
199
|
return [
|
|
131
200
|
executable,
|
|
132
201
|
"--json",
|
|
133
|
-
...
|
|
202
|
+
...errorArguments,
|
|
203
|
+
...targets,
|
|
134
204
|
...ignoreArguments,
|
|
135
205
|
...extra,
|
|
136
206
|
];
|
package/src/report.ts
CHANGED
|
@@ -25,6 +25,9 @@ function describeStatus(result: EngineResult): string {
|
|
|
25
25
|
*/
|
|
26
26
|
export function formatEngineSection(result: EngineResult): string {
|
|
27
27
|
const lines = [`== ${result.name} ==`];
|
|
28
|
+
for (const skipped of result.skipped) {
|
|
29
|
+
lines.push(`${result.name}: ${skipped}`);
|
|
30
|
+
}
|
|
28
31
|
if (result.output !== "" && !isFindingEngine(result.name)) {
|
|
29
32
|
lines.push(result.output);
|
|
30
33
|
}
|
|
@@ -73,6 +76,7 @@ export function toJsonReport(results: EngineResult[]): unknown {
|
|
|
73
76
|
name: result.name,
|
|
74
77
|
reported: result.reported,
|
|
75
78
|
resolved: result.resolved,
|
|
79
|
+
skipped: result.skipped,
|
|
76
80
|
status: result.status,
|
|
77
81
|
warnings: result.warnings,
|
|
78
82
|
})),
|
package/src/run.ts
CHANGED
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
type EngineProcessResult,
|
|
8
8
|
type EngineRunner,
|
|
9
9
|
isFindingEngine,
|
|
10
|
+
type RunOverrides,
|
|
10
11
|
resolveExecutable,
|
|
11
12
|
runEngineProcess,
|
|
13
|
+
skippedEngineOptions,
|
|
12
14
|
} from "./engines";
|
|
13
15
|
import { type NormalizedFinding, parseFindings } from "./findings";
|
|
14
16
|
|
|
@@ -30,6 +32,8 @@ export interface EngineResult {
|
|
|
30
32
|
/** baseline適用後に残った阻害する検出 */
|
|
31
33
|
reported: NormalizedFinding[];
|
|
32
34
|
resolved: number;
|
|
35
|
+
/** engineが受け取らなかった起動条件 */
|
|
36
|
+
skipped: string[];
|
|
33
37
|
status: EngineStatus;
|
|
34
38
|
warnings: NormalizedFinding[];
|
|
35
39
|
}
|
|
@@ -42,8 +46,8 @@ export interface RunOptions {
|
|
|
42
46
|
baseline: BaselineFile | null;
|
|
43
47
|
config: QualityConfig;
|
|
44
48
|
cwd: string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
49
|
+
/** コマンドラインから渡された起動条件の上書き */
|
|
50
|
+
overrides: RunOverrides;
|
|
47
51
|
/** comment-checkのbaseline差分を無効化する未作成のpath */
|
|
48
52
|
rawBaseline: string;
|
|
49
53
|
/** engineの実行fileを解決する関数 テストでは差し替える */
|
|
@@ -63,6 +67,7 @@ function baseResult(
|
|
|
63
67
|
output,
|
|
64
68
|
reported: [],
|
|
65
69
|
resolved: 0,
|
|
70
|
+
skipped: [],
|
|
66
71
|
warnings: [],
|
|
67
72
|
};
|
|
68
73
|
}
|
|
@@ -144,7 +149,7 @@ async function runProcessEngine(
|
|
|
144
149
|
};
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
async function
|
|
152
|
+
async function executeEngine(
|
|
148
153
|
name: EngineName,
|
|
149
154
|
options: RunOptions,
|
|
150
155
|
context: EngineCommandContext,
|
|
@@ -164,6 +169,19 @@ async function runEngine(
|
|
|
164
169
|
return await runProcessEngine(name, options, executable, context, runner);
|
|
165
170
|
}
|
|
166
171
|
|
|
172
|
+
async function runEngine(
|
|
173
|
+
name: EngineName,
|
|
174
|
+
options: RunOptions,
|
|
175
|
+
context: EngineCommandContext,
|
|
176
|
+
runner: EngineRunner,
|
|
177
|
+
): Promise<EngineResult> {
|
|
178
|
+
const result = await executeEngine(name, options, context, runner);
|
|
179
|
+
return {
|
|
180
|
+
...result,
|
|
181
|
+
skipped: skippedEngineOptions(name, options.config.config?.[name]),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
/**
|
|
168
186
|
* 設定で有効なengineを順に起動する
|
|
169
187
|
*/
|
|
@@ -171,9 +189,8 @@ export async function runEngines(options: RunOptions): Promise<EngineResult[]> {
|
|
|
171
189
|
const runner = options.runner ?? runEngineProcess;
|
|
172
190
|
const context: EngineCommandContext = {
|
|
173
191
|
config: options.config,
|
|
174
|
-
|
|
192
|
+
overrides: options.overrides,
|
|
175
193
|
rawBaseline: options.rawBaseline,
|
|
176
|
-
targets: options.targets,
|
|
177
194
|
};
|
|
178
195
|
const results: EngineResult[] = [];
|
|
179
196
|
for (const name of enabledEngines(options.config)) {
|