@konvert7/klint 0.38.0 → 1.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/README.md +49 -166
- package/bin/klint.js +47 -0
- package/klint.schema.json +1 -1
- package/klint.schema.yaml +1 -1
- package/package.json +15 -25
- package/cli/command.ts +0 -189
- package/cli/commands.ts +0 -90
- package/cli/help.ts +0 -21
- package/cli/install-skill.ts +0 -260
- package/cli/output.ts +0 -178
- package/cli/paths.ts +0 -20
- package/cli/rust-config.ts +0 -145
- package/cli/rust-engine.ts +0 -323
- package/cli/stats.ts +0 -92
- package/cli.ts +0 -214
- package/core/arch-comments.ts +0 -125
- package/core/arch.ts +0 -470
- package/core/ast.ts +0 -63
- package/core/config.schema.ts +0 -413
- package/core/fixer.ts +0 -47
- package/core/native-binary.ts +0 -123
- package/core/paths.ts +0 -9
- package/core/rule-helpers.ts +0 -76
- package/core/runner.ts +0 -196
- package/core/schema-version.ts +0 -46
- package/core/skill.ts +0 -164
- package/core/stats.ts +0 -160
- package/core/types.ts +0 -146
- package/plugins/index.ts +0 -6
- package/plugins/sonar.ts +0 -27
- package/rules/index.ts +0 -26
- package/rules/no-async-predicate.ts +0 -51
- package/rules/no-consecutive-array-push.ts +0 -61
- package/rules/no-date-equality.ts +0 -35
- package/rules/no-floating-promise.ts +0 -37
- package/rules/no-misused-promises.ts +0 -59
- package/rules/no-nested-template-literals.ts +0 -47
- package/rules/no-object-in-template.ts +0 -95
- package/rules/no-optional-chain-on-non-nullable.ts +0 -47
- package/rules/no-single-char-class.ts +0 -113
- package/rules/no-string-match.ts +0 -63
- package/rules/no-sync-in-async.ts +0 -43
- package/rules/no-unguarded-json-parse.ts +0 -35
- package/rules/prefer-at.ts +0 -46
- package/rules/prefer-nullish-coalescing-assign.ts +0 -111
- package/rules/prefer-string-raw-regexp.ts +0 -93
- package/rules/prefer-string-raw.ts +0 -39
- package/rules/prefer-string-replaceall.ts +0 -67
- package/skill/klint-rules/SKILL.md +0 -264
- package/tools/generate-homebrew-formula.ts +0 -64
- package/tools/prepare-native-release.ts +0 -166
- package/tools/prepare-nuget-release.ts +0 -62
- package/tools/prepare-python-release.ts +0 -76
- package/tools/semantic-release-crates.js +0 -96
- package/tools/semantic-release-native-npm.js +0 -121
- package/tools/semantic-release-root-npm.js +0 -87
package/cli/output.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import type { KlintDebugEvent, runKlint } from "../core/runner";
|
|
2
|
-
import type { Violation } from "../core/types";
|
|
3
|
-
|
|
4
|
-
export function formatDuration(startedAt: number): string {
|
|
5
|
-
const elapsedMs = performance.now() - startedAt;
|
|
6
|
-
if (elapsedMs < 1000) return `${Math.round(elapsedMs)}ms`;
|
|
7
|
-
return `${(elapsedMs / 1000).toFixed(elapsedMs < 10_000 ? 2 : 1)}s`;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function writeTextOutput(
|
|
11
|
-
violations: Array<Omit<Violation, "fix">>,
|
|
12
|
-
startedAt: number
|
|
13
|
-
): never {
|
|
14
|
-
const errors = violations.filter((v) => v.severity === "error");
|
|
15
|
-
const warns = violations.filter((v) => v.severity === "warn");
|
|
16
|
-
|
|
17
|
-
if (errors.length === 0 && warns.length === 0) {
|
|
18
|
-
process.stdout.write(`klint: 0 violations in ${formatDuration(startedAt)}\n`);
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const formatBlock = (v: (typeof violations)[number]) => {
|
|
23
|
-
const prefix = v.severity === "warn" ? "⚠" : "×";
|
|
24
|
-
const header = `${v.file}:${v.line} [${v.rule}]`;
|
|
25
|
-
const sep = "━".repeat(Math.max(0, 80 - header.length));
|
|
26
|
-
return `${header} ${sep}\n\n ${prefix} ${v.message}\n`;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (warns.length > 0) {
|
|
30
|
-
process.stderr.write(
|
|
31
|
-
`klint: ${warns.length} warning(s)\n\n${warns.map(formatBlock).join("\n")}`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
if (errors.length > 0) {
|
|
35
|
-
process.stderr.write(
|
|
36
|
-
`klint: ${errors.length} error(s)\n\n${errors.map(formatBlock).join("\n")}`
|
|
37
|
-
);
|
|
38
|
-
process.stderr.write(`\nklint: finished in ${formatDuration(startedAt)}\n`);
|
|
39
|
-
process.exit(2);
|
|
40
|
-
}
|
|
41
|
-
process.stderr.write(`\nklint: finished in ${formatDuration(startedAt)}\n`);
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface JsonPayload {
|
|
46
|
-
violations: Array<Omit<Violation, "fix"> & { fix: unknown }>;
|
|
47
|
-
summary: {
|
|
48
|
-
errors: number;
|
|
49
|
-
warnings: number;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function emptyJsonPayload(): JsonPayload {
|
|
54
|
-
return {
|
|
55
|
-
violations: [],
|
|
56
|
-
summary: { errors: 0, warnings: 0 },
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function toJsonPayload(violations: ReturnType<typeof runKlint>): JsonPayload {
|
|
61
|
-
const errors = violations.filter((v) => v.severity === "error");
|
|
62
|
-
return {
|
|
63
|
-
violations: violations.map((v) => ({ ...v, fix: v.fix ?? null })),
|
|
64
|
-
summary: { errors: errors.length, warnings: violations.length - errors.length },
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function mergeJsonOutputs(...outputs: JsonPayload[]): JsonPayload {
|
|
69
|
-
const violations = outputs
|
|
70
|
-
.flatMap((output) => output.violations)
|
|
71
|
-
.sort(
|
|
72
|
-
(a, b) =>
|
|
73
|
-
a.file.localeCompare(b.file) ||
|
|
74
|
-
a.line - b.line ||
|
|
75
|
-
a.rule.localeCompare(b.rule) ||
|
|
76
|
-
a.message.localeCompare(b.message)
|
|
77
|
-
);
|
|
78
|
-
const errors = violations.filter((violation) => violation.severity === "error").length;
|
|
79
|
-
return {
|
|
80
|
-
violations,
|
|
81
|
-
summary: {
|
|
82
|
-
errors,
|
|
83
|
-
warnings: violations.length - errors,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function withAdvisories(
|
|
89
|
-
stdout: string,
|
|
90
|
-
advisories: Array<Omit<Violation, "fix">>
|
|
91
|
-
): string {
|
|
92
|
-
if (advisories.length === 0) return stdout;
|
|
93
|
-
const payload = parseJsonPayload(stdout);
|
|
94
|
-
if (!isJsonPayload(payload)) return stdout;
|
|
95
|
-
|
|
96
|
-
const violations = [
|
|
97
|
-
...payload.violations,
|
|
98
|
-
...advisories.map((advisory) => ({ ...advisory, fix: null })),
|
|
99
|
-
];
|
|
100
|
-
const errors = violations.filter((v) => v.severity === "error").length;
|
|
101
|
-
return JSON.stringify({
|
|
102
|
-
violations,
|
|
103
|
-
summary: { errors, warnings: violations.length - errors },
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function isJsonPayload(value: unknown): value is JsonPayload {
|
|
108
|
-
if (!value || typeof value !== "object") return false;
|
|
109
|
-
const candidate = value as Partial<JsonPayload>;
|
|
110
|
-
return Array.isArray(candidate.violations) && typeof candidate.summary === "object";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function toJsonCliResult(violations: ReturnType<typeof runKlint>): {
|
|
114
|
-
stdout: string;
|
|
115
|
-
status: number;
|
|
116
|
-
} {
|
|
117
|
-
const payload = toJsonPayload(violations);
|
|
118
|
-
return {
|
|
119
|
-
stdout: JSON.stringify(payload),
|
|
120
|
-
status: payload.summary.errors > 0 ? 2 : 0,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function writeDebugEvent(event: KlintDebugEvent): void {
|
|
125
|
-
switch (event.type) {
|
|
126
|
-
case "walk:start":
|
|
127
|
-
process.stderr.write(`[klint:debug] walk ${event.dir}\n`);
|
|
128
|
-
break;
|
|
129
|
-
case "walk:done":
|
|
130
|
-
process.stderr.write(`[klint:debug] walked ${event.dir} (${event.files} files)\n`);
|
|
131
|
-
break;
|
|
132
|
-
case "files:resolved":
|
|
133
|
-
process.stderr.write(`[klint:debug] resolved ${event.files} files\n`);
|
|
134
|
-
break;
|
|
135
|
-
case "rule:start":
|
|
136
|
-
process.stderr.write(
|
|
137
|
-
`[klint:debug] rule ${event.rule} start (${event.files} files)\n`
|
|
138
|
-
);
|
|
139
|
-
break;
|
|
140
|
-
case "rule:done":
|
|
141
|
-
process.stderr.write(
|
|
142
|
-
`[klint:debug] rule ${event.rule} done (${event.violations} violations)\n`
|
|
143
|
-
);
|
|
144
|
-
break;
|
|
145
|
-
case "arch:start":
|
|
146
|
-
process.stderr.write(`[klint:debug] arch start (${event.files} files)\n`);
|
|
147
|
-
break;
|
|
148
|
-
case "arch:done":
|
|
149
|
-
process.stderr.write(`[klint:debug] arch done (${event.violations} violations)\n`);
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function parseJsonPayload(value: string): unknown {
|
|
155
|
-
try {
|
|
156
|
-
return JSON.parse(value);
|
|
157
|
-
} catch {
|
|
158
|
-
return undefined;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function jsonPayloadEquals(a: unknown, b: unknown): boolean {
|
|
163
|
-
return JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function canonicalJson(value: unknown): unknown {
|
|
167
|
-
if (Array.isArray(value)) {
|
|
168
|
-
return value
|
|
169
|
-
.map(canonicalJson)
|
|
170
|
-
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
171
|
-
}
|
|
172
|
-
if (!value || typeof value !== "object") return value;
|
|
173
|
-
return Object.fromEntries(
|
|
174
|
-
Object.entries(value)
|
|
175
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
176
|
-
.map(([key, entry]) => [key, canonicalJson(entry)])
|
|
177
|
-
);
|
|
178
|
-
}
|
package/cli/paths.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
export const PACKAGE_ROOT = resolve(import.meta.dir, "..");
|
|
5
|
-
|
|
6
|
-
export const SHIPPED_SKILL_PATH = resolve(
|
|
7
|
-
PACKAGE_ROOT,
|
|
8
|
-
"skill",
|
|
9
|
-
"klint-rules",
|
|
10
|
-
"SKILL.md"
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
export function installedVersion(): string {
|
|
14
|
-
try {
|
|
15
|
-
const manifest = readFileSync(resolve(PACKAGE_ROOT, "package.json"), "utf-8");
|
|
16
|
-
return (JSON.parse(manifest) as { version?: string }).version ?? "0.0.0";
|
|
17
|
-
} catch {
|
|
18
|
-
return "0.0.0";
|
|
19
|
-
}
|
|
20
|
-
}
|
package/cli/rust-config.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { resolveNativePackageBinary } from "../core/native-binary";
|
|
4
|
-
import type { RuleConfigValue } from "../core/types";
|
|
5
|
-
import { BUILT_IN_PLUGINS } from "../plugins/index";
|
|
6
|
-
import { PACKAGE_ROOT } from "./paths";
|
|
7
|
-
|
|
8
|
-
const RUST_SUPPORTED_RULES = new Set([
|
|
9
|
-
"no-async-predicate",
|
|
10
|
-
"no-consecutive-array-push",
|
|
11
|
-
"no-nested-template-literals",
|
|
12
|
-
"no-string-match",
|
|
13
|
-
"no-sync-in-async",
|
|
14
|
-
"no-unguarded-json-parse",
|
|
15
|
-
"sonar/no-single-char-class",
|
|
16
|
-
"sonar/prefer-at",
|
|
17
|
-
"sonar/prefer-nullish-coalescing-assign",
|
|
18
|
-
"sonar/prefer-string-replaceall",
|
|
19
|
-
"sonar/prefer-string-raw",
|
|
20
|
-
"sonar/prefer-string-raw-regexp",
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
export function splitRulesForAuto(rules: Record<string, RuleConfigValue>): {
|
|
24
|
-
rustRules: Record<string, RuleConfigValue>;
|
|
25
|
-
tsRules: Record<string, RuleConfigValue>;
|
|
26
|
-
} {
|
|
27
|
-
const rustRules: Record<string, RuleConfigValue> = {};
|
|
28
|
-
const tsRules: Record<string, RuleConfigValue> = {};
|
|
29
|
-
|
|
30
|
-
for (const [name, value] of Object.entries(rules)) {
|
|
31
|
-
if (RUST_SUPPORTED_RULES.has(name)) rustRules[name] = value;
|
|
32
|
-
else tsRules[name] = value;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return { rustRules, tsRules };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface RustEngineCommand {
|
|
39
|
-
bin: string;
|
|
40
|
-
args: string[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function resolveRustEngineCommand(configDir: string): RustEngineCommand {
|
|
44
|
-
const args = ["--config", configDir, "--json"];
|
|
45
|
-
const explicitBin = process.env.KLINT_RUST_BIN;
|
|
46
|
-
if (explicitBin) {
|
|
47
|
-
return { bin: explicitBin, args };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const localBin = join(
|
|
51
|
-
PACKAGE_ROOT,
|
|
52
|
-
"target",
|
|
53
|
-
"debug",
|
|
54
|
-
process.platform === "win32" ? "klint-rs.exe" : "klint-rs"
|
|
55
|
-
);
|
|
56
|
-
if (isSourceCheckout() && existsSync(localBin)) {
|
|
57
|
-
return { bin: localBin, args };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const nativePackageBin = resolveNativePackageBinary({ packageRoot: PACKAGE_ROOT });
|
|
61
|
-
if (nativePackageBin) {
|
|
62
|
-
return { bin: nativePackageBin, args };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!isSourceCheckout()) {
|
|
66
|
-
process.stderr.write(
|
|
67
|
-
"klint: native Rust engine binary is unavailable for this package version\n"
|
|
68
|
-
);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
bin: "cargo",
|
|
74
|
-
args: ["run", "--quiet", "-p", "klint-rs", "--", ...args],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isSourceCheckout(): boolean {
|
|
79
|
-
return existsSync(join(PACKAGE_ROOT, "crates", "klint-rs", "Cargo.toml"));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function rustEngineUnsupportedReason({
|
|
83
|
-
fix,
|
|
84
|
-
raw,
|
|
85
|
-
rulesFile,
|
|
86
|
-
}: {
|
|
87
|
-
fix: boolean;
|
|
88
|
-
raw: {
|
|
89
|
-
plugins?: string[];
|
|
90
|
-
rules?: Record<string, RuleConfigValue>;
|
|
91
|
-
arch?: unknown;
|
|
92
|
-
};
|
|
93
|
-
rulesFile?: string;
|
|
94
|
-
}): string | undefined {
|
|
95
|
-
if (fix) return "KLINT_ENGINE=rust does not support --fix";
|
|
96
|
-
if (rulesFile) return "KLINT_ENGINE=rust does not support --rules";
|
|
97
|
-
let effectiveRules: Record<string, RuleConfigValue>;
|
|
98
|
-
try {
|
|
99
|
-
effectiveRules = resolveEffectiveRules(raw.plugins, raw.rules ?? {});
|
|
100
|
-
} catch (error) {
|
|
101
|
-
return error instanceof Error
|
|
102
|
-
? error.message
|
|
103
|
-
: "KLINT_ENGINE=rust failed to load plugins";
|
|
104
|
-
}
|
|
105
|
-
const activeSupportedRules = Object.entries(effectiveRules).filter(
|
|
106
|
-
([name, value]) =>
|
|
107
|
-
ruleConfigSeverity(value) !== "off" && RUST_SUPPORTED_RULES.has(name)
|
|
108
|
-
);
|
|
109
|
-
const unsupportedRules = Object.entries(effectiveRules)
|
|
110
|
-
.filter(
|
|
111
|
-
([name, value]) =>
|
|
112
|
-
ruleConfigSeverity(value) !== "off" && !RUST_SUPPORTED_RULES.has(name)
|
|
113
|
-
)
|
|
114
|
-
.map(([name]) => name);
|
|
115
|
-
if (!raw.arch && activeSupportedRules.length === 0 && unsupportedRules.length === 0) {
|
|
116
|
-
return "KLINT_ENGINE=rust requires an arch config or supported rule";
|
|
117
|
-
}
|
|
118
|
-
if (unsupportedRules.length > 0) {
|
|
119
|
-
return [
|
|
120
|
-
"Rust engine currently supports arch rules and selected rules only",
|
|
121
|
-
"",
|
|
122
|
-
"Unsupported rules:",
|
|
123
|
-
...unsupportedRules.map((rule) => `- ${rule}`),
|
|
124
|
-
].join("\n");
|
|
125
|
-
}
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function resolveEffectiveRules(
|
|
130
|
-
plugins: string[] | undefined,
|
|
131
|
-
rules: Record<string, RuleConfigValue>
|
|
132
|
-
): Record<string, RuleConfigValue> {
|
|
133
|
-
const pluginDefaults: Record<string, RuleConfigValue> = {};
|
|
134
|
-
for (const pluginName of plugins ?? []) {
|
|
135
|
-
const plugin = BUILT_IN_PLUGINS[pluginName];
|
|
136
|
-
if (!plugin) throw new Error(`Unknown klint plugin: "${pluginName}"`);
|
|
137
|
-
Object.assign(pluginDefaults, plugin.rules);
|
|
138
|
-
}
|
|
139
|
-
return { ...pluginDefaults, ...rules };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function ruleConfigSeverity(value: RuleConfigValue): string | undefined {
|
|
143
|
-
if (typeof value === "string") return value;
|
|
144
|
-
return value.severity;
|
|
145
|
-
}
|
package/cli/rust-engine.ts
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { type SpawnSyncReturns, spawnSync } from "node:child_process";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { runKlint } from "../core/runner";
|
|
6
|
-
import type { RunRecorder } from "../core/stats";
|
|
7
|
-
import type { KlintRule, RuleConfigValue, Violation } from "../core/types";
|
|
8
|
-
import {
|
|
9
|
-
emptyJsonPayload,
|
|
10
|
-
isJsonPayload,
|
|
11
|
-
type JsonPayload,
|
|
12
|
-
jsonPayloadEquals,
|
|
13
|
-
mergeJsonOutputs,
|
|
14
|
-
parseJsonPayload,
|
|
15
|
-
toJsonCliResult,
|
|
16
|
-
toJsonPayload,
|
|
17
|
-
withAdvisories,
|
|
18
|
-
writeTextOutput,
|
|
19
|
-
} from "./output";
|
|
20
|
-
import { PACKAGE_ROOT } from "./paths";
|
|
21
|
-
import {
|
|
22
|
-
resolveEffectiveRules,
|
|
23
|
-
resolveRustEngineCommand,
|
|
24
|
-
ruleConfigSeverity,
|
|
25
|
-
rustEngineUnsupportedReason,
|
|
26
|
-
splitRulesForAuto,
|
|
27
|
-
} from "./rust-config";
|
|
28
|
-
|
|
29
|
-
const RUST_CHILD_ENV = { ...process.env, KLINT_NO_STATS: "1" };
|
|
30
|
-
|
|
31
|
-
export function runRustEngine({
|
|
32
|
-
advisories,
|
|
33
|
-
fix,
|
|
34
|
-
json,
|
|
35
|
-
raw,
|
|
36
|
-
record,
|
|
37
|
-
root,
|
|
38
|
-
rulesFile,
|
|
39
|
-
startedAt,
|
|
40
|
-
}: {
|
|
41
|
-
advisories: Array<Omit<Violation, "fix">>;
|
|
42
|
-
fix: boolean;
|
|
43
|
-
json: boolean;
|
|
44
|
-
raw: {
|
|
45
|
-
plugins?: string[];
|
|
46
|
-
rules?: Record<string, RuleConfigValue>;
|
|
47
|
-
arch?: unknown;
|
|
48
|
-
};
|
|
49
|
-
record: RunRecorder;
|
|
50
|
-
root: string;
|
|
51
|
-
rulesFile?: string;
|
|
52
|
-
startedAt: number;
|
|
53
|
-
}): void {
|
|
54
|
-
const unsupportedReason = rustEngineUnsupportedReason({
|
|
55
|
-
fix,
|
|
56
|
-
raw,
|
|
57
|
-
rulesFile,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (unsupportedReason) {
|
|
61
|
-
process.stderr.write(`klint: ${unsupportedReason}\n`);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { configDir: rustConfigDir, cleanup } = writeRustEngineConfig({ raw, root });
|
|
66
|
-
const command = resolveRustEngineCommand(rustConfigDir);
|
|
67
|
-
let result: SpawnSyncReturns<string>;
|
|
68
|
-
try {
|
|
69
|
-
if (process.argv.includes("--debug") || process.argv.includes("-debug")) {
|
|
70
|
-
process.stderr.write(
|
|
71
|
-
`[klint:debug] rust engine: ${command.bin} ${command.args.join(" ")}\n`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
result = spawnSync(command.bin, command.args, {
|
|
75
|
-
cwd: PACKAGE_ROOT,
|
|
76
|
-
encoding: "utf-8",
|
|
77
|
-
env: RUST_CHILD_ENV,
|
|
78
|
-
});
|
|
79
|
-
} finally {
|
|
80
|
-
cleanup();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
84
|
-
const payload = parseJsonPayload(result.stdout);
|
|
85
|
-
if (isJsonPayload(payload)) record(payload.violations);
|
|
86
|
-
if (json) {
|
|
87
|
-
if (result.stdout) process.stdout.write(withAdvisories(result.stdout, advisories));
|
|
88
|
-
process.exit(result.status ?? 1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!isJsonPayload(payload)) {
|
|
92
|
-
process.stderr.write("klint: --engine rust failed to parse Rust JSON output\n");
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
writeTextOutput([...payload.violations, ...advisories], startedAt);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function runCompareEngine({
|
|
99
|
-
advisories,
|
|
100
|
-
fix,
|
|
101
|
-
json,
|
|
102
|
-
raw,
|
|
103
|
-
record,
|
|
104
|
-
root,
|
|
105
|
-
rulesFile,
|
|
106
|
-
tsViolations,
|
|
107
|
-
}: {
|
|
108
|
-
advisories: Array<Omit<Violation, "fix">>;
|
|
109
|
-
fix: boolean;
|
|
110
|
-
json: boolean;
|
|
111
|
-
raw: {
|
|
112
|
-
plugins?: string[];
|
|
113
|
-
rules?: Record<string, RuleConfigValue>;
|
|
114
|
-
arch?: unknown;
|
|
115
|
-
};
|
|
116
|
-
record: RunRecorder;
|
|
117
|
-
root: string;
|
|
118
|
-
rulesFile?: string;
|
|
119
|
-
tsViolations: ReturnType<typeof runKlint>;
|
|
120
|
-
}): void {
|
|
121
|
-
if (!json) {
|
|
122
|
-
process.stderr.write("klint: --engine compare currently requires --json\n");
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const unsupportedReason = rustEngineUnsupportedReason({
|
|
127
|
-
fix,
|
|
128
|
-
raw,
|
|
129
|
-
rulesFile,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
if (unsupportedReason) {
|
|
133
|
-
process.stderr.write(`klint: ${unsupportedReason}\n`);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const tsResult = toJsonCliResult(tsViolations);
|
|
138
|
-
const { configDir: rustConfigDir, cleanup } = writeRustEngineConfig({ raw, root });
|
|
139
|
-
const rustCommand = resolveRustEngineCommand(rustConfigDir);
|
|
140
|
-
let rustResult: SpawnSyncReturns<string>;
|
|
141
|
-
try {
|
|
142
|
-
rustResult = spawnSync(rustCommand.bin, rustCommand.args, {
|
|
143
|
-
cwd: PACKAGE_ROOT,
|
|
144
|
-
encoding: "utf-8",
|
|
145
|
-
env: RUST_CHILD_ENV,
|
|
146
|
-
});
|
|
147
|
-
} finally {
|
|
148
|
-
cleanup();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const rustStatus = rustResult.status ?? 1;
|
|
152
|
-
if (rustResult.stderr) process.stderr.write(rustResult.stderr);
|
|
153
|
-
|
|
154
|
-
const tsPayload = parseJsonPayload(tsResult.stdout);
|
|
155
|
-
const rustPayload = parseJsonPayload(rustResult.stdout);
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
rustStatus !== tsResult.status ||
|
|
159
|
-
tsPayload === undefined ||
|
|
160
|
-
rustPayload === undefined ||
|
|
161
|
-
!jsonPayloadEquals(tsPayload, rustPayload)
|
|
162
|
-
) {
|
|
163
|
-
const mismatchLines = [
|
|
164
|
-
"klint: compare engine mismatch",
|
|
165
|
-
`TypeScript exit: ${tsResult.status}`,
|
|
166
|
-
`Rust exit: ${rustStatus}`,
|
|
167
|
-
];
|
|
168
|
-
process.stderr.write(`${mismatchLines.join("\n")}\n`);
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
record(tsViolations);
|
|
173
|
-
process.stdout.write(withAdvisories(tsResult.stdout, advisories));
|
|
174
|
-
process.exit(tsResult.status);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function writeRustEngineConfig({
|
|
178
|
-
raw,
|
|
179
|
-
root,
|
|
180
|
-
}: {
|
|
181
|
-
raw: {
|
|
182
|
-
include?: string[];
|
|
183
|
-
plugins?: string[];
|
|
184
|
-
rules?: Record<string, RuleConfigValue>;
|
|
185
|
-
arch?: unknown;
|
|
186
|
-
};
|
|
187
|
-
root: string;
|
|
188
|
-
}): { configDir: string; cleanup: () => void } {
|
|
189
|
-
const tempDir = mkdtempSync(join(tmpdir(), "klint-rust-engine-"));
|
|
190
|
-
writeFileSync(
|
|
191
|
-
join(tempDir, "klint.config.json"),
|
|
192
|
-
JSON.stringify({
|
|
193
|
-
root,
|
|
194
|
-
include: raw.include ?? ["."],
|
|
195
|
-
plugins: [],
|
|
196
|
-
rules: resolveEffectiveRules(raw.plugins, raw.rules ?? {}),
|
|
197
|
-
arch: raw.arch,
|
|
198
|
-
})
|
|
199
|
-
);
|
|
200
|
-
return {
|
|
201
|
-
configDir: tempDir,
|
|
202
|
-
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function runAutoEngine({
|
|
207
|
-
advisories,
|
|
208
|
-
fix,
|
|
209
|
-
json,
|
|
210
|
-
raw,
|
|
211
|
-
record,
|
|
212
|
-
root,
|
|
213
|
-
rulesFile,
|
|
214
|
-
startedAt,
|
|
215
|
-
customRules,
|
|
216
|
-
customRulesMap,
|
|
217
|
-
}: {
|
|
218
|
-
advisories: Array<Omit<Violation, "fix">>;
|
|
219
|
-
fix: boolean;
|
|
220
|
-
json: boolean;
|
|
221
|
-
raw: {
|
|
222
|
-
root?: string;
|
|
223
|
-
include?: string[];
|
|
224
|
-
plugins?: string[];
|
|
225
|
-
rules?: Record<string, RuleConfigValue>;
|
|
226
|
-
arch?: unknown;
|
|
227
|
-
};
|
|
228
|
-
record: RunRecorder;
|
|
229
|
-
root: string;
|
|
230
|
-
rulesFile?: string;
|
|
231
|
-
startedAt: number;
|
|
232
|
-
customRules: Record<string, KlintRule>;
|
|
233
|
-
customRulesMap: Record<string, RuleConfigValue>;
|
|
234
|
-
}): void {
|
|
235
|
-
if (fix) {
|
|
236
|
-
process.stderr.write("klint: --engine auto does not support --fix\n");
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
if (rulesFile) {
|
|
240
|
-
process.stderr.write("klint: --engine auto does not support --rules\n");
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const effectiveRules = resolveEffectiveRules(raw.plugins, raw.rules ?? {});
|
|
245
|
-
const { rustRules, tsRules } = splitRulesForAuto(effectiveRules);
|
|
246
|
-
const tsViolations = runKlint(
|
|
247
|
-
{
|
|
248
|
-
root,
|
|
249
|
-
include: raw.include ?? ["."],
|
|
250
|
-
plugins: [],
|
|
251
|
-
rules: { ...customRulesMap, ...tsRules },
|
|
252
|
-
},
|
|
253
|
-
customRules
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
const rustOutput = runAutoRustSubset({
|
|
257
|
-
raw,
|
|
258
|
-
root,
|
|
259
|
-
rustRules,
|
|
260
|
-
});
|
|
261
|
-
const tsOutput = toJsonPayload(tsViolations);
|
|
262
|
-
const merged = mergeJsonOutputs(rustOutput, tsOutput);
|
|
263
|
-
record(merged.violations);
|
|
264
|
-
if (json) {
|
|
265
|
-
process.stdout.write(withAdvisories(JSON.stringify(merged), advisories));
|
|
266
|
-
process.exit(merged.summary.errors > 0 ? 2 : 0);
|
|
267
|
-
}
|
|
268
|
-
writeTextOutput([...merged.violations, ...advisories], startedAt);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function runAutoRustSubset({
|
|
272
|
-
raw,
|
|
273
|
-
root,
|
|
274
|
-
rustRules,
|
|
275
|
-
}: {
|
|
276
|
-
raw: {
|
|
277
|
-
include?: string[];
|
|
278
|
-
arch?: unknown;
|
|
279
|
-
};
|
|
280
|
-
root: string;
|
|
281
|
-
rustRules: Record<string, RuleConfigValue>;
|
|
282
|
-
}): JsonPayload {
|
|
283
|
-
const hasRustRules = Object.values(rustRules).some(
|
|
284
|
-
(value) => ruleConfigSeverity(value) !== "off"
|
|
285
|
-
);
|
|
286
|
-
if (!raw.arch && !hasRustRules) {
|
|
287
|
-
return emptyJsonPayload();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const tempDir = mkdtempSync(join(tmpdir(), "klint-auto-rust-"));
|
|
291
|
-
try {
|
|
292
|
-
writeFileSync(
|
|
293
|
-
join(tempDir, "klint.config.json"),
|
|
294
|
-
JSON.stringify({
|
|
295
|
-
root,
|
|
296
|
-
include: raw.include ?? ["."],
|
|
297
|
-
rules: rustRules,
|
|
298
|
-
arch: raw.arch,
|
|
299
|
-
})
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
const command = resolveRustEngineCommand(tempDir);
|
|
303
|
-
const result = spawnSync(command.bin, command.args, {
|
|
304
|
-
cwd: PACKAGE_ROOT,
|
|
305
|
-
encoding: "utf-8",
|
|
306
|
-
env: RUST_CHILD_ENV,
|
|
307
|
-
});
|
|
308
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
309
|
-
if ((result.status ?? 1) !== 0 && (result.status ?? 1) !== 2) {
|
|
310
|
-
process.stderr.write(result.stdout);
|
|
311
|
-
process.exit(result.status ?? 1);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const payload = parseJsonPayload(result.stdout);
|
|
315
|
-
if (!isJsonPayload(payload)) {
|
|
316
|
-
process.stderr.write("klint: --engine auto failed to parse Rust JSON output\n");
|
|
317
|
-
process.exit(1);
|
|
318
|
-
}
|
|
319
|
-
return payload;
|
|
320
|
-
} finally {
|
|
321
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
322
|
-
}
|
|
323
|
-
}
|