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,230 @@
|
|
|
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: "jsts" 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("ts-slop runner", () => {
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
it("should have correct runner definition", async () => {
|
|
36
|
+
const slopModule = await import("./ts-slop.js");
|
|
37
|
+
const runner = slopModule.default;
|
|
38
|
+
|
|
39
|
+
expect(runner.id).toBe("ts-slop");
|
|
40
|
+
// NOTE: TS/JS slop is now handled by ast-grep-napi
|
|
41
|
+
// This CLI runner is disabled by default as fallback
|
|
42
|
+
expect(runner.appliesTo).toEqual([]); // Disabled - use ast-grep-napi
|
|
43
|
+
expect(runner.priority).toBe(20);
|
|
44
|
+
expect(runner.enabledByDefault).toBe(false);
|
|
45
|
+
expect(runner.skipTestFiles).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should detect ast-grep availability", () => {
|
|
49
|
+
const { spawnSync } =
|
|
50
|
+
require("node:child_process") as typeof import("node:child_process");
|
|
51
|
+
const result = spawnSync("npx", ["sg", "--version"], {
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
timeout: 10000,
|
|
54
|
+
shell: true,
|
|
55
|
+
});
|
|
56
|
+
expect(
|
|
57
|
+
result.error || result.status !== 0 ? "not available" : "available",
|
|
58
|
+
).toBe("available");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should detect for-index-length pattern (or other slop)", async () => {
|
|
62
|
+
const tmpFile = path.join(
|
|
63
|
+
process.env.TEMP || "/tmp",
|
|
64
|
+
`ts_slop_test_for_${Date.now()}.ts`,
|
|
65
|
+
);
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
tmpFile,
|
|
68
|
+
`// Slop: using index loop instead of for-of
|
|
69
|
+
function processItems(items: string[]) {
|
|
70
|
+
for (let i = 0; i < items.length; i++) {
|
|
71
|
+
console.log(items[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const slopModule = await import("./ts-slop.js");
|
|
79
|
+
const runner = slopModule.default;
|
|
80
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
81
|
+
|
|
82
|
+
// Should detect at least some slop patterns
|
|
83
|
+
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
84
|
+
expect(result.status).not.toBe("skipped");
|
|
85
|
+
} finally {
|
|
86
|
+
safeUnlink(tmpFile);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should detect manual Math min/max pattern (or other slop)", async () => {
|
|
91
|
+
const tmpFile = path.join(
|
|
92
|
+
process.env.TEMP || "/tmp",
|
|
93
|
+
`ts_slop_test_minmax_${Date.now()}.ts`,
|
|
94
|
+
);
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
tmpFile,
|
|
97
|
+
`// Slop: manual min/max instead of Math
|
|
98
|
+
function getMax(a: number, b: number): number {
|
|
99
|
+
if (a > b) {
|
|
100
|
+
const m = a;
|
|
101
|
+
} else {
|
|
102
|
+
const m = b;
|
|
103
|
+
}
|
|
104
|
+
return m;
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const slopModule = await import("./ts-slop.js");
|
|
111
|
+
const runner = slopModule.default;
|
|
112
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
113
|
+
|
|
114
|
+
// Should detect at least some slop patterns
|
|
115
|
+
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
116
|
+
expect(result.status).not.toBe("skipped");
|
|
117
|
+
} finally {
|
|
118
|
+
safeUnlink(tmpFile);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should detect indexOf !== -1 pattern (or other slop)", async () => {
|
|
123
|
+
const tmpFile = path.join(
|
|
124
|
+
process.env.TEMP || "/tmp",
|
|
125
|
+
`ts_slop_test_indexof_${Date.now()}.ts`,
|
|
126
|
+
);
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
tmpFile,
|
|
129
|
+
`// Slop: indexOf check instead of includes
|
|
130
|
+
function hasItem(arr: string[], item: string): boolean {
|
|
131
|
+
if (arr.indexOf(item) !== -1) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const slopModule = await import("./ts-slop.js");
|
|
141
|
+
const runner = slopModule.default;
|
|
142
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
143
|
+
|
|
144
|
+
// Should detect at least some slop patterns
|
|
145
|
+
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
146
|
+
expect(result.status).not.toBe("skipped");
|
|
147
|
+
} finally {
|
|
148
|
+
safeUnlink(tmpFile);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should detect array length > 0 pattern", async () => {
|
|
153
|
+
const tmpFile = path.join(
|
|
154
|
+
process.env.TEMP || "/tmp",
|
|
155
|
+
`ts_slop_test_length_${Date.now()}.ts`,
|
|
156
|
+
);
|
|
157
|
+
fs.writeFileSync(
|
|
158
|
+
tmpFile,
|
|
159
|
+
`// Slop: length check instead of truthiness
|
|
160
|
+
function processItems(arr: string[]): void {
|
|
161
|
+
if (arr.length > 0) {
|
|
162
|
+
console.log("has items");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const slopModule = await import("./ts-slop.js");
|
|
170
|
+
const runner = slopModule.default;
|
|
171
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
172
|
+
|
|
173
|
+
// This pattern may or may not be detected depending on rule specificity
|
|
174
|
+
// Just verify the scan ran without errors
|
|
175
|
+
expect(result.status).toBe("succeeded");
|
|
176
|
+
} finally {
|
|
177
|
+
safeUnlink(tmpFile);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should pass clean TypeScript files", async () => {
|
|
182
|
+
const tmpFile = path.join(
|
|
183
|
+
process.env.TEMP || "/tmp",
|
|
184
|
+
`ts_slop_test_ok_${Date.now()}.ts`,
|
|
185
|
+
);
|
|
186
|
+
fs.writeFileSync(
|
|
187
|
+
tmpFile,
|
|
188
|
+
`// Clean TypeScript code
|
|
189
|
+
function processItems(items: string[]): void {
|
|
190
|
+
for (const item of items) {
|
|
191
|
+
console.log(item);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function findMax(a: number, b: number): number {
|
|
196
|
+
return Math.max(a, b);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function contains(arr: string[], item: string): boolean {
|
|
200
|
+
return arr.includes(item);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function hasItems(arr: string[]): boolean {
|
|
204
|
+
return arr.length > 0; // This is actually OK, but let's see
|
|
205
|
+
}
|
|
206
|
+
`,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const slopModule = await import("./ts-slop.js");
|
|
211
|
+
const runner = slopModule.default;
|
|
212
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
213
|
+
|
|
214
|
+
// Should have minimal or no slop issues for clean code
|
|
215
|
+
const slopIssues = result.diagnostics.filter(
|
|
216
|
+
(d) => d.tool === "ts-slop",
|
|
217
|
+
);
|
|
218
|
+
// Allow for minor issues - the length check might still trigger
|
|
219
|
+
expect(slopIssues.length).toBeLessThanOrEqual(1);
|
|
220
|
+
} finally {
|
|
221
|
+
try {
|
|
222
|
+
if (fs.existsSync(tmpFile)) {
|
|
223
|
+
safeUnlink(tmpFile);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Ignore cleanup errors
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript Slop runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Detects "slop" patterns in TypeScript/JavaScript 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 patterns adapted for TypeScript
|
|
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("ts-slop-rules");
|
|
27
|
+
|
|
28
|
+
const tsSlopRunner: RunnerDefinition = {
|
|
29
|
+
id: "ts-slop",
|
|
30
|
+
// NOTE: TypeScript/JavaScript slop detection is now handled by ast-grep-napi
|
|
31
|
+
// This CLI runner is kept as fallback for edge cases but disabled by default
|
|
32
|
+
appliesTo: [], // Disabled - use ast-grep-napi instead
|
|
33
|
+
priority: 20,
|
|
34
|
+
enabledByDefault: false,
|
|
35
|
+
skipTestFiles: true,
|
|
36
|
+
|
|
37
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
38
|
+
// Check if ast-grep is available
|
|
39
|
+
if (!isSgAvailable()) {
|
|
40
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Find slop config
|
|
44
|
+
const configPath = findSlopConfig(ctx.cwd);
|
|
45
|
+
if (!configPath) {
|
|
46
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Run ast-grep scan
|
|
50
|
+
const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
|
|
51
|
+
|
|
52
|
+
const result = safeSpawn("npx", args, {
|
|
53
|
+
timeout: 30000,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const raw = result.stdout + result.stderr;
|
|
57
|
+
|
|
58
|
+
if (result.status === 0 && !raw.trim()) {
|
|
59
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse results
|
|
63
|
+
const diagnostics = parseSlopOutput(raw, ctx.filePath);
|
|
64
|
+
|
|
65
|
+
if (diagnostics.length === 0) {
|
|
66
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: "failed",
|
|
71
|
+
diagnostics,
|
|
72
|
+
semantic: "warning",
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function parseSlopOutput(raw: string, filePath: string): Diagnostic[] {
|
|
78
|
+
const diagnostics: Diagnostic[] = [];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
if (Array.isArray(parsed)) {
|
|
83
|
+
for (const item of parsed) {
|
|
84
|
+
const line = item.range?.start?.line || 1;
|
|
85
|
+
const ruleId = item.rule || "unknown";
|
|
86
|
+
const message = item.message || "";
|
|
87
|
+
|
|
88
|
+
// Categorize by severity based on weight from metadata
|
|
89
|
+
const weight = item.metadata?.weight || 3;
|
|
90
|
+
const severity = weight >= 4 ? "error" : "warning";
|
|
91
|
+
const category = item.metadata?.category || "slop";
|
|
92
|
+
|
|
93
|
+
// Add slop category indicator to message
|
|
94
|
+
let enhancedMessage = `[${category}] ${message}`;
|
|
95
|
+
if (item.replacement) {
|
|
96
|
+
const preview =
|
|
97
|
+
item.replacement.length > 40
|
|
98
|
+
? `${item.replacement.substring(0, 40)}...`
|
|
99
|
+
: item.replacement;
|
|
100
|
+
enhancedMessage += `\n💡 Suggested fix: → "${preview}"`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
id: `ts-slop-${line}-${ruleId}`,
|
|
105
|
+
message: enhancedMessage,
|
|
106
|
+
filePath,
|
|
107
|
+
line,
|
|
108
|
+
column: item.range?.start?.column || 0,
|
|
109
|
+
severity,
|
|
110
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
111
|
+
tool: "ts-slop",
|
|
112
|
+
rule: ruleId,
|
|
113
|
+
fixable: !!item.replacement,
|
|
114
|
+
fixSuggestion: item.replacement,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// JSON parse failed, try line-by-line
|
|
120
|
+
const lines = raw.split("\n");
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
if (line.includes(":") && line.includes("L")) {
|
|
123
|
+
const match = line.match(/L(\d+):?\s*(.+)/);
|
|
124
|
+
if (match) {
|
|
125
|
+
diagnostics.push({
|
|
126
|
+
id: `ts-slop-${match[1]}-line`,
|
|
127
|
+
message: `[slop] ${match[2].trim()}`,
|
|
128
|
+
filePath,
|
|
129
|
+
line: parseInt(match[1], 10),
|
|
130
|
+
severity: "warning",
|
|
131
|
+
semantic: "warning",
|
|
132
|
+
tool: "ts-slop",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return diagnostics;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default tsSlopRunner;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared diagnostic output parsers for pi-lens runners
|
|
3
|
+
*
|
|
4
|
+
* Common patterns for parsing tool output into standardized diagnostics.
|
|
5
|
+
* Supports the common `file:line:col: message` format used by most linters.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create a parser for line-based tool output.
|
|
9
|
+
* Common format: file:line:col: message (with variations)
|
|
10
|
+
*/
|
|
11
|
+
export function createLineParser(config) {
|
|
12
|
+
return (raw, filePath) => {
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
// Optionally strip ANSI codes (for tools that output colored text)
|
|
15
|
+
const clean = config.stripAnsi !== false
|
|
16
|
+
? raw.replace(/\x1b\[[0-9;]*m/g, "")
|
|
17
|
+
: raw;
|
|
18
|
+
const lines = clean.split("\n").filter((l) => l.trim());
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const match = line.match(config.regex);
|
|
21
|
+
if (!match)
|
|
22
|
+
continue;
|
|
23
|
+
const lineNum = parseInt(match[2], 10);
|
|
24
|
+
const colNum = parseInt(match[3], 10);
|
|
25
|
+
const severity = config.getSeverity
|
|
26
|
+
? config.getSeverity(line, match)
|
|
27
|
+
: "warning";
|
|
28
|
+
const fixable = typeof config.fixable === "function"
|
|
29
|
+
? config.fixable(match)
|
|
30
|
+
: config.fixable ?? false;
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
id: config.generateId(match),
|
|
33
|
+
message: config.extractMessage(match),
|
|
34
|
+
filePath,
|
|
35
|
+
line: lineNum,
|
|
36
|
+
column: colNum,
|
|
37
|
+
severity,
|
|
38
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
39
|
+
tool: config.tool,
|
|
40
|
+
rule: config.extractRule?.(match),
|
|
41
|
+
fixable,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return diagnostics;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// PRE-BUILT PARSERS FOR COMMON TOOLS
|
|
49
|
+
// =============================================================================
|
|
50
|
+
/**
|
|
51
|
+
* Parse Ruff output: file:line:col: CODE message
|
|
52
|
+
*/
|
|
53
|
+
export const parseRuffOutput = createLineParser({
|
|
54
|
+
tool: "ruff",
|
|
55
|
+
regex: /^(.+?):(\d+):(\d+):\s*(\w+)\s*(.+)/,
|
|
56
|
+
extractMessage: (m) => `${m[4]}: ${m[5]}`, // CODE: message
|
|
57
|
+
extractRule: (m) => m[4],
|
|
58
|
+
generateId: (m) => `ruff-${m[4]}`,
|
|
59
|
+
fixable: true, // Ruff can fix most issues
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Parse Go vet output: file:line:col: message
|
|
63
|
+
*/
|
|
64
|
+
export const parseGoVetOutput = createLineParser({
|
|
65
|
+
tool: "go-vet",
|
|
66
|
+
regex: /^(.+?):(\d+):(\d+):\s*(.+)/,
|
|
67
|
+
extractMessage: (m) => m[4],
|
|
68
|
+
generateId: (m) => `go-vet-${m[2]}`,
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* Parse Biome output: file:line:col message (category)
|
|
72
|
+
* With autofix support for fix suggestions
|
|
73
|
+
*/
|
|
74
|
+
export function createBiomeParser(autofix = false) {
|
|
75
|
+
return createLineParser({
|
|
76
|
+
tool: "biome",
|
|
77
|
+
regex: /^(.+?):(\d+):(\d+)\s+(.+?)\s*\((.+?)\)/,
|
|
78
|
+
extractMessage: (m) => `${m[5]}: ${m[4]}`, // category: message
|
|
79
|
+
extractRule: (m) => m[5],
|
|
80
|
+
generateId: (m) => `biome-${m[2]}-${m[5]}`,
|
|
81
|
+
getSeverity: (line) => (line.includes("error") ? "error" : "warning"),
|
|
82
|
+
fixable: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Backward-compatible default biome parser
|
|
86
|
+
export const parseBiomeOutput = createBiomeParser(false);
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// GENERIC PARSER FACTORY
|
|
89
|
+
// =============================================================================
|
|
90
|
+
/**
|
|
91
|
+
* Create a simple parser for tools using standard file:line:col format.
|
|
92
|
+
* Format variations: :line:col:, line:col, (line,col), etc.
|
|
93
|
+
*/
|
|
94
|
+
export function createSimpleParser(tool, options = {}) {
|
|
95
|
+
const sep = options.separator ?? ":";
|
|
96
|
+
const severity = options.severity ?? "warning";
|
|
97
|
+
const fixable = options.fixable ?? false;
|
|
98
|
+
// Build regex based on separator type
|
|
99
|
+
const escapedSep = sep === " " ? "\\s+" : escapeRegExp(sep);
|
|
100
|
+
const regex = options.includesFileName
|
|
101
|
+
? new RegExp(`^(.+?)${escapedSep}(\\d+)${escapedSep}(\\d+)${escapedSep}(.+)`)
|
|
102
|
+
: new RegExp(`^(\\d+)${escapedSep}(\\d+)${escapedSep}(.+)`);
|
|
103
|
+
return (raw, filePath) => {
|
|
104
|
+
const diagnostics = [];
|
|
105
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const match = line.match(regex);
|
|
108
|
+
if (!match)
|
|
109
|
+
continue;
|
|
110
|
+
const lineNum = options.includesFileName
|
|
111
|
+
? parseInt(match[2], 10)
|
|
112
|
+
: parseInt(match[1], 10);
|
|
113
|
+
const colNum = options.includesFileName
|
|
114
|
+
? parseInt(match[3], 10)
|
|
115
|
+
: parseInt(match[2], 10);
|
|
116
|
+
const message = options.includesFileName ? match[4] : match[3];
|
|
117
|
+
diagnostics.push({
|
|
118
|
+
id: `${tool}-${lineNum}`,
|
|
119
|
+
message: message.trim(),
|
|
120
|
+
filePath,
|
|
121
|
+
line: lineNum,
|
|
122
|
+
column: colNum,
|
|
123
|
+
severity,
|
|
124
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
125
|
+
tool,
|
|
126
|
+
fixable,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return diagnostics;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function escapeRegExp(string) {
|
|
133
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
134
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared diagnostic output parsers for pi-lens runners
|
|
3
|
+
*
|
|
4
|
+
* Common patterns for parsing tool output into standardized diagnostics.
|
|
5
|
+
* Supports the common `file:line:col: message` format used by most linters.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Diagnostic } from "../../types.js";
|
|
9
|
+
|
|
10
|
+
export interface LineParserConfig {
|
|
11
|
+
/** Tool name for diagnostic identification */
|
|
12
|
+
tool: string;
|
|
13
|
+
/** Regex pattern to match lines. Must capture: [fullMatch, file?, line?, col?, ...messageParts] */
|
|
14
|
+
regex: RegExp;
|
|
15
|
+
/** Extract message from regex match groups */
|
|
16
|
+
extractMessage: (match: RegExpMatchArray) => string;
|
|
17
|
+
/** Extract rule/code from regex match groups (optional) */
|
|
18
|
+
extractRule?: (match: RegExpMatchArray) => string | undefined;
|
|
19
|
+
/** Generate diagnostic ID from match */
|
|
20
|
+
generateId: (match: RegExpMatchArray) => string;
|
|
21
|
+
/** Determine severity from line content or match (defaults to warning) */
|
|
22
|
+
getSeverity?: (line: string, match: RegExpMatchArray) => "error" | "warning" | "info";
|
|
23
|
+
/** Whether this diagnostic is fixable (defaults to false) */
|
|
24
|
+
fixable?: boolean | ((match: RegExpMatchArray) => boolean);
|
|
25
|
+
/** Strip ANSI escape codes before parsing (defaults to true) */
|
|
26
|
+
stripAnsi?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a parser for line-based tool output.
|
|
31
|
+
* Common format: file:line:col: message (with variations)
|
|
32
|
+
*/
|
|
33
|
+
export function createLineParser(config: LineParserConfig) {
|
|
34
|
+
return (raw: string, filePath: string): Diagnostic[] => {
|
|
35
|
+
const diagnostics: Diagnostic[] = [];
|
|
36
|
+
|
|
37
|
+
// Optionally strip ANSI codes (for tools that output colored text)
|
|
38
|
+
const clean =
|
|
39
|
+
config.stripAnsi !== false
|
|
40
|
+
? raw.replace(/\x1b\[[0-9;]*m/g, "")
|
|
41
|
+
: raw;
|
|
42
|
+
|
|
43
|
+
const lines = clean.split("\n").filter((l) => l.trim());
|
|
44
|
+
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const match = line.match(config.regex);
|
|
47
|
+
if (!match) continue;
|
|
48
|
+
|
|
49
|
+
const lineNum = parseInt(match[2], 10);
|
|
50
|
+
const colNum = parseInt(match[3], 10);
|
|
51
|
+
|
|
52
|
+
const severity = config.getSeverity
|
|
53
|
+
? config.getSeverity(line, match)
|
|
54
|
+
: "warning";
|
|
55
|
+
|
|
56
|
+
const fixable =
|
|
57
|
+
typeof config.fixable === "function"
|
|
58
|
+
? config.fixable(match)
|
|
59
|
+
: config.fixable ?? false;
|
|
60
|
+
|
|
61
|
+
diagnostics.push({
|
|
62
|
+
id: config.generateId(match),
|
|
63
|
+
message: config.extractMessage(match),
|
|
64
|
+
filePath,
|
|
65
|
+
line: lineNum,
|
|
66
|
+
column: colNum,
|
|
67
|
+
severity,
|
|
68
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
69
|
+
tool: config.tool,
|
|
70
|
+
rule: config.extractRule?.(match),
|
|
71
|
+
fixable,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return diagnostics;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// PRE-BUILT PARSERS FOR COMMON TOOLS
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parse Ruff output: file:line:col: CODE message
|
|
85
|
+
*/
|
|
86
|
+
export const parseRuffOutput = createLineParser({
|
|
87
|
+
tool: "ruff",
|
|
88
|
+
regex: /^(.+?):(\d+):(\d+):\s*(\w+)\s*(.+)/,
|
|
89
|
+
extractMessage: (m) => `${m[4]}: ${m[5]}`, // CODE: message
|
|
90
|
+
extractRule: (m) => m[4],
|
|
91
|
+
generateId: (m) => `ruff-${m[4]}`,
|
|
92
|
+
fixable: true, // Ruff can fix most issues
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse Go vet output: file:line:col: message
|
|
97
|
+
*/
|
|
98
|
+
export const parseGoVetOutput = createLineParser({
|
|
99
|
+
tool: "go-vet",
|
|
100
|
+
regex: /^(.+?):(\d+):(\d+):\s*(.+)/,
|
|
101
|
+
extractMessage: (m) => m[4],
|
|
102
|
+
generateId: (m) => `go-vet-${m[2]}`,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parse Biome output: file:line:col message (category)
|
|
107
|
+
* With autofix support for fix suggestions
|
|
108
|
+
*/
|
|
109
|
+
export function createBiomeParser(autofix: boolean = false) {
|
|
110
|
+
return createLineParser({
|
|
111
|
+
tool: "biome",
|
|
112
|
+
regex: /^(.+?):(\d+):(\d+)\s+(.+?)\s*\((.+?)\)/,
|
|
113
|
+
extractMessage: (m) => `${m[5]}: ${m[4]}`, // category: message
|
|
114
|
+
extractRule: (m) => m[5],
|
|
115
|
+
generateId: (m) => `biome-${m[2]}-${m[5]}`,
|
|
116
|
+
getSeverity: (line) => (line.includes("error") ? "error" : "warning"),
|
|
117
|
+
fixable: true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Backward-compatible default biome parser
|
|
122
|
+
export const parseBiomeOutput = createBiomeParser(false);
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// GENERIC PARSER FACTORY
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create a simple parser for tools using standard file:line:col format.
|
|
130
|
+
* Format variations: :line:col:, line:col, (line,col), etc.
|
|
131
|
+
*/
|
|
132
|
+
export function createSimpleParser(
|
|
133
|
+
tool: string,
|
|
134
|
+
options: {
|
|
135
|
+
separator?: ":" | " " | ",";
|
|
136
|
+
includesFileName?: boolean;
|
|
137
|
+
severity?: "error" | "warning" | "info";
|
|
138
|
+
fixable?: boolean;
|
|
139
|
+
} = {},
|
|
140
|
+
): (raw: string, filePath: string) => Diagnostic[] {
|
|
141
|
+
const sep = options.separator ?? ":";
|
|
142
|
+
const severity = options.severity ?? "warning";
|
|
143
|
+
const fixable = options.fixable ?? false;
|
|
144
|
+
|
|
145
|
+
// Build regex based on separator type
|
|
146
|
+
const escapedSep = sep === " " ? "\\s+" : escapeRegExp(sep);
|
|
147
|
+
const regex = options.includesFileName
|
|
148
|
+
? new RegExp(`^(.+?)${escapedSep}(\\d+)${escapedSep}(\\d+)${escapedSep}(.+)`)
|
|
149
|
+
: new RegExp(`^(\\d+)${escapedSep}(\\d+)${escapedSep}(.+)`);
|
|
150
|
+
|
|
151
|
+
return (raw: string, filePath: string): Diagnostic[] => {
|
|
152
|
+
const diagnostics: Diagnostic[] = [];
|
|
153
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
154
|
+
|
|
155
|
+
for (const line of lines) {
|
|
156
|
+
const match = line.match(regex);
|
|
157
|
+
if (!match) continue;
|
|
158
|
+
|
|
159
|
+
const lineNum = options.includesFileName
|
|
160
|
+
? parseInt(match[2], 10)
|
|
161
|
+
: parseInt(match[1], 10);
|
|
162
|
+
const colNum = options.includesFileName
|
|
163
|
+
? parseInt(match[3], 10)
|
|
164
|
+
: parseInt(match[2], 10);
|
|
165
|
+
const message = options.includesFileName ? match[4] : match[3];
|
|
166
|
+
|
|
167
|
+
diagnostics.push({
|
|
168
|
+
id: `${tool}-${lineNum}`,
|
|
169
|
+
message: message.trim(),
|
|
170
|
+
filePath,
|
|
171
|
+
line: lineNum,
|
|
172
|
+
column: colNum,
|
|
173
|
+
severity,
|
|
174
|
+
semantic: severity === "error" ? "blocking" : "warning",
|
|
175
|
+
tool,
|
|
176
|
+
fixable,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return diagnostics;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function escapeRegExp(string: string): string {
|
|
185
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
186
|
+
}
|