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
|
@@ -7,58 +7,17 @@
|
|
|
7
7
|
* Requires: pyright (pip install pyright or npm install -g pyright)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import * as path from "node:path";
|
|
10
|
+
import { ensureTool } from "../../installer/index.js";
|
|
11
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
13
12
|
import type {
|
|
14
13
|
Diagnostic,
|
|
15
14
|
DispatchContext,
|
|
16
15
|
RunnerDefinition,
|
|
17
16
|
RunnerResult,
|
|
18
17
|
} from "../types.js";
|
|
18
|
+
import { createAvailabilityChecker } from "./utils/runner-helpers.js";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
let pyrightAvailable: boolean | null = null;
|
|
22
|
-
let pyrightCommand: string | null = null;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Find pyright command, checking venv first, then global.
|
|
26
|
-
* Looks in .venv/bin, venv/bin (Unix), .venv/Scripts, venv/Scripts (Windows)
|
|
27
|
-
*/
|
|
28
|
-
function findPyrightCommand(cwd: string): string {
|
|
29
|
-
// Check common venv locations
|
|
30
|
-
const venvPaths = [
|
|
31
|
-
".venv/bin/pyright",
|
|
32
|
-
"venv/bin/pyright",
|
|
33
|
-
".venv/Scripts/pyright.exe",
|
|
34
|
-
"venv/Scripts/pyright.exe",
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
for (const venvPath of venvPaths) {
|
|
38
|
-
const fullPath = path.join(cwd, venvPath);
|
|
39
|
-
if (fs.existsSync(fullPath)) {
|
|
40
|
-
return `"${fullPath}"`; // Quote for Windows paths with spaces
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Fall back to global
|
|
45
|
-
return "pyright";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isPyrightAvailable(cwd?: string): boolean {
|
|
49
|
-
if (pyrightAvailable !== null) return pyrightAvailable;
|
|
50
|
-
|
|
51
|
-
const command = findPyrightCommand(cwd || process.cwd());
|
|
52
|
-
|
|
53
|
-
const check = spawnSync(command, ["--version"], {
|
|
54
|
-
encoding: "utf-8",
|
|
55
|
-
timeout: 5000,
|
|
56
|
-
shell: true,
|
|
57
|
-
});
|
|
58
|
-
pyrightAvailable = !check.error && check.status === 0;
|
|
59
|
-
if (pyrightAvailable) pyrightCommand = command;
|
|
60
|
-
return pyrightAvailable;
|
|
61
|
-
}
|
|
20
|
+
const pyright = createAvailabilityChecker("pyright", ".exe");
|
|
62
21
|
|
|
63
22
|
const pyrightRunner: RunnerDefinition = {
|
|
64
23
|
id: "pyright",
|
|
@@ -67,17 +26,24 @@ const pyrightRunner: RunnerDefinition = {
|
|
|
67
26
|
enabledByDefault: true,
|
|
68
27
|
|
|
69
28
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
29
|
+
const cwd = ctx.cwd || process.cwd();
|
|
30
|
+
|
|
31
|
+
// Auto-install pyright if not available (it's one of the 4 auto-install tools)
|
|
32
|
+
if (!pyright.isAvailable(cwd)) {
|
|
33
|
+
const installed = await ensureTool("pyright");
|
|
34
|
+
if (!installed) {
|
|
35
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
36
|
+
}
|
|
73
37
|
}
|
|
74
38
|
|
|
75
39
|
// Run pyright with JSON output (use venv-local or global command)
|
|
76
|
-
const result =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
40
|
+
const result = safeSpawn(
|
|
41
|
+
pyright.getCommand()!,
|
|
42
|
+
["--outputjson", ctx.filePath],
|
|
43
|
+
{
|
|
44
|
+
timeout: 60000,
|
|
45
|
+
},
|
|
46
|
+
);
|
|
81
47
|
|
|
82
48
|
// Pyright returns non-zero when errors found, that's OK
|
|
83
49
|
if (result.error) {
|
|
@@ -102,53 +68,44 @@ const pyrightRunner: RunnerDefinition = {
|
|
|
102
68
|
return {
|
|
103
69
|
status: hasErrors ? "failed" : "succeeded",
|
|
104
70
|
diagnostics,
|
|
105
|
-
semantic: "warning",
|
|
71
|
+
semantic: hasErrors ? "blocking" : "warning",
|
|
106
72
|
};
|
|
107
73
|
} catch {
|
|
108
|
-
// JSON parse
|
|
109
|
-
return {
|
|
74
|
+
// JSON parse error
|
|
75
|
+
return {
|
|
76
|
+
status: "failed",
|
|
77
|
+
diagnostics: [],
|
|
78
|
+
semantic: "none",
|
|
79
|
+
rawOutput: output.slice(0, 500),
|
|
80
|
+
};
|
|
110
81
|
}
|
|
111
82
|
},
|
|
112
83
|
};
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
severity: "error" | "warning" | "information";
|
|
117
|
-
message: string;
|
|
118
|
-
range: {
|
|
119
|
-
start: { line: number; character: number };
|
|
120
|
-
end: { line: number; character: number };
|
|
121
|
-
};
|
|
122
|
-
rule: string;
|
|
123
|
-
}
|
|
85
|
+
function parsePyrightOutput(data: any, _filePath: string): Diagnostic[] {
|
|
86
|
+
const diagnostics: Diagnostic[] = [];
|
|
124
87
|
|
|
125
|
-
|
|
126
|
-
generalDiagnostics
|
|
127
|
-
|
|
88
|
+
// Pyright JSON output has generalDiagnostics array
|
|
89
|
+
const generalDiags = data.generalDiagnostics || [];
|
|
90
|
+
|
|
91
|
+
for (const diag of generalDiags) {
|
|
92
|
+
// Skip if not for this file (pyright may output diagnostics for imports)
|
|
93
|
+
// For now, include all - caller will filter if needed
|
|
128
94
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Only include errors and warnings, skip informational
|
|
138
|
-
return d.severity === "error" || d.severity === "warning";
|
|
139
|
-
})
|
|
140
|
-
.map((d) => ({
|
|
141
|
-
id: `pyright-${d.range.start.line}-${d.rule}`,
|
|
142
|
-
message: d.message.split("\n")[0], // First line only (pyright has multi-line messages)
|
|
143
|
-
filePath,
|
|
144
|
-
line: d.range.start.line + 1, // Pyright is 0-indexed, we're 1-indexed
|
|
145
|
-
column: d.range.start.character + 1,
|
|
146
|
-
severity: d.severity === "error" ? "error" : "warning",
|
|
147
|
-
semantic: d.severity === "error" ? "blocking" : "warning",
|
|
95
|
+
diagnostics.push({
|
|
96
|
+
id: `pyright-${diag.rule || diag.start?.line || "unknown"}`,
|
|
97
|
+
message: diag.message || "Type error",
|
|
98
|
+
filePath: diag.file || _filePath,
|
|
99
|
+
line: diag.start?.line || 0,
|
|
100
|
+
column: diag.start?.column || 0,
|
|
101
|
+
severity: diag.severity === "error" ? "error" : "warning",
|
|
102
|
+
semantic: diag.severity === "error" ? "blocking" : "warning",
|
|
148
103
|
tool: "pyright",
|
|
149
|
-
rule:
|
|
150
|
-
|
|
151
|
-
|
|
104
|
+
rule: diag.rule,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return diagnostics;
|
|
152
109
|
}
|
|
153
110
|
|
|
154
111
|
export default pyrightRunner;
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
import { safeSpawn } from "../../safe-spawn.js";
|
|
13
|
+
import { createConfigFinder, isSgAvailable, } from "./utils/runner-helpers.js";
|
|
14
|
+
const findSlopConfig = createConfigFinder("python-slop-rules");
|
|
15
|
+
const pythonSlopRunner = {
|
|
16
|
+
id: "python-slop",
|
|
17
|
+
appliesTo: ["python"],
|
|
18
|
+
priority: 25, // Between pyright (5) and ruff (10)
|
|
19
|
+
enabledByDefault: true,
|
|
20
|
+
skipTestFiles: true, // Slop rules can be noisy in test files
|
|
21
|
+
async run(ctx) {
|
|
22
|
+
// Check if ast-grep is available
|
|
23
|
+
if (!isSgAvailable()) {
|
|
24
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
25
|
+
}
|
|
26
|
+
// Find slop config
|
|
27
|
+
const configPath = findSlopConfig(ctx.cwd);
|
|
28
|
+
if (!configPath) {
|
|
29
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
30
|
+
}
|
|
31
|
+
// Run ast-grep scan
|
|
32
|
+
const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
|
|
33
|
+
const result = safeSpawn("npx", args, {
|
|
34
|
+
timeout: 30000,
|
|
35
|
+
});
|
|
36
|
+
const raw = result.stdout + result.stderr;
|
|
37
|
+
if (result.status === 0 && !raw.trim()) {
|
|
38
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
39
|
+
}
|
|
40
|
+
// Parse results
|
|
41
|
+
const diagnostics = parseSlopOutput(raw, ctx.filePath);
|
|
42
|
+
if (diagnostics.length === 0) {
|
|
43
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
status: "failed",
|
|
47
|
+
diagnostics,
|
|
48
|
+
semantic: "warning",
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
function parseSlopOutput(raw, filePath) {
|
|
53
|
+
const diagnostics = [];
|
|
54
|
+
try {
|
|
55
|
+
// Try to parse as JSON first
|
|
56
|
+
const data = JSON.parse(raw);
|
|
57
|
+
const items = Array.isArray(data) ? data : [data];
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
const rule = item.rule || "slop";
|
|
60
|
+
const message = item.message || "Pattern detected";
|
|
61
|
+
const severity = item.severity || "warning";
|
|
62
|
+
diagnostics.push({
|
|
63
|
+
id: `python-slop-${rule}`,
|
|
64
|
+
message,
|
|
65
|
+
filePath,
|
|
66
|
+
line: item.start?.line || 0,
|
|
67
|
+
column: item.start?.column || 0,
|
|
68
|
+
severity: severity === "error" ? "error" : "warning",
|
|
69
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
70
|
+
tool: "python-slop",
|
|
71
|
+
rule,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Not JSON, try line-by-line parsing
|
|
77
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
// Try to extract line numbers from typical output formats
|
|
80
|
+
const match = line.match(/:(\d+):/);
|
|
81
|
+
if (match) {
|
|
82
|
+
diagnostics.push({
|
|
83
|
+
id: "python-slop-pattern",
|
|
84
|
+
message: line.trim(),
|
|
85
|
+
filePath,
|
|
86
|
+
line: parseInt(match[1], 10),
|
|
87
|
+
column: 0,
|
|
88
|
+
severity: "warning",
|
|
89
|
+
semantic: "warning",
|
|
90
|
+
tool: "python-slop",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return diagnostics;
|
|
96
|
+
}
|
|
97
|
+
export default pythonSlopRunner;
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
function createMockContext(filePath) {
|
|
6
|
+
return {
|
|
7
|
+
filePath,
|
|
8
|
+
cwd: process.cwd(),
|
|
9
|
+
kind: "python",
|
|
10
|
+
autofix: false,
|
|
11
|
+
deltaMode: false,
|
|
12
|
+
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
13
|
+
pi: {},
|
|
14
|
+
hasTool: async () => false,
|
|
15
|
+
log: () => { },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Helper for safe file cleanup
|
|
19
|
+
function safeUnlink(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(filePath)) {
|
|
22
|
+
fs.unlinkSync(filePath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Ignore cleanup errors on Windows
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
describe("python-slop runner", () => {
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
it("should have correct runner definition", async () => {
|
|
32
|
+
const slopModule = await import("./python-slop.js");
|
|
33
|
+
const runner = slopModule.default;
|
|
34
|
+
expect(runner.id).toBe("python-slop");
|
|
35
|
+
expect(runner.appliesTo).toEqual(["python"]);
|
|
36
|
+
expect(runner.priority).toBe(25);
|
|
37
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
38
|
+
expect(runner.skipTestFiles).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it("should detect ast-grep availability", () => {
|
|
41
|
+
const { spawnSync } = require("node:child_process");
|
|
42
|
+
const result = spawnSync("npx", ["sg", "--version"], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
timeout: 10000,
|
|
45
|
+
shell: true,
|
|
46
|
+
});
|
|
47
|
+
expect(result.error || result.status !== 0 ? "not available" : "available").toBe("available");
|
|
48
|
+
});
|
|
49
|
+
it("should detect verbose range-len pattern", async () => {
|
|
50
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_range_${Date.now()}.py`);
|
|
51
|
+
fs.writeFileSync(tmpFile, `# Slop: using range(len()) instead of enumerate
|
|
52
|
+
def process_items(items):
|
|
53
|
+
for i in range(len(items)):
|
|
54
|
+
print(items[i])
|
|
55
|
+
`);
|
|
56
|
+
try {
|
|
57
|
+
const slopModule = await import("./python-slop.js");
|
|
58
|
+
const runner = slopModule.default;
|
|
59
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
60
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
61
|
+
expect(result.diagnostics.some((d) => d.tool === "python-slop" &&
|
|
62
|
+
d.message.includes("range(len") &&
|
|
63
|
+
d.message.includes("enumerate"))).toBe(true);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
safeUnlink(tmpFile);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("should detect manual min/max pattern", async () => {
|
|
70
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_minmax_${Date.now()}.py`);
|
|
71
|
+
fs.writeFileSync(tmpFile, `# Slop: manual min/max instead of built-in
|
|
72
|
+
def find_max(a, b):
|
|
73
|
+
if a > b:
|
|
74
|
+
m = a
|
|
75
|
+
else:
|
|
76
|
+
m = b
|
|
77
|
+
return m
|
|
78
|
+
`);
|
|
79
|
+
try {
|
|
80
|
+
const slopModule = await import("./python-slop.js");
|
|
81
|
+
const runner = slopModule.default;
|
|
82
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
83
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
84
|
+
expect(result.diagnostics.some((d) => d.tool === "python-slop" &&
|
|
85
|
+
(d.message.includes("min") || d.message.includes("max")))).toBe(true);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
safeUnlink(tmpFile);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it("should detect defensive None guard", async () => {
|
|
92
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_guard_${Date.now()}.py`);
|
|
93
|
+
fs.writeFileSync(tmpFile, `# Slop: defensive None guard
|
|
94
|
+
def process(data):
|
|
95
|
+
if data is None:
|
|
96
|
+
return None
|
|
97
|
+
return data.upper()
|
|
98
|
+
`);
|
|
99
|
+
try {
|
|
100
|
+
const slopModule = await import("./python-slop.js");
|
|
101
|
+
const runner = slopModule.default;
|
|
102
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
103
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
104
|
+
expect(result.diagnostics.some((d) => d.tool === "python-slop" &&
|
|
105
|
+
(d.message.includes("defensive") ||
|
|
106
|
+
d.message.includes("guard")))).toBe(true);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
safeUnlink(tmpFile);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
it("should detect list comprehension ceremony", async () => {
|
|
113
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_list_${Date.now()}.py`);
|
|
114
|
+
fs.writeFileSync(tmpFile, `# Slop: redundant list comprehension
|
|
115
|
+
def convert(items):
|
|
116
|
+
return [x for x in items]
|
|
117
|
+
`);
|
|
118
|
+
try {
|
|
119
|
+
const slopModule = await import("./python-slop.js");
|
|
120
|
+
const runner = slopModule.default;
|
|
121
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
122
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
123
|
+
expect(result.diagnostics.some((d) => d.tool === "python-slop" &&
|
|
124
|
+
d.message.includes("list") &&
|
|
125
|
+
d.message.includes("unnecessary"))).toBe(true);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
safeUnlink(tmpFile);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
it("should detect chained comparison opportunity", async () => {
|
|
132
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_chain_${Date.now()}.py`);
|
|
133
|
+
fs.writeFileSync(tmpFile, `# Slop: could use chained comparison
|
|
134
|
+
def check_range(x, a, b):
|
|
135
|
+
return a < x and x < b
|
|
136
|
+
`);
|
|
137
|
+
try {
|
|
138
|
+
const slopModule = await import("./python-slop.js");
|
|
139
|
+
const runner = slopModule.default;
|
|
140
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
141
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
142
|
+
expect(result.diagnostics.some((d) => d.tool === "python-slop" &&
|
|
143
|
+
d.message.includes("chained"))).toBe(true);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
safeUnlink(tmpFile);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
it("should pass clean Python files", async () => {
|
|
150
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_ok_${Date.now()}.py`);
|
|
151
|
+
fs.writeFileSync(tmpFile, `# Clean Python code
|
|
152
|
+
def process_items(items):
|
|
153
|
+
"""Process items using proper Python idioms."""
|
|
154
|
+
for i, item in enumerate(items):
|
|
155
|
+
print(f"{i}: {item}")
|
|
156
|
+
|
|
157
|
+
def find_max(a, b):
|
|
158
|
+
return max(a, b)
|
|
159
|
+
|
|
160
|
+
def check_range(x, min_val, max_val):
|
|
161
|
+
return min_val < x < max_val
|
|
162
|
+
|
|
163
|
+
def convert(items):
|
|
164
|
+
return list(items)
|
|
165
|
+
`);
|
|
166
|
+
try {
|
|
167
|
+
const slopModule = await import("./python-slop.js");
|
|
168
|
+
const runner = slopModule.default;
|
|
169
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
170
|
+
// Should have no slop issues
|
|
171
|
+
const slopIssues = result.diagnostics.filter((d) => d.tool === "python-slop");
|
|
172
|
+
expect(slopIssues.length).toBe(0);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
safeUnlink(tmpFile);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
it("should categorize by weight correctly", async () => {
|
|
179
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `slop_test_weight_${Date.now()}.py`);
|
|
180
|
+
fs.writeFileSync(tmpFile, `# Multiple slop patterns - weight 3 and weight 4
|
|
181
|
+
def bad_code(items):
|
|
182
|
+
# Weight 3: range(len)
|
|
183
|
+
for i in range(len(items)):
|
|
184
|
+
print(items[i])
|
|
185
|
+
|
|
186
|
+
# Weight 3: redundant list comprehension
|
|
187
|
+
return [x for x in items]
|
|
188
|
+
`);
|
|
189
|
+
try {
|
|
190
|
+
const slopModule = await import("./python-slop.js");
|
|
191
|
+
const runner = slopModule.default;
|
|
192
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
193
|
+
// Should detect at least the range(len) pattern
|
|
194
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
195
|
+
// All should be warnings (weight 3)
|
|
196
|
+
const warnings = result.diagnostics.filter((d) => d.severity === "warning");
|
|
197
|
+
expect(warnings.length).toBeGreaterThanOrEqual(1);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
safeUnlink(tmpFile);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|