@yuu1111/quality-check 0.5.1 → 0.7.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/findings.ts DELETED
@@ -1,106 +0,0 @@
1
- import type { EngineName } from "./config";
2
-
3
- /**
4
- * 検出の重大度
5
- */
6
- export type FindingSeverity = "error" | "warning";
7
-
8
- /**
9
- * engine間の差を吸収した検出1件
10
- */
11
- export interface NormalizedFinding {
12
- column: number;
13
- engine: EngineName;
14
- file: string;
15
- line: number;
16
- rule: string;
17
- severity: FindingSeverity;
18
- text: string;
19
- }
20
-
21
- /**
22
- * engineが--jsonで返した検出の分類
23
- */
24
- export interface ParsedFindings {
25
- errors: NormalizedFinding[];
26
- warnings: NormalizedFinding[];
27
- }
28
-
29
- type JsonObject = Record<string, unknown>;
30
-
31
- function isJsonObject(value: unknown): value is JsonObject {
32
- return typeof value === "object" && value !== null && !Array.isArray(value);
33
- }
34
-
35
- function readFinding(
36
- engine: EngineName,
37
- value: unknown,
38
- severity: FindingSeverity,
39
- textField: "message" | "text",
40
- ): NormalizedFinding {
41
- if (!isJsonObject(value)) {
42
- throw new Error(`${engine} printed an unexpected finding`);
43
- }
44
- const { column, file, line, rule } = value;
45
- const text = value[textField];
46
- if (
47
- typeof rule !== "string" ||
48
- typeof file !== "string" ||
49
- typeof line !== "number" ||
50
- typeof column !== "number" ||
51
- typeof text !== "string"
52
- ) {
53
- throw new Error(`${engine} printed an unexpected finding`);
54
- }
55
- return { column, engine, file, line, rule, severity, text };
56
- }
57
-
58
- function readEntries(
59
- engine: EngineName,
60
- value: unknown,
61
- field: string,
62
- ): unknown[] {
63
- if (!Array.isArray(value)) {
64
- throw new Error(`${engine} printed JSON without a ${field} array`);
65
- }
66
- return value;
67
- }
68
-
69
- function parseJson(engine: EngineName, stdout: string): JsonObject {
70
- let value: unknown;
71
- try {
72
- value = JSON.parse(stdout);
73
- } catch {
74
- throw new Error(`${engine} did not print JSON`);
75
- }
76
- if (!isJsonObject(value)) {
77
- throw new Error(`${engine} printed an unexpected JSON value`);
78
- }
79
- return value;
80
- }
81
-
82
- /**
83
- * engineの--json出力を検出へ変換する 解析できない出力は例外にする
84
- */
85
- export function parseFindings(
86
- engine: EngineName,
87
- stdout: string,
88
- ): ParsedFindings {
89
- const value = parseJson(engine, stdout);
90
- if (engine === "comment-check") {
91
- return {
92
- errors: readEntries(engine, value.added, "added").map((entry) =>
93
- readFinding(engine, entry, "error", "text"),
94
- ),
95
- warnings: [],
96
- };
97
- }
98
- return {
99
- errors: readEntries(engine, value.errors, "errors").map((entry) =>
100
- readFinding(engine, entry, "error", "message"),
101
- ),
102
- warnings: readEntries(engine, value.warnings, "warnings").map((entry) =>
103
- readFinding(engine, entry, "warning", "message"),
104
- ),
105
- };
106
- }
package/src/report.ts DELETED
@@ -1,105 +0,0 @@
1
- import { type Painter, plainPainter } from "./color";
2
- import { isFindingEngine } from "./engines";
3
- import type { NormalizedFinding } from "./findings";
4
- import type { EngineResult } from "./run";
5
-
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}`;
12
- }
13
-
14
- function describeCounts(result: EngineResult): string {
15
- if (!isFindingEngine(result.name)) {
16
- return `exit ${result.exitCode}`;
17
- }
18
- return `${result.reported.length} new, ${result.resolved} resolved, ${result.warnings.length} warnings`;
19
- }
20
-
21
- function describeStatus(result: EngineResult, paint: Painter): string {
22
- if (result.status === "error") {
23
- return paint("error", "error");
24
- }
25
- const tone = result.status === "passed" ? "pass" : "error";
26
- return `${paint(result.status, tone)} (${describeCounts(result)})`;
27
- }
28
-
29
- /**
30
- * engine1つ分の出力sectionを組み立てる
31
- */
32
- export function formatEngineSection(
33
- result: EngineResult,
34
- paint: Painter = plainPainter,
35
- ): string {
36
- const lines = [paint(`== ${result.name} ==`, "header")];
37
- for (const skipped of result.skipped) {
38
- lines.push(paint(`${result.name}: ${skipped}`, "muted"));
39
- }
40
- if (result.output !== "" && !isFindingEngine(result.name)) {
41
- lines.push(result.output);
42
- }
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"));
48
- }
49
- if (result.message !== undefined) {
50
- lines.push(paint(result.message, "error"));
51
- }
52
- lines.push(`${result.name}: ${describeStatus(result, paint)}`);
53
- return lines.join("\n");
54
- }
55
-
56
- /**
57
- * 失敗したengineを列挙した集約summaryを組み立てる
58
- */
59
- export function formatSummary(
60
- results: EngineResult[],
61
- paint: Painter = plainPainter,
62
- ): string {
63
- const failed = results
64
- .filter((result) => result.status !== "passed")
65
- .map((result) => result.name);
66
- const passed = results
67
- .filter((result) => result.status === "passed")
68
- .map((result) => result.name);
69
- if (failed.length === 0) {
70
- return paint(`quality-check: ${results.length} engines passed`, "pass");
71
- }
72
- const lines = [
73
- paint(
74
- `quality-check: ${failed.length} of ${results.length} engines failed`,
75
- "error",
76
- ),
77
- ` ${paint("failed:", "error")} ${failed.join(", ")}`,
78
- ];
79
- if (passed.length > 0) {
80
- lines.push(` ${paint("passed:", "pass")} ${passed.join(", ")}`);
81
- }
82
- return lines.join("\n");
83
- }
84
-
85
- /**
86
- * engineの結果をJSONへ変換する
87
- */
88
- export function toJsonReport(results: EngineResult[]): unknown {
89
- return {
90
- engines: results.map((result) => ({
91
- detected: result.detected.length,
92
- exitCode: result.exitCode,
93
- message: result.message ?? null,
94
- name: result.name,
95
- reported: result.reported,
96
- resolved: result.resolved,
97
- skipped: result.skipped,
98
- status: result.status,
99
- warnings: result.warnings,
100
- })),
101
- failed: results
102
- .filter((result) => result.status !== "passed")
103
- .map((result) => result.name),
104
- };
105
- }
package/src/run.ts DELETED
@@ -1,203 +0,0 @@
1
- import { type BaselineFile, compareWithBaseline } from "./baseline";
2
- import { type EngineName, enabledEngines, type QualityConfig } from "./config";
3
- import {
4
- buildEngineCommand,
5
- ENGINE_BINS,
6
- type EngineCommandContext,
7
- type EngineProcessResult,
8
- type EngineRunner,
9
- isFindingEngine,
10
- type RunOverrides,
11
- resolveExecutable,
12
- runEngineProcess,
13
- skippedEngineOptions,
14
- } from "./engines";
15
- import { type NormalizedFinding, parseFindings } from "./findings";
16
-
17
- /**
18
- * engine1つ分の実行状態
19
- */
20
- export type EngineStatus = "error" | "failed" | "passed";
21
-
22
- /**
23
- * engine1つ分の実行結果
24
- */
25
- export interface EngineResult {
26
- /** baseline適用前の阻害する検出 */
27
- detected: NormalizedFinding[];
28
- exitCode: number | null;
29
- message?: string;
30
- name: EngineName;
31
- output: string;
32
- /** baseline適用後に残った阻害する検出 */
33
- reported: NormalizedFinding[];
34
- resolved: number;
35
- /** engineが受け取らなかった起動条件 */
36
- skipped: string[];
37
- status: EngineStatus;
38
- warnings: NormalizedFinding[];
39
- }
40
-
41
- /**
42
- * engineをまとめて起動するための実行条件
43
- */
44
- export interface RunOptions {
45
- /** 適用するbaseline nullなら差分判定を行わない */
46
- baseline: BaselineFile | null;
47
- /** engine自身の出力へ色を付けるか */
48
- color: boolean;
49
- config: QualityConfig;
50
- cwd: string;
51
- /** コマンドラインから渡された起動条件の上書き */
52
- overrides: RunOverrides;
53
- /** comment-checkのbaseline差分を無効化する未作成のpath */
54
- rawBaseline: string;
55
- /** engineの実行fileを解決する関数 テストでは差し替える */
56
- resolve?: (name: EngineName, cwd: string) => string | null;
57
- runner?: EngineRunner;
58
- }
59
-
60
- function baseResult(
61
- name: EngineName,
62
- exitCode: number | null,
63
- output: string,
64
- ): Omit<EngineResult, "status"> {
65
- return {
66
- detected: [],
67
- exitCode,
68
- name,
69
- output,
70
- reported: [],
71
- resolved: 0,
72
- skipped: [],
73
- warnings: [],
74
- };
75
- }
76
-
77
- function joinOutput(result: EngineProcessResult): string {
78
- return [result.stdout, result.stderr]
79
- .map((part) => part.trimEnd())
80
- .filter((part) => part !== "")
81
- .join("\n");
82
- }
83
-
84
- function describeError(error: unknown): string {
85
- return error instanceof Error ? error.message : String(error);
86
- }
87
-
88
- async function runFindingEngine(
89
- name: EngineName,
90
- options: RunOptions,
91
- executable: string,
92
- context: EngineCommandContext,
93
- runner: EngineRunner,
94
- ): Promise<EngineResult> {
95
- const result = await runner(buildEngineCommand(name, executable, context), {
96
- cwd: options.cwd,
97
- });
98
- const output = joinOutput(result);
99
- if (result.exitCode === 2) {
100
- return {
101
- ...baseResult(name, 2, output),
102
- message: `${name} could not finish`,
103
- status: "error",
104
- };
105
- }
106
- let parsed: ReturnType<typeof parseFindings>;
107
- try {
108
- parsed = parseFindings(name, result.stdout);
109
- } catch (error) {
110
- return {
111
- ...baseResult(name, result.exitCode, output),
112
- message: describeError(error),
113
- status: "error",
114
- };
115
- }
116
- const comparison =
117
- options.baseline === null
118
- ? { added: parsed.errors, resolved: [] }
119
- : compareWithBaseline(parsed.errors, options.baseline);
120
- return {
121
- ...baseResult(name, result.exitCode, output),
122
- detected: parsed.errors,
123
- reported: comparison.added,
124
- resolved: comparison.resolved.length,
125
- status: comparison.added.length > 0 ? "failed" : "passed",
126
- warnings: parsed.warnings,
127
- };
128
- }
129
-
130
- async function runProcessEngine(
131
- name: EngineName,
132
- options: RunOptions,
133
- executable: string,
134
- context: EngineCommandContext,
135
- runner: EngineRunner,
136
- ): Promise<EngineResult> {
137
- const result = await runner(buildEngineCommand(name, executable, context), {
138
- cwd: options.cwd,
139
- });
140
- const output = joinOutput(result);
141
- if (result.exitCode === 2) {
142
- return {
143
- ...baseResult(name, 2, output),
144
- message: `${name} could not finish`,
145
- status: "error",
146
- };
147
- }
148
- return {
149
- ...baseResult(name, result.exitCode, output),
150
- status: result.exitCode === 0 ? "passed" : "failed",
151
- };
152
- }
153
-
154
- async function executeEngine(
155
- name: EngineName,
156
- options: RunOptions,
157
- context: EngineCommandContext,
158
- runner: EngineRunner,
159
- ): Promise<EngineResult> {
160
- const executable = (options.resolve ?? resolveExecutable)(name, options.cwd);
161
- if (executable === null) {
162
- return {
163
- ...baseResult(name, null, ""),
164
- message: `${ENGINE_BINS[name]} is not installed`,
165
- status: "error",
166
- };
167
- }
168
- if (isFindingEngine(name)) {
169
- return await runFindingEngine(name, options, executable, context, runner);
170
- }
171
- return await runProcessEngine(name, options, executable, context, runner);
172
- }
173
-
174
- async function runEngine(
175
- name: EngineName,
176
- options: RunOptions,
177
- context: EngineCommandContext,
178
- runner: EngineRunner,
179
- ): Promise<EngineResult> {
180
- const result = await executeEngine(name, options, context, runner);
181
- return {
182
- ...result,
183
- skipped: skippedEngineOptions(name, options.config.config?.[name]),
184
- };
185
- }
186
-
187
- /**
188
- * 設定で有効なengineを順に起動する
189
- */
190
- export async function runEngines(options: RunOptions): Promise<EngineResult[]> {
191
- const runner = options.runner ?? runEngineProcess;
192
- const context: EngineCommandContext = {
193
- color: options.color,
194
- config: options.config,
195
- overrides: options.overrides,
196
- rawBaseline: options.rawBaseline,
197
- };
198
- const results: EngineResult[] = [];
199
- for (const name of enabledEngines(options.config)) {
200
- results.push(await runEngine(name, options, context, runner));
201
- }
202
- return results;
203
- }