@konvert7/klint 0.35.2 → 0.37.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.md +10 -7
- package/cli/command.ts +189 -0
- package/cli/commands.ts +90 -0
- package/cli/help.ts +11 -42
- package/cli/install-skill.ts +15 -20
- package/cli/rust-config.ts +1 -0
- package/cli/stats.ts +75 -17
- package/cli.ts +25 -51
- package/core/stats.ts +43 -11
- package/klint.schema.json +905 -1081
- package/klint.schema.yaml +698 -906
- package/package.json +7 -7
- package/skill/klint-rules/SKILL.md +2 -2
- package/tools/semantic-release-root-npm.js +4 -3
- package/tools/generate-schema.ts +0 -34
package/core/stats.ts
CHANGED
|
@@ -6,6 +6,7 @@ export const STATS_DIR = ".klint";
|
|
|
6
6
|
export const STATS_FILE = "stats.json";
|
|
7
7
|
const SELF_IGNORE = "# Created by klint automatically.\n*\n";
|
|
8
8
|
const STATS_VERSION = 1;
|
|
9
|
+
const KLINT_ADVISORY_PREFIX = "klint/";
|
|
9
10
|
|
|
10
11
|
export type RunSource = "hook" | "direct";
|
|
11
12
|
|
|
@@ -23,18 +24,31 @@ interface OpenError {
|
|
|
23
24
|
export interface KlintStats {
|
|
24
25
|
version: typeof STATS_VERSION;
|
|
25
26
|
runs: Record<RunSource, number>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
bySource: Record<RunSource, number>;
|
|
29
|
-
};
|
|
27
|
+
reported: RuleCounts;
|
|
28
|
+
prevented: RuleCounts;
|
|
30
29
|
open: OpenError[];
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
interface RuleCounts {
|
|
33
|
+
byRule: Record<string, number>;
|
|
34
|
+
bySource: Record<RunSource, number>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function emptyCounts(): RuleCounts {
|
|
38
|
+
return { byRule: {}, bySource: { hook: 0, direct: 0 } };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function bump(counts: RuleCounts, rule: string, source: RunSource): void {
|
|
42
|
+
counts.byRule[rule] = (counts.byRule[rule] ?? 0) + 1;
|
|
43
|
+
counts.bySource[source] += 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
export function emptyStats(): KlintStats {
|
|
34
47
|
return {
|
|
35
48
|
version: STATS_VERSION,
|
|
36
49
|
runs: { hook: 0, direct: 0 },
|
|
37
|
-
|
|
50
|
+
reported: emptyCounts(),
|
|
51
|
+
prevented: emptyCounts(),
|
|
38
52
|
open: [],
|
|
39
53
|
};
|
|
40
54
|
}
|
|
@@ -43,6 +57,10 @@ function sameError(a: Omit<OpenError, "caughtBy">, b: Omit<OpenError, "caughtBy"
|
|
|
43
57
|
return a.rule === b.rule && a.file === b.file && a.message === b.message;
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
function isCodeViolation(violation: CountedViolation): boolean {
|
|
61
|
+
return !violation.rule.startsWith(KLINT_ADVISORY_PREFIX);
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
function errorsOf(
|
|
47
65
|
violations: readonly CountedViolation[]
|
|
48
66
|
): Omit<OpenError, "caughtBy">[] {
|
|
@@ -58,14 +76,16 @@ export function nextStats(
|
|
|
58
76
|
): KlintStats {
|
|
59
77
|
const stats = structuredClone(previous);
|
|
60
78
|
stats.runs[source] += 1;
|
|
79
|
+
for (const violation of violations.filter(isCodeViolation)) {
|
|
80
|
+
bump(stats.reported, violation.rule, source);
|
|
81
|
+
}
|
|
61
82
|
|
|
62
83
|
const unmatched = errorsOf(violations);
|
|
63
84
|
const stillOpen: OpenError[] = [];
|
|
64
85
|
for (const error of previous.open) {
|
|
65
86
|
const match = unmatched.findIndex((current) => sameError(current, error));
|
|
66
87
|
if (match === -1) {
|
|
67
|
-
stats.prevented
|
|
68
|
-
stats.prevented.bySource[error.caughtBy] += 1;
|
|
88
|
+
bump(stats.prevented, error.rule, error.caughtBy);
|
|
69
89
|
continue;
|
|
70
90
|
}
|
|
71
91
|
unmatched.splice(match, 1);
|
|
@@ -78,6 +98,15 @@ export function nextStats(
|
|
|
78
98
|
return stats;
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
function isRuleCounts(value: unknown): value is RuleCounts {
|
|
102
|
+
const candidate = value as Partial<RuleCounts> | undefined;
|
|
103
|
+
return (
|
|
104
|
+
typeof candidate?.byRule === "object" &&
|
|
105
|
+
typeof candidate.bySource?.hook === "number" &&
|
|
106
|
+
typeof candidate.bySource.direct === "number"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
function isStats(value: unknown): value is KlintStats {
|
|
82
111
|
if (!value || typeof value !== "object") return false;
|
|
83
112
|
const candidate = value as Partial<KlintStats>;
|
|
@@ -85,19 +114,22 @@ function isStats(value: unknown): value is KlintStats {
|
|
|
85
114
|
candidate.version === STATS_VERSION &&
|
|
86
115
|
typeof candidate.runs?.hook === "number" &&
|
|
87
116
|
typeof candidate.runs.direct === "number" &&
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
typeof candidate.prevented.bySource.direct === "number" &&
|
|
117
|
+
isRuleCounts(candidate.prevented) &&
|
|
118
|
+
(candidate.reported === undefined || isRuleCounts(candidate.reported)) &&
|
|
91
119
|
Array.isArray(candidate.open)
|
|
92
120
|
);
|
|
93
121
|
}
|
|
94
122
|
|
|
123
|
+
function withReportedCounts(stats: KlintStats): KlintStats {
|
|
124
|
+
return { ...stats, reported: stats.reported ?? emptyCounts() };
|
|
125
|
+
}
|
|
126
|
+
|
|
95
127
|
export function readStats(configDir: string): KlintStats {
|
|
96
128
|
try {
|
|
97
129
|
const parsed: unknown = JSON.parse(
|
|
98
130
|
readFileSync(join(configDir, STATS_DIR, STATS_FILE), "utf-8")
|
|
99
131
|
);
|
|
100
|
-
return isStats(parsed) ? parsed : emptyStats();
|
|
132
|
+
return isStats(parsed) ? withReportedCounts(parsed) : emptyStats();
|
|
101
133
|
} catch {
|
|
102
134
|
return emptyStats();
|
|
103
135
|
}
|