@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/README.ja.md +7 -7
- package/README.md +13 -9
- package/dist/cli.js +861 -0
- package/package.json +12 -5
- package/src/config.ts +81 -18
- package/src/engines.ts +51 -7
- package/src/baseline.ts +0 -147
- package/src/cli.ts +0 -183
- package/src/color.ts +0 -47
- package/src/findings.ts +0 -106
- package/src/report.ts +0 -105
- package/src/run.ts +0 -203
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
|
-
}
|