pi-lens 2.2.9 → 3.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/CHANGELOG.md +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import type { DispatchContext } from "../types.js";
|
|
6
|
+
|
|
7
|
+
function createMockContext(filePath: string): DispatchContext {
|
|
8
|
+
return {
|
|
9
|
+
filePath,
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
kind: "python" as any,
|
|
12
|
+
autofix: false,
|
|
13
|
+
deltaMode: false,
|
|
14
|
+
baselines: { get: () => [], add: () => {}, save: () => {} } as any,
|
|
15
|
+
pi: {} as any,
|
|
16
|
+
hasTool: async () => false,
|
|
17
|
+
log: () => {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Helper for safe file cleanup
|
|
22
|
+
function safeUnlink(filePath: string): void {
|
|
23
|
+
try {
|
|
24
|
+
if (fs.existsSync(filePath)) {
|
|
25
|
+
fs.unlinkSync(filePath);
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// Ignore cleanup errors on Windows
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("python-slop runner", () => {
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
it("should have correct runner definition", async () => {
|
|
36
|
+
const slopModule = await import("./python-slop.js");
|
|
37
|
+
const runner = slopModule.default;
|
|
38
|
+
|
|
39
|
+
expect(runner.id).toBe("python-slop");
|
|
40
|
+
expect(runner.appliesTo).toEqual(["python"]);
|
|
41
|
+
expect(runner.priority).toBe(25);
|
|
42
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
43
|
+
expect(runner.skipTestFiles).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should detect ast-grep availability", () => {
|
|
47
|
+
const { spawnSync } =
|
|
48
|
+
require("node:child_process") as typeof import("node:child_process");
|
|
49
|
+
const result = spawnSync("npx", ["sg", "--version"], {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
timeout: 10000,
|
|
52
|
+
shell: true,
|
|
53
|
+
});
|
|
54
|
+
expect(
|
|
55
|
+
result.error || result.status !== 0 ? "not available" : "available",
|
|
56
|
+
).toBe("available");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should detect verbose range-len pattern", async () => {
|
|
60
|
+
const tmpFile = path.join(
|
|
61
|
+
process.env.TEMP || "/tmp",
|
|
62
|
+
`slop_test_range_${Date.now()}.py`,
|
|
63
|
+
);
|
|
64
|
+
fs.writeFileSync(
|
|
65
|
+
tmpFile,
|
|
66
|
+
`# Slop: using range(len()) instead of enumerate
|
|
67
|
+
def process_items(items):
|
|
68
|
+
for i in range(len(items)):
|
|
69
|
+
print(items[i])
|
|
70
|
+
`,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const slopModule = await import("./python-slop.js");
|
|
75
|
+
const runner = slopModule.default;
|
|
76
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
77
|
+
|
|
78
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
79
|
+
expect(
|
|
80
|
+
result.diagnostics.some(
|
|
81
|
+
(d) =>
|
|
82
|
+
d.tool === "python-slop" &&
|
|
83
|
+
d.message.includes("range(len") &&
|
|
84
|
+
d.message.includes("enumerate"),
|
|
85
|
+
),
|
|
86
|
+
).toBe(true);
|
|
87
|
+
} finally {
|
|
88
|
+
safeUnlink(tmpFile);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should detect manual min/max pattern", async () => {
|
|
93
|
+
const tmpFile = path.join(
|
|
94
|
+
process.env.TEMP || "/tmp",
|
|
95
|
+
`slop_test_minmax_${Date.now()}.py`,
|
|
96
|
+
);
|
|
97
|
+
fs.writeFileSync(
|
|
98
|
+
tmpFile,
|
|
99
|
+
`# Slop: manual min/max instead of built-in
|
|
100
|
+
def find_max(a, b):
|
|
101
|
+
if a > b:
|
|
102
|
+
m = a
|
|
103
|
+
else:
|
|
104
|
+
m = b
|
|
105
|
+
return m
|
|
106
|
+
`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const slopModule = await import("./python-slop.js");
|
|
111
|
+
const runner = slopModule.default;
|
|
112
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
113
|
+
|
|
114
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
115
|
+
expect(
|
|
116
|
+
result.diagnostics.some(
|
|
117
|
+
(d) =>
|
|
118
|
+
d.tool === "python-slop" &&
|
|
119
|
+
(d.message.includes("min") || d.message.includes("max")),
|
|
120
|
+
),
|
|
121
|
+
).toBe(true);
|
|
122
|
+
} finally {
|
|
123
|
+
safeUnlink(tmpFile);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should detect defensive None guard", async () => {
|
|
128
|
+
const tmpFile = path.join(
|
|
129
|
+
process.env.TEMP || "/tmp",
|
|
130
|
+
`slop_test_guard_${Date.now()}.py`,
|
|
131
|
+
);
|
|
132
|
+
fs.writeFileSync(
|
|
133
|
+
tmpFile,
|
|
134
|
+
`# Slop: defensive None guard
|
|
135
|
+
def process(data):
|
|
136
|
+
if data is None:
|
|
137
|
+
return None
|
|
138
|
+
return data.upper()
|
|
139
|
+
`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const slopModule = await import("./python-slop.js");
|
|
144
|
+
const runner = slopModule.default;
|
|
145
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
146
|
+
|
|
147
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
148
|
+
expect(
|
|
149
|
+
result.diagnostics.some(
|
|
150
|
+
(d) =>
|
|
151
|
+
d.tool === "python-slop" &&
|
|
152
|
+
(d.message.includes("defensive") ||
|
|
153
|
+
d.message.includes("guard")),
|
|
154
|
+
),
|
|
155
|
+
).toBe(true);
|
|
156
|
+
} finally {
|
|
157
|
+
safeUnlink(tmpFile);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should detect list comprehension ceremony", async () => {
|
|
162
|
+
const tmpFile = path.join(
|
|
163
|
+
process.env.TEMP || "/tmp",
|
|
164
|
+
`slop_test_list_${Date.now()}.py`,
|
|
165
|
+
);
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
tmpFile,
|
|
168
|
+
`# Slop: redundant list comprehension
|
|
169
|
+
def convert(items):
|
|
170
|
+
return [x for x in items]
|
|
171
|
+
`,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const slopModule = await import("./python-slop.js");
|
|
176
|
+
const runner = slopModule.default;
|
|
177
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
178
|
+
|
|
179
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
180
|
+
expect(
|
|
181
|
+
result.diagnostics.some(
|
|
182
|
+
(d) =>
|
|
183
|
+
d.tool === "python-slop" &&
|
|
184
|
+
d.message.includes("list") &&
|
|
185
|
+
d.message.includes("unnecessary"),
|
|
186
|
+
),
|
|
187
|
+
).toBe(true);
|
|
188
|
+
} finally {
|
|
189
|
+
safeUnlink(tmpFile);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should detect chained comparison opportunity", async () => {
|
|
194
|
+
const tmpFile = path.join(
|
|
195
|
+
process.env.TEMP || "/tmp",
|
|
196
|
+
`slop_test_chain_${Date.now()}.py`,
|
|
197
|
+
);
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
tmpFile,
|
|
200
|
+
`# Slop: could use chained comparison
|
|
201
|
+
def check_range(x, a, b):
|
|
202
|
+
return a < x and x < b
|
|
203
|
+
`,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const slopModule = await import("./python-slop.js");
|
|
208
|
+
const runner = slopModule.default;
|
|
209
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
210
|
+
|
|
211
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
212
|
+
expect(
|
|
213
|
+
result.diagnostics.some(
|
|
214
|
+
(d) =>
|
|
215
|
+
d.tool === "python-slop" &&
|
|
216
|
+
d.message.includes("chained"),
|
|
217
|
+
),
|
|
218
|
+
).toBe(true);
|
|
219
|
+
} finally {
|
|
220
|
+
safeUnlink(tmpFile);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should pass clean Python files", async () => {
|
|
225
|
+
const tmpFile = path.join(
|
|
226
|
+
process.env.TEMP || "/tmp",
|
|
227
|
+
`slop_test_ok_${Date.now()}.py`,
|
|
228
|
+
);
|
|
229
|
+
fs.writeFileSync(
|
|
230
|
+
tmpFile,
|
|
231
|
+
`# Clean Python code
|
|
232
|
+
def process_items(items):
|
|
233
|
+
"""Process items using proper Python idioms."""
|
|
234
|
+
for i, item in enumerate(items):
|
|
235
|
+
print(f"{i}: {item}")
|
|
236
|
+
|
|
237
|
+
def find_max(a, b):
|
|
238
|
+
return max(a, b)
|
|
239
|
+
|
|
240
|
+
def check_range(x, min_val, max_val):
|
|
241
|
+
return min_val < x < max_val
|
|
242
|
+
|
|
243
|
+
def convert(items):
|
|
244
|
+
return list(items)
|
|
245
|
+
`,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const slopModule = await import("./python-slop.js");
|
|
250
|
+
const runner = slopModule.default;
|
|
251
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
252
|
+
|
|
253
|
+
// Should have no slop issues
|
|
254
|
+
const slopIssues = result.diagnostics.filter(
|
|
255
|
+
(d) => d.tool === "python-slop",
|
|
256
|
+
);
|
|
257
|
+
expect(slopIssues.length).toBe(0);
|
|
258
|
+
} finally {
|
|
259
|
+
safeUnlink(tmpFile);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should categorize by weight correctly", async () => {
|
|
264
|
+
const tmpFile = path.join(
|
|
265
|
+
process.env.TEMP || "/tmp",
|
|
266
|
+
`slop_test_weight_${Date.now()}.py`,
|
|
267
|
+
);
|
|
268
|
+
fs.writeFileSync(
|
|
269
|
+
tmpFile,
|
|
270
|
+
`# Multiple slop patterns - weight 3 and weight 4
|
|
271
|
+
def bad_code(items):
|
|
272
|
+
# Weight 3: range(len)
|
|
273
|
+
for i in range(len(items)):
|
|
274
|
+
print(items[i])
|
|
275
|
+
|
|
276
|
+
# Weight 3: redundant list comprehension
|
|
277
|
+
return [x for x in items]
|
|
278
|
+
`,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const slopModule = await import("./python-slop.js");
|
|
283
|
+
const runner = slopModule.default;
|
|
284
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
285
|
+
|
|
286
|
+
// Should detect at least the range(len) pattern
|
|
287
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
288
|
+
|
|
289
|
+
// All should be warnings (weight 3)
|
|
290
|
+
const warnings = result.diagnostics.filter(
|
|
291
|
+
(d) => d.severity === "warning",
|
|
292
|
+
);
|
|
293
|
+
expect(warnings.length).toBeGreaterThanOrEqual(1);
|
|
294
|
+
} finally {
|
|
295
|
+
safeUnlink(tmpFile);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Slop runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Detects "slop" patterns in Python code:
|
|
5
|
+
* - Verbose patterns (ceremony that adds no value)
|
|
6
|
+
* - Defensive over-checking (excessive guards)
|
|
7
|
+
* - Manual reimplementation of builtins
|
|
8
|
+
* - Unnecessary object allocations
|
|
9
|
+
*
|
|
10
|
+
* Based on slop-code-bench: https://github.com/SprocketLab/slop-code-bench
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
15
|
+
import {
|
|
16
|
+
createConfigFinder,
|
|
17
|
+
isSgAvailable,
|
|
18
|
+
} from "./utils/runner-helpers.js";
|
|
19
|
+
import type {
|
|
20
|
+
Diagnostic,
|
|
21
|
+
DispatchContext,
|
|
22
|
+
RunnerDefinition,
|
|
23
|
+
RunnerResult,
|
|
24
|
+
} from "../types.js";
|
|
25
|
+
|
|
26
|
+
const findSlopConfig = createConfigFinder("python-slop-rules");
|
|
27
|
+
|
|
28
|
+
const pythonSlopRunner: RunnerDefinition = {
|
|
29
|
+
id: "python-slop",
|
|
30
|
+
appliesTo: ["python"],
|
|
31
|
+
priority: 25, // Between pyright (5) and ruff (10)
|
|
32
|
+
enabledByDefault: true,
|
|
33
|
+
skipTestFiles: true, // Slop rules can be noisy in test files
|
|
34
|
+
|
|
35
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
36
|
+
// Check if ast-grep is available
|
|
37
|
+
if (!isSgAvailable()) {
|
|
38
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find slop config
|
|
42
|
+
const configPath = findSlopConfig(ctx.cwd);
|
|
43
|
+
if (!configPath) {
|
|
44
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Run ast-grep scan
|
|
48
|
+
const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
|
|
49
|
+
|
|
50
|
+
const result = safeSpawn("npx", args, {
|
|
51
|
+
timeout: 30000,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const raw = result.stdout + result.stderr;
|
|
55
|
+
|
|
56
|
+
if (result.status === 0 && !raw.trim()) {
|
|
57
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Parse results
|
|
61
|
+
const diagnostics = parseSlopOutput(raw, ctx.filePath);
|
|
62
|
+
|
|
63
|
+
if (diagnostics.length === 0) {
|
|
64
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
status: "failed",
|
|
69
|
+
diagnostics,
|
|
70
|
+
semantic: "warning",
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function parseSlopOutput(raw: string, filePath: string): Diagnostic[] {
|
|
76
|
+
const diagnostics: Diagnostic[] = [];
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Try to parse as JSON first
|
|
80
|
+
const data = JSON.parse(raw);
|
|
81
|
+
const items = Array.isArray(data) ? data : [data];
|
|
82
|
+
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
const rule = item.rule || "slop";
|
|
85
|
+
const message = item.message || "Pattern detected";
|
|
86
|
+
const severity = item.severity || "warning";
|
|
87
|
+
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
id: `python-slop-${rule}`,
|
|
90
|
+
message,
|
|
91
|
+
filePath,
|
|
92
|
+
line: item.start?.line || 0,
|
|
93
|
+
column: item.start?.column || 0,
|
|
94
|
+
severity: severity === "error" ? "error" : "warning",
|
|
95
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
96
|
+
tool: "python-slop",
|
|
97
|
+
rule,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Not JSON, try line-by-line parsing
|
|
102
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
// Try to extract line numbers from typical output formats
|
|
105
|
+
const match = line.match(/:(\d+):/);
|
|
106
|
+
if (match) {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
id: "python-slop-pattern",
|
|
109
|
+
message: line.trim(),
|
|
110
|
+
filePath,
|
|
111
|
+
line: parseInt(match[1], 10),
|
|
112
|
+
column: 0,
|
|
113
|
+
severity: "warning",
|
|
114
|
+
semantic: "warning",
|
|
115
|
+
tool: "python-slop",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return diagnostics;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default pythonSlopRunner;
|
|
@@ -4,63 +4,33 @@
|
|
|
4
4
|
* Ruff handles both formatting and linting for Python files.
|
|
5
5
|
* Supports venv-local installations.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import * as path from "node:path";
|
|
7
|
+
import { ensureTool } from "../../installer/index.js";
|
|
8
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
10
9
|
import { stripAnsi } from "../../sanitize.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Find ruff command, checking venv first, then global.
|
|
16
|
-
*/
|
|
17
|
-
function findRuffCommand(cwd) {
|
|
18
|
-
const venvPaths = [
|
|
19
|
-
".venv/bin/ruff",
|
|
20
|
-
"venv/bin/ruff",
|
|
21
|
-
".venv/Scripts/ruff.exe",
|
|
22
|
-
"venv/Scripts/ruff.exe",
|
|
23
|
-
];
|
|
24
|
-
for (const venvPath of venvPaths) {
|
|
25
|
-
const fullPath = path.join(cwd, venvPath);
|
|
26
|
-
if (fs.existsSync(fullPath)) {
|
|
27
|
-
return `"${fullPath}"`;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return "ruff";
|
|
31
|
-
}
|
|
32
|
-
function isRuffAvailable(cwd) {
|
|
33
|
-
if (ruffAvailable !== null)
|
|
34
|
-
return ruffAvailable;
|
|
35
|
-
const command = findRuffCommand(cwd || process.cwd());
|
|
36
|
-
const check = spawnSync(command, ["--version"], {
|
|
37
|
-
encoding: "utf-8",
|
|
38
|
-
timeout: 5000,
|
|
39
|
-
shell: true,
|
|
40
|
-
});
|
|
41
|
-
ruffAvailable = !check.error && check.status === 0;
|
|
42
|
-
if (ruffAvailable)
|
|
43
|
-
ruffCommand = command;
|
|
44
|
-
return ruffAvailable;
|
|
45
|
-
}
|
|
10
|
+
import { parseRuffOutput } from "./utils/diagnostic-parsers.js";
|
|
11
|
+
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
12
|
+
const ruff = createAvailabilityChecker("ruff", ".exe");
|
|
46
13
|
const ruffRunner = {
|
|
47
14
|
id: "ruff-lint",
|
|
48
15
|
appliesTo: ["python"],
|
|
49
16
|
priority: 10,
|
|
50
17
|
enabledByDefault: true,
|
|
51
18
|
async run(ctx) {
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
19
|
+
const cwd = ctx.cwd || process.cwd();
|
|
20
|
+
// Auto-install ruff if not available (it's one of the 4 auto-install tools)
|
|
21
|
+
if (!ruff.isAvailable(cwd)) {
|
|
22
|
+
const installed = await ensureTool("ruff");
|
|
23
|
+
if (!installed) {
|
|
24
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
25
|
+
}
|
|
55
26
|
}
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
27
|
+
// IMPORTANT: Never use --fix in dispatch runner to prevent infinite loops.
|
|
28
|
+
// Writing to the file would trigger another tool_result event, which would
|
|
29
|
+
// call dispatchLint again, creating a feedback loop.
|
|
30
|
+
// Fixes should be applied through explicit commands or user edits.
|
|
31
|
+
const args = ["check", ctx.filePath];
|
|
32
|
+
const result = safeSpawn(ruff.getCommand(), args, {
|
|
62
33
|
timeout: 30000,
|
|
63
|
-
shell: true,
|
|
64
34
|
});
|
|
65
35
|
const raw = stripAnsi(result.stdout + result.stderr);
|
|
66
36
|
if (result.status === 0) {
|
|
@@ -75,27 +45,4 @@ const ruffRunner = {
|
|
|
75
45
|
};
|
|
76
46
|
},
|
|
77
47
|
};
|
|
78
|
-
function parseRuffOutput(raw, filePath) {
|
|
79
|
-
const lines = raw.split("\n").filter((l) => l.trim());
|
|
80
|
-
const diagnostics = [];
|
|
81
|
-
for (const line of lines) {
|
|
82
|
-
// Parse ruff output: file:line:col: message (code)
|
|
83
|
-
const match = line.match(/^(.+?):(\d+):(\d+):\s*(.+?)\s+\((.+?)\)/);
|
|
84
|
-
if (match) {
|
|
85
|
-
diagnostics.push({
|
|
86
|
-
id: `ruff-${match[2]}-${match[5]}`,
|
|
87
|
-
message: `${match[5]}: ${match[4]}`,
|
|
88
|
-
filePath,
|
|
89
|
-
line: parseInt(match[2], 10),
|
|
90
|
-
column: parseInt(match[3], 10),
|
|
91
|
-
severity: line.includes("error") ? "error" : "warning",
|
|
92
|
-
semantic: "warning",
|
|
93
|
-
tool: "ruff",
|
|
94
|
-
rule: match[5],
|
|
95
|
-
fixable: true,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return diagnostics;
|
|
100
|
-
}
|
|
101
48
|
export default ruffRunner;
|
|
@@ -5,56 +5,18 @@
|
|
|
5
5
|
* Supports venv-local installations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import * as path from "node:path";
|
|
8
|
+
import { ensureTool } from "../../installer/index.js";
|
|
9
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
11
10
|
import { stripAnsi } from "../../sanitize.js";
|
|
12
11
|
import type {
|
|
13
|
-
Diagnostic,
|
|
14
12
|
DispatchContext,
|
|
15
13
|
RunnerDefinition,
|
|
16
14
|
RunnerResult,
|
|
17
15
|
} from "../types.js";
|
|
16
|
+
import { parseRuffOutput } from "./utils/diagnostic-parsers.js";
|
|
17
|
+
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
let ruffAvailable: boolean | null = null;
|
|
21
|
-
let ruffCommand: string | null = null;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Find ruff command, checking venv first, then global.
|
|
25
|
-
*/
|
|
26
|
-
function findRuffCommand(cwd: string): string {
|
|
27
|
-
const venvPaths = [
|
|
28
|
-
".venv/bin/ruff",
|
|
29
|
-
"venv/bin/ruff",
|
|
30
|
-
".venv/Scripts/ruff.exe",
|
|
31
|
-
"venv/Scripts/ruff.exe",
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const venvPath of venvPaths) {
|
|
35
|
-
const fullPath = path.join(cwd, venvPath);
|
|
36
|
-
if (fs.existsSync(fullPath)) {
|
|
37
|
-
return `"${fullPath}"`;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return "ruff";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isRuffAvailable(cwd?: string): boolean {
|
|
45
|
-
if (ruffAvailable !== null) return ruffAvailable;
|
|
46
|
-
|
|
47
|
-
const command = findRuffCommand(cwd || process.cwd());
|
|
48
|
-
const check = spawnSync(command, ["--version"], {
|
|
49
|
-
encoding: "utf-8",
|
|
50
|
-
timeout: 5000,
|
|
51
|
-
shell: true,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
ruffAvailable = !check.error && check.status === 0;
|
|
55
|
-
if (ruffAvailable) ruffCommand = command;
|
|
56
|
-
return ruffAvailable;
|
|
57
|
-
}
|
|
19
|
+
const ruff = createAvailabilityChecker("ruff", ".exe");
|
|
58
20
|
|
|
59
21
|
const ruffRunner: RunnerDefinition = {
|
|
60
22
|
id: "ruff-lint",
|
|
@@ -63,20 +25,24 @@ const ruffRunner: RunnerDefinition = {
|
|
|
63
25
|
enabledByDefault: true,
|
|
64
26
|
|
|
65
27
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
28
|
+
const cwd = ctx.cwd || process.cwd();
|
|
29
|
+
|
|
30
|
+
// Auto-install ruff if not available (it's one of the 4 auto-install tools)
|
|
31
|
+
if (!ruff.isAvailable(cwd)) {
|
|
32
|
+
const installed = await ensureTool("ruff");
|
|
33
|
+
if (!installed) {
|
|
34
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
35
|
+
}
|
|
69
36
|
}
|
|
70
37
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
38
|
+
// IMPORTANT: Never use --fix in dispatch runner to prevent infinite loops.
|
|
39
|
+
// Writing to the file would trigger another tool_result event, which would
|
|
40
|
+
// call dispatchLint again, creating a feedback loop.
|
|
41
|
+
// Fixes should be applied through explicit commands or user edits.
|
|
42
|
+
const args = ["check", ctx.filePath];
|
|
75
43
|
|
|
76
|
-
const result =
|
|
77
|
-
encoding: "utf-8",
|
|
44
|
+
const result = safeSpawn(ruff.getCommand()!, args, {
|
|
78
45
|
timeout: 30000,
|
|
79
|
-
shell: true,
|
|
80
46
|
});
|
|
81
47
|
|
|
82
48
|
const raw = stripAnsi(result.stdout + result.stderr);
|
|
@@ -96,30 +62,4 @@ const ruffRunner: RunnerDefinition = {
|
|
|
96
62
|
},
|
|
97
63
|
};
|
|
98
64
|
|
|
99
|
-
function parseRuffOutput(raw: string, filePath: string): Diagnostic[] {
|
|
100
|
-
const lines = raw.split("\n").filter((l) => l.trim());
|
|
101
|
-
const diagnostics: Diagnostic[] = [];
|
|
102
|
-
|
|
103
|
-
for (const line of lines) {
|
|
104
|
-
// Parse ruff output: file:line:col: message (code)
|
|
105
|
-
const match = line.match(/^(.+?):(\d+):(\d+):\s*(.+?)\s+\((.+?)\)/);
|
|
106
|
-
if (match) {
|
|
107
|
-
diagnostics.push({
|
|
108
|
-
id: `ruff-${match[2]}-${match[5]}`,
|
|
109
|
-
message: `${match[5]}: ${match[4]}`,
|
|
110
|
-
filePath,
|
|
111
|
-
line: parseInt(match[2], 10),
|
|
112
|
-
column: parseInt(match[3], 10),
|
|
113
|
-
severity: line.includes("error") ? "error" : "warning",
|
|
114
|
-
semantic: "warning",
|
|
115
|
-
tool: "ruff",
|
|
116
|
-
rule: match[5],
|
|
117
|
-
fixable: true,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return diagnostics;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
65
|
export default ruffRunner;
|