@yuu1111/quality-check 0.5.1 → 0.6.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 +2 -5
- package/README.md +4 -7
- package/dist/cli.js +846 -0
- package/package.json +12 -5
- package/src/config.ts +29 -4
- package/src/engines.ts +40 -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/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuu1111/quality-check",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Integrated quality check runner",
|
|
5
|
-
"license": "MIT",
|
|
6
5
|
"repository": {
|
|
7
6
|
"type": "git",
|
|
8
7
|
"url": "git+https://github.com/yuu1111/configs.git",
|
|
@@ -10,7 +9,7 @@
|
|
|
10
9
|
},
|
|
11
10
|
"type": "module",
|
|
12
11
|
"bin": {
|
|
13
|
-
"quality-check": "
|
|
12
|
+
"quality-check": "dist/cli.js"
|
|
14
13
|
},
|
|
15
14
|
"exports": {
|
|
16
15
|
".": "./src/config.ts",
|
|
@@ -18,10 +17,18 @@
|
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
20
19
|
"README.ja.md",
|
|
21
|
-
"src"
|
|
20
|
+
"src/config.ts",
|
|
21
|
+
"src/engines.ts",
|
|
22
|
+
"dist"
|
|
22
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "bun build src/cli.ts --target=bun --outdir=dist"
|
|
26
|
+
},
|
|
23
27
|
"keywords": [
|
|
24
28
|
"quality",
|
|
25
29
|
"lint"
|
|
26
|
-
]
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@yuu1111/shared": "workspace:*"
|
|
33
|
+
}
|
|
27
34
|
}
|
package/src/config.ts
CHANGED
|
@@ -39,12 +39,20 @@ export interface TsdocCheckOptions extends EngineOptions {
|
|
|
39
39
|
error?: string[];
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* 型検査へ渡す起動条件
|
|
44
|
+
*/
|
|
45
|
+
export interface TypecheckOptions extends EngineOptions {
|
|
46
|
+
/** 型検査するtsconfigのpath 省略時はカレントのtsconfig.jsonを1回だけ読む */
|
|
47
|
+
projects?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
/**
|
|
43
51
|
* engine名ごとの起動条件
|
|
44
52
|
*/
|
|
45
53
|
export interface EngineConfigMap {
|
|
46
54
|
biome: EngineOptions;
|
|
47
|
-
typecheck:
|
|
55
|
+
typecheck: TypecheckOptions;
|
|
48
56
|
knip: EngineOptions;
|
|
49
57
|
"comment-check": EngineOptions;
|
|
50
58
|
"document-style-check": EngineOptions;
|
|
@@ -97,7 +105,7 @@ function isEngineName(value: string): value is EngineName {
|
|
|
97
105
|
|
|
98
106
|
const ENGINE_OPTION_KEYS: Record<EngineName, readonly string[]> = {
|
|
99
107
|
biome: ["args", "ignore", "targets"],
|
|
100
|
-
typecheck: ["args", "ignore", "targets"],
|
|
108
|
+
typecheck: ["args", "ignore", "projects", "targets"],
|
|
101
109
|
knip: ["args", "ignore", "targets"],
|
|
102
110
|
"comment-check": ["args", "ignore", "targets"],
|
|
103
111
|
"document-style-check": ["args", "ignore", "targets"],
|
|
@@ -155,11 +163,16 @@ function parseEngines(
|
|
|
155
163
|
return engines;
|
|
156
164
|
}
|
|
157
165
|
|
|
166
|
+
type ParsedEngineOptions = EngineOptions & {
|
|
167
|
+
error?: string[];
|
|
168
|
+
projects?: string[];
|
|
169
|
+
};
|
|
170
|
+
|
|
158
171
|
function parseEngineOptions(
|
|
159
172
|
value: JsonObject,
|
|
160
173
|
source: string,
|
|
161
174
|
name: EngineName,
|
|
162
|
-
):
|
|
175
|
+
): ParsedEngineOptions {
|
|
163
176
|
const unknown = Object.keys(value).filter(
|
|
164
177
|
(key) => !ENGINE_OPTION_KEYS[name].includes(key),
|
|
165
178
|
);
|
|
@@ -168,7 +181,7 @@ function parseEngineOptions(
|
|
|
168
181
|
`${source}: config.${name} has an unknown option: ${unknown[0]}`,
|
|
169
182
|
);
|
|
170
183
|
}
|
|
171
|
-
const options:
|
|
184
|
+
const options: ParsedEngineOptions = {};
|
|
172
185
|
const ignore = readStringArray(
|
|
173
186
|
value.ignore,
|
|
174
187
|
`${source}: config.${name}.ignore`,
|
|
@@ -196,6 +209,18 @@ function parseEngineOptions(
|
|
|
196
209
|
options.error = error;
|
|
197
210
|
}
|
|
198
211
|
}
|
|
212
|
+
if (name === "typecheck") {
|
|
213
|
+
const projects = readStringArray(
|
|
214
|
+
value.projects,
|
|
215
|
+
`${source}: config.${name}.projects`,
|
|
216
|
+
);
|
|
217
|
+
if (projects !== undefined) {
|
|
218
|
+
if (projects.length === 0) {
|
|
219
|
+
throw new Error(`${source}: config.${name}.projects must not be empty`);
|
|
220
|
+
}
|
|
221
|
+
options.projects = projects;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
199
224
|
return options;
|
|
200
225
|
}
|
|
201
226
|
|
package/src/engines.ts
CHANGED
|
@@ -70,7 +70,7 @@ export const ENGINE_LIMITS: Record<
|
|
|
70
70
|
biome: { ignore: "biome.json holds its settings" },
|
|
71
71
|
typecheck: {
|
|
72
72
|
ignore: "tsconfig.json holds its settings",
|
|
73
|
-
targets: "
|
|
73
|
+
targets: "tsconfig.json and projects hold its settings",
|
|
74
74
|
},
|
|
75
75
|
knip: {
|
|
76
76
|
ignore: "knip.ts holds its settings",
|
|
@@ -161,6 +161,24 @@ function colorArguments(name: EngineName, color: boolean): string[] {
|
|
|
161
161
|
return [];
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* 型検査のコマンドを組み立てる projectを渡すとそのtsconfigを読む
|
|
166
|
+
*/
|
|
167
|
+
function buildTypecheckCommand(
|
|
168
|
+
executable: string,
|
|
169
|
+
context: EngineCommandContext,
|
|
170
|
+
project: string | undefined,
|
|
171
|
+
): string[] {
|
|
172
|
+
const options = engineConfig(context.config, "typecheck") ?? {};
|
|
173
|
+
return [
|
|
174
|
+
executable,
|
|
175
|
+
"--noEmit",
|
|
176
|
+
...(project === undefined ? [] : ["--project", project]),
|
|
177
|
+
...colorArguments("typecheck", context.color),
|
|
178
|
+
...(options.args ?? []),
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
/**
|
|
165
183
|
* engineへ渡すコマンドを組み立てる
|
|
166
184
|
*/
|
|
@@ -194,12 +212,7 @@ export function buildEngineCommand(
|
|
|
194
212
|
];
|
|
195
213
|
}
|
|
196
214
|
if (name === "typecheck") {
|
|
197
|
-
return
|
|
198
|
-
executable,
|
|
199
|
-
"--noEmit",
|
|
200
|
-
...colorArguments(name, context.color),
|
|
201
|
-
...extra,
|
|
202
|
-
];
|
|
215
|
+
return buildTypecheckCommand(executable, context, undefined);
|
|
203
216
|
}
|
|
204
217
|
if (name === "knip") {
|
|
205
218
|
return [executable, ...extra];
|
|
@@ -235,6 +248,26 @@ export function buildEngineCommand(
|
|
|
235
248
|
];
|
|
236
249
|
}
|
|
237
250
|
|
|
251
|
+
/**
|
|
252
|
+
* engineの起動コマンドを順番に返す 型検査だけはprojectsごとに起動する
|
|
253
|
+
*/
|
|
254
|
+
export function buildEngineCommands(
|
|
255
|
+
name: EngineName,
|
|
256
|
+
executable: string,
|
|
257
|
+
context: EngineCommandContext,
|
|
258
|
+
): string[][] {
|
|
259
|
+
const projects =
|
|
260
|
+
name === "typecheck"
|
|
261
|
+
? (engineConfig(context.config, "typecheck")?.projects ?? [])
|
|
262
|
+
: [];
|
|
263
|
+
if (projects.length === 0) {
|
|
264
|
+
return [buildEngineCommand(name, executable, context)];
|
|
265
|
+
}
|
|
266
|
+
return projects.map((project) =>
|
|
267
|
+
buildTypecheckCommand(executable, context, project),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
238
271
|
/**
|
|
239
272
|
* Bun.spawnでengineを起動する既定のrunner
|
|
240
273
|
*/
|
package/src/baseline.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import type { NormalizedFinding } from "./findings";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* baselineへ記録する検出1件の識別情報と件数
|
|
6
|
-
*/
|
|
7
|
-
export interface BaselineEntry {
|
|
8
|
-
count: number;
|
|
9
|
-
engine: string;
|
|
10
|
-
file: string;
|
|
11
|
-
rule: string;
|
|
12
|
-
text: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* baseline fileの形式
|
|
17
|
-
*/
|
|
18
|
-
export interface BaselineFile {
|
|
19
|
-
entries: BaselineEntry[];
|
|
20
|
-
version: 1;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* baselineと現在の検出を比較した結果
|
|
25
|
-
*/
|
|
26
|
-
export interface BaselineComparison {
|
|
27
|
-
added: NormalizedFinding[];
|
|
28
|
-
resolved: BaselineEntry[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type JsonObject = Record<string, unknown>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 検出をbaseline上で一意に識別するkeyに使う項目
|
|
35
|
-
*/
|
|
36
|
-
export interface BaselineKey {
|
|
37
|
-
engine: string;
|
|
38
|
-
file: string;
|
|
39
|
-
rule: string;
|
|
40
|
-
text: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isJsonObject(value: unknown): value is JsonObject {
|
|
44
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isBaselineEntry(value: unknown): value is BaselineEntry {
|
|
48
|
-
if (!isJsonObject(value)) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return (
|
|
52
|
-
typeof value.engine === "string" &&
|
|
53
|
-
typeof value.rule === "string" &&
|
|
54
|
-
typeof value.file === "string" &&
|
|
55
|
-
typeof value.text === "string" &&
|
|
56
|
-
typeof value.count === "number"
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function entryKey(entry: BaselineKey): string {
|
|
61
|
-
return [entry.engine, entry.rule, entry.file, entry.text].join("\u0000");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function compareEntries(left: BaselineEntry, right: BaselineEntry): number {
|
|
65
|
-
const leftKey = entryKey(left);
|
|
66
|
-
const rightKey = entryKey(right);
|
|
67
|
-
if (leftKey === rightKey) {
|
|
68
|
-
return 0;
|
|
69
|
-
}
|
|
70
|
-
return leftKey < rightKey ? -1 : 1;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 検出をengineとruleとfileと本文で集計してbaselineを作る
|
|
75
|
-
*/
|
|
76
|
-
export function createBaseline(findings: NormalizedFinding[]): BaselineFile {
|
|
77
|
-
const entries = new Map<string, BaselineEntry>();
|
|
78
|
-
for (const finding of findings) {
|
|
79
|
-
const key = entryKey(finding);
|
|
80
|
-
const existing = entries.get(key);
|
|
81
|
-
if (existing) {
|
|
82
|
-
existing.count += 1;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
entries.set(key, {
|
|
86
|
-
count: 1,
|
|
87
|
-
engine: finding.engine,
|
|
88
|
-
file: finding.file,
|
|
89
|
-
rule: finding.rule,
|
|
90
|
-
text: finding.text,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return { entries: [...entries.values()].sort(compareEntries), version: 1 };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 現在の検出からbaseline済みを除き、解消済みentryを求める
|
|
98
|
-
*/
|
|
99
|
-
export function compareWithBaseline(
|
|
100
|
-
findings: NormalizedFinding[],
|
|
101
|
-
baseline: BaselineFile,
|
|
102
|
-
): BaselineComparison {
|
|
103
|
-
const remaining = new Map<string, number>();
|
|
104
|
-
for (const entry of baseline.entries) {
|
|
105
|
-
const key = entryKey(entry);
|
|
106
|
-
remaining.set(key, (remaining.get(key) ?? 0) + entry.count);
|
|
107
|
-
}
|
|
108
|
-
const added: NormalizedFinding[] = [];
|
|
109
|
-
for (const finding of findings) {
|
|
110
|
-
const key = entryKey(finding);
|
|
111
|
-
const count = remaining.get(key) ?? 0;
|
|
112
|
-
if (count > 0) {
|
|
113
|
-
remaining.set(key, count - 1);
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
added.push(finding);
|
|
117
|
-
}
|
|
118
|
-
const resolved = baseline.entries.filter(
|
|
119
|
-
(entry) => (remaining.get(entryKey(entry)) ?? 0) > 0,
|
|
120
|
-
);
|
|
121
|
-
return { added, resolved };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* baseline fileを読み込む 存在しない場合は空のbaselineを返す
|
|
126
|
-
*/
|
|
127
|
-
export function readBaseline(path: string): BaselineFile {
|
|
128
|
-
if (!existsSync(path)) {
|
|
129
|
-
return { entries: [], version: 1 };
|
|
130
|
-
}
|
|
131
|
-
const value: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
132
|
-
if (
|
|
133
|
-
!isJsonObject(value) ||
|
|
134
|
-
!Array.isArray(value.entries) ||
|
|
135
|
-
!value.entries.every(isBaselineEntry)
|
|
136
|
-
) {
|
|
137
|
-
throw new Error(`${path} is not a quality baseline`);
|
|
138
|
-
}
|
|
139
|
-
return { entries: value.entries, version: 1 };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* baseline fileをタブ区切りのJSONで書き込む
|
|
144
|
-
*/
|
|
145
|
-
export function writeBaseline(path: string, baseline: BaselineFile): void {
|
|
146
|
-
writeFileSync(path, `${JSON.stringify(baseline, null, "\t")}\n`);
|
|
147
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
import { createBaseline, readBaseline, writeBaseline } from "./baseline";
|
|
5
|
-
import { ansiPainter, colorEnabled, plainPainter } from "./color";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_BASELINE_FILE,
|
|
8
|
-
findConfigFile,
|
|
9
|
-
loadConfig,
|
|
10
|
-
type QualityConfig,
|
|
11
|
-
} from "./config";
|
|
12
|
-
import { formatEngineSection, formatSummary, toJsonReport } from "./report";
|
|
13
|
-
import { runEngines } from "./run";
|
|
14
|
-
|
|
15
|
-
const USAGE =
|
|
16
|
-
"Usage: quality-check [--config <path>] [--baseline <path>] [--ignore <path>] [--update-baseline] [--json] [path...]";
|
|
17
|
-
|
|
18
|
-
interface Options {
|
|
19
|
-
baselinePath: string | undefined;
|
|
20
|
-
configPath: string | undefined;
|
|
21
|
-
ignores: string[];
|
|
22
|
-
json: boolean;
|
|
23
|
-
targets: string[];
|
|
24
|
-
update: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function applyFlagOption(options: Options, argument: string): boolean {
|
|
28
|
-
if (argument === "--json") {
|
|
29
|
-
options.json = true;
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
if (argument === "--update-baseline") {
|
|
33
|
-
options.update = true;
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function applyValueOption(
|
|
40
|
-
options: Options,
|
|
41
|
-
argument: string,
|
|
42
|
-
argv: string[],
|
|
43
|
-
index: number,
|
|
44
|
-
): number | null {
|
|
45
|
-
if (argument === "--config") {
|
|
46
|
-
options.configPath = argv[index + 1];
|
|
47
|
-
return 1;
|
|
48
|
-
}
|
|
49
|
-
if (argument.startsWith("--config=")) {
|
|
50
|
-
options.configPath = argument.slice("--config=".length);
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
if (argument === "--baseline") {
|
|
54
|
-
options.baselinePath = argv[index + 1];
|
|
55
|
-
return 1;
|
|
56
|
-
}
|
|
57
|
-
if (argument.startsWith("--baseline=")) {
|
|
58
|
-
options.baselinePath = argument.slice("--baseline=".length);
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
if (argument === "--ignore") {
|
|
62
|
-
const value = argv[index + 1];
|
|
63
|
-
if (value !== undefined) {
|
|
64
|
-
options.ignores.push(value);
|
|
65
|
-
}
|
|
66
|
-
return 1;
|
|
67
|
-
}
|
|
68
|
-
if (argument.startsWith("--ignore=")) {
|
|
69
|
-
options.ignores.push(argument.slice("--ignore=".length));
|
|
70
|
-
return 0;
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function parseArguments(argv: string[]): Options {
|
|
76
|
-
const options: Options = {
|
|
77
|
-
baselinePath: undefined,
|
|
78
|
-
configPath: undefined,
|
|
79
|
-
ignores: [],
|
|
80
|
-
json: false,
|
|
81
|
-
targets: [],
|
|
82
|
-
update: false,
|
|
83
|
-
};
|
|
84
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
85
|
-
const argument = argv[index] ?? "";
|
|
86
|
-
const consumed = applyValueOption(options, argument, argv, index);
|
|
87
|
-
if (consumed !== null) {
|
|
88
|
-
index += consumed;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (applyFlagOption(options, argument)) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (argument.startsWith("-")) {
|
|
95
|
-
throw new Error(`unknown option: ${argument}`);
|
|
96
|
-
}
|
|
97
|
-
options.targets.push(argument);
|
|
98
|
-
}
|
|
99
|
-
return options;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function resolveBaselinePath(
|
|
103
|
-
options: Options,
|
|
104
|
-
config: QualityConfig,
|
|
105
|
-
cwd: string,
|
|
106
|
-
): string | null {
|
|
107
|
-
if (options.baselinePath !== undefined) {
|
|
108
|
-
return resolve(cwd, options.baselinePath);
|
|
109
|
-
}
|
|
110
|
-
if (config.baseline === false) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
return resolve(cwd, config.baseline ?? DEFAULT_BASELINE_FILE);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function resolveConfigPath(options: Options, cwd: string): string | null {
|
|
117
|
-
if (options.configPath !== undefined) {
|
|
118
|
-
return resolve(cwd, options.configPath);
|
|
119
|
-
}
|
|
120
|
-
return findConfigFile(cwd);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function main(argv: string[]): Promise<number> {
|
|
124
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
125
|
-
console.log(USAGE);
|
|
126
|
-
return 0;
|
|
127
|
-
}
|
|
128
|
-
const options = parseArguments(argv);
|
|
129
|
-
const color = colorEnabled(process.stdout, process.env);
|
|
130
|
-
const paint = color ? ansiPainter() : plainPainter;
|
|
131
|
-
const cwd = process.cwd();
|
|
132
|
-
const configPath = resolveConfigPath(options, cwd);
|
|
133
|
-
if (configPath === null) {
|
|
134
|
-
console.error(`quality.config.ts not found in ${cwd}`);
|
|
135
|
-
return 2;
|
|
136
|
-
}
|
|
137
|
-
const config = await loadConfig(configPath);
|
|
138
|
-
const baselinePath = resolveBaselinePath(options, config, cwd);
|
|
139
|
-
const results = await runEngines({
|
|
140
|
-
baseline:
|
|
141
|
-
options.update || baselinePath === null
|
|
142
|
-
? null
|
|
143
|
-
: readBaseline(baselinePath),
|
|
144
|
-
color,
|
|
145
|
-
config,
|
|
146
|
-
cwd,
|
|
147
|
-
overrides: { ignore: options.ignores, targets: options.targets },
|
|
148
|
-
rawBaseline: join(tmpdir(), `quality-check-raw-${process.pid}.json`),
|
|
149
|
-
});
|
|
150
|
-
if (options.update) {
|
|
151
|
-
if (baselinePath === null) {
|
|
152
|
-
console.error("baseline is disabled by the configuration");
|
|
153
|
-
return 2;
|
|
154
|
-
}
|
|
155
|
-
const baseline = createBaseline(
|
|
156
|
-
results.flatMap((result) => result.detected),
|
|
157
|
-
);
|
|
158
|
-
writeBaseline(baselinePath, baseline);
|
|
159
|
-
console.log(
|
|
160
|
-
paint(
|
|
161
|
-
`Recorded ${baseline.entries.length} entries in ${baselinePath}`,
|
|
162
|
-
"pass",
|
|
163
|
-
),
|
|
164
|
-
);
|
|
165
|
-
return 0;
|
|
166
|
-
}
|
|
167
|
-
if (options.json) {
|
|
168
|
-
console.log(JSON.stringify(toJsonReport(results), null, "\t"));
|
|
169
|
-
} else {
|
|
170
|
-
for (const result of results) {
|
|
171
|
-
console.log(formatEngineSection(result, paint));
|
|
172
|
-
}
|
|
173
|
-
console.log(formatSummary(results, paint));
|
|
174
|
-
}
|
|
175
|
-
return results.some((result) => result.status !== "passed") ? 1 : 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
process.exit(await main(process.argv.slice(2)));
|
|
180
|
-
} catch (error) {
|
|
181
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
182
|
-
process.exit(2);
|
|
183
|
-
}
|
package/src/color.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
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/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
|
-
}
|