@yuu1111/quality-check 1.0.0 → 4.0.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/src/engines.ts DELETED
@@ -1,403 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import {
4
- type EngineName,
5
- type EngineOptions,
6
- engineConfig,
7
- type QualityConfig,
8
- RULE_VOCABULARY,
9
- type RuleState,
10
- } from "./config";
11
-
12
- /**
13
- * 子プロセスとして起動したengineの生の結果
14
- */
15
- export interface EngineProcessResult {
16
- exitCode: number | null;
17
- stderr: string;
18
- stdout: string;
19
- }
20
-
21
- /**
22
- * engineを起動して出力を返す関数 テストでは差し替える
23
- */
24
- export type EngineRunner = (
25
- command: string[],
26
- options: { cwd: string },
27
- ) => Promise<EngineProcessResult>;
28
-
29
- /**
30
- * engineごとの実行file名
31
- */
32
- export const ENGINE_BINS: Record<EngineName, string> = {
33
- biome: "biome",
34
- "code-style-check": "code-style-check",
35
- "comment-check": "comment-check",
36
- "document-style-check": "document-style-check",
37
- knip: "knip",
38
- "tsdoc-check": "tsdoc-check",
39
- typecheck: "tsc",
40
- };
41
-
42
- const WINDOWS_SHIMS = [".exe", ".cmd", ".bat", ""];
43
- const POSIX_SHIMS = [""];
44
-
45
- /**
46
- * コマンドラインから渡された起動条件の上書き
47
- */
48
- export interface RunOverrides {
49
- ignore: string[];
50
- targets: string[];
51
- }
52
-
53
- /**
54
- * engineのコマンドを組み立てるための実行条件
55
- */
56
- export interface EngineCommandContext {
57
- config: QualityConfig;
58
- /** engine自身の出力へ色を付けるか */
59
- color: boolean;
60
- /** 対応するengineへだけ足す上書き */
61
- overrides: RunOverrides;
62
- /** comment-checkのbaseline差分を無効化するために渡す未作成のpath */
63
- rawBaseline: string;
64
- }
65
-
66
- /**
67
- * engineが受け取らない起動条件と、その設定の持ち主
68
- */
69
- export const ENGINE_LIMITS: Record<
70
- EngineName,
71
- Partial<Record<"ignore" | "targets", string>>
72
- > = {
73
- biome: { ignore: "biome.json holds its settings" },
74
- typecheck: {
75
- ignore: "tsconfig.json holds its settings",
76
- targets: "tsconfig.json and projects hold its settings",
77
- },
78
- knip: {
79
- ignore: "knip.ts holds its settings",
80
- targets: "knip analyzes the whole project",
81
- },
82
- "code-style-check": {},
83
- "comment-check": {},
84
- "document-style-check": {},
85
- "tsdoc-check": {},
86
- };
87
-
88
- /**
89
- * engineが受け取らないため渡さなかった起動条件を返す
90
- *
91
- * @param name - 受け取れない起動条件を引くengine名
92
- * @param options - engineへ渡そうとした起動条件
93
- * @returns 渡さなかった起動条件とその理由の一覧
94
- */
95
- export function skippedEngineOptions(
96
- name: EngineName,
97
- options: EngineOptions | undefined,
98
- ): string[] {
99
- if (options === undefined) {
100
- return [];
101
- }
102
- const limits = ENGINE_LIMITS[name];
103
- const skipped: string[] = [];
104
- for (const key of ["ignore", "targets"] as const) {
105
- const reason = limits[key];
106
- if (reason !== undefined && options[key] !== undefined) {
107
- skipped.push(`${key} skipped (${reason})`);
108
- }
109
- }
110
- return skipped;
111
- }
112
-
113
- /**
114
- * 検出をJSONで返すengineか
115
- *
116
- * @param name - 判定するengine名
117
- * @returns 検出をJSONで返すengineならtrue
118
- */
119
- export function isFindingEngine(name: EngineName): boolean {
120
- return (
121
- name === "code-style-check" ||
122
- name === "comment-check" ||
123
- name === "document-style-check" ||
124
- name === "tsdoc-check"
125
- );
126
- }
127
-
128
- /**
129
- * node_modules/.binとPATHからengineの実行fileを探す
130
- *
131
- * @param name - 実行fileを探すengine名
132
- * @param cwd - 探索を開始する作業ディレクトリのpath
133
- * @returns 見つけた実行fileのpath PATHにも無ければnull
134
- */
135
- export function resolveExecutable(
136
- name: EngineName,
137
- cwd: string,
138
- ): string | null {
139
- const shims = process.platform === "win32" ? WINDOWS_SHIMS : POSIX_SHIMS;
140
- const bin = ENGINE_BINS[name];
141
- let directory = cwd;
142
- for (;;) {
143
- for (const shim of shims) {
144
- const candidate = join(
145
- directory,
146
- "node_modules",
147
- ".bin",
148
- `${bin}${shim}`,
149
- );
150
- if (existsSync(candidate)) {
151
- return candidate;
152
- }
153
- }
154
- const parent = dirname(directory);
155
- if (parent === directory) {
156
- break;
157
- }
158
- directory = parent;
159
- }
160
- const onPath = Bun.which(bin);
161
- return onPath ?? null;
162
- }
163
-
164
- /**
165
- * engine自身の出力へ色を付けるための引数を返す
166
- */
167
- function colorArguments(name: EngineName, color: boolean): string[] {
168
- if (!color) {
169
- return [];
170
- }
171
- if (name === "biome") {
172
- return ["--colors=force"];
173
- }
174
- if (name === "typecheck") {
175
- return ["--pretty"];
176
- }
177
- return [];
178
- }
179
-
180
- /**
181
- * 型検査のコマンドを組み立てる projectを渡すとそのtsconfigを読む
182
- */
183
- function buildTypecheckCommand(
184
- executable: string,
185
- context: EngineCommandContext,
186
- project: string | undefined,
187
- ): string[] {
188
- const options = engineConfig(context.config, "typecheck") ?? {};
189
- return [
190
- executable,
191
- "--noEmit",
192
- ...(project === undefined ? [] : ["--project", project]),
193
- ...colorArguments("typecheck", context.color),
194
- ...(options.args ?? []),
195
- ];
196
- }
197
-
198
- /**
199
- * 設定fileが持つruleの指定
200
- */
201
- interface RuleSelection {
202
- enable?: boolean;
203
- error?: boolean;
204
- rules?: Record<string, RuleState>;
205
- }
206
-
207
- /**
208
- * engineが公開するrule語彙
209
- */
210
- type RuleVocabulary = (typeof RULE_VOCABULARY)[EngineName];
211
-
212
- /**
213
- * 選んだruleを有効の一覧と違反の一覧へ反映する
214
- *
215
- * @param vocabulary - 反映先engineのrule語彙
216
- * @param selected - 反映先のrule名の集合
217
- * @param rule - 反映するrule名
218
- * @param state - 反映する状態
219
- */
220
- function applyRuleState(
221
- vocabulary: RuleVocabulary,
222
- selected: { enable: Set<string>; error: Set<string> },
223
- rule: string,
224
- state: RuleState,
225
- ): void {
226
- if (state === "off") {
227
- selected.enable.delete(rule);
228
- selected.error.delete(rule);
229
- return;
230
- }
231
- if (state === "on") {
232
- selected.error.delete(rule);
233
- } else {
234
- selected.error.add(rule);
235
- }
236
- if (vocabulary.optIn.includes(rule)) {
237
- selected.enable.add(rule);
238
- }
239
- }
240
-
241
- /**
242
- * ruleの一括指定と個別指定をengineへ渡すruleの一覧へ展開する
243
- *
244
- * @param name - 展開するengine名
245
- * @param options - engineへ渡す起動条件
246
- * @returns --enableへ渡すrule名と--errorへ渡すrule名の組
247
- */
248
- function selectRules(
249
- name: EngineName,
250
- options: EngineOptions | null | undefined,
251
- ): { enable: string[]; error: string[] } {
252
- const selection = (options ?? {}) as RuleSelection;
253
- const vocabulary = RULE_VOCABULARY[name];
254
- const selected = { enable: new Set<string>(), error: new Set<string>() };
255
- if (selection.enable === true) {
256
- for (const rule of vocabulary.optIn) {
257
- selected.enable.add(rule);
258
- }
259
- }
260
- if (selection.error === true) {
261
- for (const rule of vocabulary.all) {
262
- applyRuleState(vocabulary, selected, rule, "error");
263
- }
264
- }
265
- for (const [rule, state] of Object.entries(selection.rules ?? {})) {
266
- applyRuleState(vocabulary, selected, rule, state);
267
- }
268
- return { enable: [...selected.enable], error: [...selected.error] };
269
- }
270
-
271
- /**
272
- * engineへ渡すコマンドを組み立てる
273
- *
274
- * @param name - コマンドを組み立てるengine名
275
- * @param executable - 起動するengineの実行fileのpath
276
- * @param context - 設定と上書きを持つ実行条件
277
- * @returns engineへ渡す引数を並べたコマンド
278
- */
279
- export function buildEngineCommand(
280
- name: EngineName,
281
- executable: string,
282
- context: EngineCommandContext,
283
- ): string[] {
284
- const options: EngineOptions = engineConfig(context.config, name) ?? {};
285
- const limits = ENGINE_LIMITS[name];
286
- const selection = selectRules(name, options);
287
- const enableArguments = selection.enable.flatMap((rule) => [
288
- "--enable",
289
- rule,
290
- ]);
291
- const errorArguments = selection.error.flatMap((rule) => ["--error", rule]);
292
- const extra = options.args ?? [];
293
- const ignores =
294
- limits.ignore === undefined
295
- ? [...(options.ignore ?? []), ...context.overrides.ignore]
296
- : [];
297
- const requested =
298
- context.overrides.targets.length > 0
299
- ? context.overrides.targets
300
- : (options.targets ?? ["."]);
301
- const targets = limits.targets === undefined ? requested : [];
302
- const ignoreArguments = ignores.flatMap((ignore) => ["--ignore", ignore]);
303
- if (name === "biome") {
304
- return [
305
- executable,
306
- "check",
307
- ...colorArguments(name, context.color),
308
- ...targets,
309
- ...extra,
310
- ];
311
- }
312
- if (name === "typecheck") {
313
- return buildTypecheckCommand(executable, context, undefined);
314
- }
315
- if (name === "knip") {
316
- return [executable, ...extra];
317
- }
318
- if (name === "comment-check") {
319
- return [
320
- executable,
321
- "--json",
322
- "--baseline",
323
- context.rawBaseline,
324
- ...enableArguments,
325
- ...targets,
326
- ...ignoreArguments,
327
- ...extra,
328
- ];
329
- }
330
- if (name === "document-style-check") {
331
- return [
332
- executable,
333
- "lint",
334
- "--json",
335
- ...enableArguments,
336
- ...targets,
337
- ...ignoreArguments,
338
- ...extra,
339
- ];
340
- }
341
- if (name === "code-style-check") {
342
- return [executable, "--json", ...targets, ...ignoreArguments, ...extra];
343
- }
344
- return [
345
- executable,
346
- "--json",
347
- ...enableArguments,
348
- ...errorArguments,
349
- ...targets,
350
- ...ignoreArguments,
351
- ...extra,
352
- ];
353
- }
354
-
355
- /**
356
- * engineの起動コマンドを順番に返す 型検査だけはprojectsごとに起動する
357
- *
358
- * @param name - コマンドを組み立てるengine名
359
- * @param executable - 起動するengineの実行fileのpath
360
- * @param context - 設定と上書きを持つ実行条件
361
- * @returns 起動する順に並べたコマンドの配列
362
- */
363
- export function buildEngineCommands(
364
- name: EngineName,
365
- executable: string,
366
- context: EngineCommandContext,
367
- ): string[][] {
368
- const projects =
369
- name === "typecheck"
370
- ? (engineConfig(context.config, "typecheck")?.projects ?? [])
371
- : [];
372
- if (projects.length === 0) {
373
- return [buildEngineCommand(name, executable, context)];
374
- }
375
- return projects.map((project) =>
376
- buildTypecheckCommand(executable, context, project),
377
- );
378
- }
379
-
380
- /**
381
- * Bun.spawnでengineを起動する既定のrunner
382
- *
383
- * @param command - 実行fileと引数を並べたコマンド
384
- * @param options - engineを起動する作業ディレクトリを持つ条件
385
- * @returns 終了codeと標準出力と標準エラーを持つ結果
386
- */
387
- export async function runEngineProcess(
388
- command: string[],
389
- options: { cwd: string },
390
- ): Promise<EngineProcessResult> {
391
- const child = Bun.spawn({
392
- cmd: command,
393
- cwd: options.cwd,
394
- stderr: "pipe",
395
- stdout: "pipe",
396
- });
397
- const [stdout, stderr, exitCode] = await Promise.all([
398
- new Response(child.stdout).text(),
399
- new Response(child.stderr).text(),
400
- child.exited,
401
- ]);
402
- return { exitCode, stderr, stdout };
403
- }