@konvert7/klint 0.37.1 → 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.
Files changed (56) hide show
  1. package/README.md +49 -166
  2. package/bin/klint.js +47 -0
  3. package/klint.schema.json +1 -1
  4. package/klint.schema.yaml +1 -1
  5. package/package.json +15 -25
  6. package/cli/command.ts +0 -189
  7. package/cli/commands.ts +0 -90
  8. package/cli/help.ts +0 -21
  9. package/cli/install-skill.ts +0 -260
  10. package/cli/output.ts +0 -178
  11. package/cli/paths.ts +0 -20
  12. package/cli/rust-config.ts +0 -145
  13. package/cli/rust-engine.ts +0 -323
  14. package/cli/stats.ts +0 -92
  15. package/cli.ts +0 -214
  16. package/core/arch-comments.ts +0 -125
  17. package/core/arch.ts +0 -470
  18. package/core/ast.ts +0 -63
  19. package/core/config.schema.ts +0 -413
  20. package/core/fixer.ts +0 -47
  21. package/core/native-binary.ts +0 -123
  22. package/core/paths.ts +0 -9
  23. package/core/rule-helpers.ts +0 -76
  24. package/core/runner.ts +0 -196
  25. package/core/schema-version.ts +0 -46
  26. package/core/skill.ts +0 -164
  27. package/core/stats.ts +0 -160
  28. package/core/types.ts +0 -146
  29. package/plugins/index.ts +0 -6
  30. package/plugins/sonar.ts +0 -27
  31. package/rules/index.ts +0 -26
  32. package/rules/no-async-predicate.ts +0 -51
  33. package/rules/no-consecutive-array-push.ts +0 -61
  34. package/rules/no-date-equality.ts +0 -35
  35. package/rules/no-floating-promise.ts +0 -37
  36. package/rules/no-misused-promises.ts +0 -59
  37. package/rules/no-nested-template-literals.ts +0 -47
  38. package/rules/no-object-in-template.ts +0 -95
  39. package/rules/no-optional-chain-on-non-nullable.ts +0 -47
  40. package/rules/no-single-char-class.ts +0 -113
  41. package/rules/no-string-match.ts +0 -63
  42. package/rules/no-sync-in-async.ts +0 -43
  43. package/rules/no-unguarded-json-parse.ts +0 -35
  44. package/rules/prefer-at.ts +0 -46
  45. package/rules/prefer-nullish-coalescing-assign.ts +0 -111
  46. package/rules/prefer-string-raw-regexp.ts +0 -93
  47. package/rules/prefer-string-raw.ts +0 -39
  48. package/rules/prefer-string-replaceall.ts +0 -67
  49. package/skill/klint-rules/SKILL.md +0 -264
  50. package/tools/generate-homebrew-formula.ts +0 -64
  51. package/tools/prepare-native-release.ts +0 -166
  52. package/tools/prepare-nuget-release.ts +0 -62
  53. package/tools/prepare-python-release.ts +0 -76
  54. package/tools/semantic-release-crates.js +0 -96
  55. package/tools/semantic-release-native-npm.js +0 -121
  56. 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
- }
@@ -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
- }
@@ -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
- }