pi-lens 2.1.1 → 2.2.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 +10 -0
- package/README.md +38 -0
- package/clients/dispatch/runners/index.js +3 -1
- package/clients/dispatch/runners/index.ts +3 -1
- package/clients/dispatch/runners/pyright.js +68 -0
- package/clients/dispatch/runners/pyright.test.js +84 -0
- package/clients/dispatch/runners/pyright.test.ts +109 -0
- package/clients/dispatch/runners/pyright.ts +102 -0
- package/commands/rate.js +285 -0
- package/commands/rate.test.js +119 -0
- package/commands/rate.test.ts +131 -0
- package/commands/rate.ts +348 -0
- package/index.ts +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -298,6 +298,16 @@ All notable changes to pi-lens will be documented in this file.
|
|
|
298
298
|
### Changed
|
|
299
299
|
- **Improved ast-grep tool descriptions**: Better pattern guidance to prevent overly broad searches.
|
|
300
300
|
|
|
301
|
+
## [2.2.0] - 2026-03-29
|
|
302
|
+
|
|
303
|
+
### Added
|
|
304
|
+
- **`/lens-rate` command**: Visual code quality scoring across 6 dimensions (Type Safety, Complexity, Security, Architecture, Dead Code, Tests). Shows grade A-F and colored progress bars.
|
|
305
|
+
- **Pyright runner**: Real Python type-checking via pyright. Catches type errors like `result: str = add(1, 2)` that ruff misses. Runs alongside ruff (pyright for types, ruff for linting).
|
|
306
|
+
- **Vitest config**: Increased test timeout to 15s for CLI spawn tests. Fixes flaky test failures when npx downloads packages.
|
|
307
|
+
|
|
308
|
+
### Fixed
|
|
309
|
+
- **Test flakiness**: Availability tests (biome, knip, jscpd) no longer timeout when npx is downloading packages.
|
|
310
|
+
|
|
301
311
|
## [1.3.0] - 2026-03-23
|
|
302
312
|
|
|
303
313
|
### Changed
|
package/README.md
CHANGED
|
@@ -16,6 +16,43 @@ pi install git:github.com/apmantza/pi-lens
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## What's New (v2.2)
|
|
20
|
+
|
|
21
|
+
### `/lens-rate` — Code Quality Scoring
|
|
22
|
+
|
|
23
|
+
Visual scoring breakdown across 6 dimensions with grade A-F:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────────────┐
|
|
27
|
+
│ 📊 CODE QUALITY SCORE: 85/100 (B) │
|
|
28
|
+
├─────────────────────────────────────────────────────────┤
|
|
29
|
+
│ 🔷 Type Safety 🟩🟩🟩🟩🟩🟩🟩🟩⬜⬜ 85 │
|
|
30
|
+
│ 🧩 Complexity 🟩🟩🟩🟩🟩🟩🟩🟩⬜⬜ 82 │
|
|
31
|
+
│ 🔒 Security 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100 │
|
|
32
|
+
│ 🏗️ Architecture 🟩🟩🟩🟩🟩🟩🟩🟩⬜⬜ 80 │
|
|
33
|
+
│ 🗑️ Dead Code 🟩🟩🟩🟩🟩🟩🟩🟩🟩⬜ 90 │
|
|
34
|
+
│ ✅ Tests 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 100 │
|
|
35
|
+
└─────────────────────────────────────────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Scores are calculated from real scan data: type-coverage, complexity metrics, secret detection, architect rules, knip, and test results.
|
|
39
|
+
|
|
40
|
+
### Python Type-Checking (Pyright)
|
|
41
|
+
|
|
42
|
+
Python files now get **real type-checking** via pyright, not just linting:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# ruff won't catch this, but pyright will:
|
|
46
|
+
def add(x: int, y: int) -> int:
|
|
47
|
+
return x + y
|
|
48
|
+
|
|
49
|
+
result: str = add(1, 2) # ❌ pyright: Type "int" not assignable to "str"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Pyright runs alongside ruff — pyright catches type errors, ruff catches style issues.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
19
56
|
## What's New (v2.1)
|
|
20
57
|
|
|
21
58
|
### Content-Level Secret Scanning
|
|
@@ -358,6 +395,7 @@ Each rule includes a `message` and `note` that are shown in diagnostics, so the
|
|
|
358
395
|
| `type-coverage` | `npm i -D type-coverage` | TypeScript `any` coverage percentage |
|
|
359
396
|
| `madge` | `npm i -D madge` | Circular dependency detection |
|
|
360
397
|
| `ruff` | `pip install ruff` | Python lint + format + autofix |
|
|
398
|
+
| `pyright` | `npx pyright` (auto-installed) | Python type-checking |
|
|
361
399
|
|
|
362
400
|
---
|
|
363
401
|
|
|
@@ -7,12 +7,14 @@ import architectRunner from "./architect.js";
|
|
|
7
7
|
import astGrepRunner from "./ast-grep.js";
|
|
8
8
|
import biomeRunner from "./biome.js";
|
|
9
9
|
import goVetRunner from "./go-vet.js";
|
|
10
|
+
import pyrightRunner from "./pyright.js";
|
|
10
11
|
import ruffRunner from "./ruff.js";
|
|
11
12
|
import rustClippyRunner from "./rust-clippy.js";
|
|
12
13
|
import tsLspRunner from "./ts-lsp.js";
|
|
13
14
|
import typeSafetyRunner from "./type-safety.js";
|
|
14
15
|
// Register all runners (ordered by priority)
|
|
15
|
-
registerRunner(tsLspRunner);
|
|
16
|
+
registerRunner(tsLspRunner); // TypeScript type-checking
|
|
17
|
+
registerRunner(pyrightRunner); // Python type-checking
|
|
16
18
|
registerRunner(biomeRunner);
|
|
17
19
|
registerRunner(ruffRunner);
|
|
18
20
|
registerRunner(typeSafetyRunner);
|
|
@@ -8,13 +8,15 @@ import architectRunner from "./architect.js";
|
|
|
8
8
|
import astGrepRunner from "./ast-grep.js";
|
|
9
9
|
import biomeRunner from "./biome.js";
|
|
10
10
|
import goVetRunner from "./go-vet.js";
|
|
11
|
+
import pyrightRunner from "./pyright.js";
|
|
11
12
|
import ruffRunner from "./ruff.js";
|
|
12
13
|
import rustClippyRunner from "./rust-clippy.js";
|
|
13
14
|
import tsLspRunner from "./ts-lsp.js";
|
|
14
15
|
import typeSafetyRunner from "./type-safety.js";
|
|
15
16
|
|
|
16
17
|
// Register all runners (ordered by priority)
|
|
17
|
-
registerRunner(tsLspRunner);
|
|
18
|
+
registerRunner(tsLspRunner); // TypeScript type-checking
|
|
19
|
+
registerRunner(pyrightRunner); // Python type-checking
|
|
18
20
|
registerRunner(biomeRunner);
|
|
19
21
|
registerRunner(ruffRunner);
|
|
20
22
|
registerRunner(typeSafetyRunner);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyright runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Provides real Python type-checking (not just linting).
|
|
5
|
+
* Catches type errors like: result: str = add(1, 2) # Type "int" not assignable to "str"
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
const pyrightRunner = {
|
|
9
|
+
id: "pyright",
|
|
10
|
+
appliesTo: ["python"],
|
|
11
|
+
priority: 5, // Higher priority than ruff (10) - type errors are more important
|
|
12
|
+
enabledByDefault: true,
|
|
13
|
+
async run(ctx) {
|
|
14
|
+
// Run pyright with JSON output
|
|
15
|
+
const result = spawnSync("npx", ["pyright", "--outputjson", ctx.filePath], {
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
timeout: 60000, // Pyright can be slower on first run
|
|
18
|
+
shell: true,
|
|
19
|
+
});
|
|
20
|
+
// Pyright returns non-zero when errors found, that's OK
|
|
21
|
+
if (result.error) {
|
|
22
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
23
|
+
}
|
|
24
|
+
const output = (result.stdout || "").trim();
|
|
25
|
+
if (!output) {
|
|
26
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(output);
|
|
30
|
+
const diagnostics = parsePyrightOutput(data, ctx.filePath);
|
|
31
|
+
if (diagnostics.length === 0) {
|
|
32
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
33
|
+
}
|
|
34
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
35
|
+
return {
|
|
36
|
+
status: hasErrors ? "failed" : "succeeded",
|
|
37
|
+
diagnostics,
|
|
38
|
+
semantic: "warning",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// JSON parse failed, skip
|
|
43
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
function parsePyrightOutput(data, filePath) {
|
|
48
|
+
if (!data.generalDiagnostics)
|
|
49
|
+
return [];
|
|
50
|
+
return data.generalDiagnostics
|
|
51
|
+
.filter((d) => {
|
|
52
|
+
// Only include errors and warnings, skip informational
|
|
53
|
+
return d.severity === "error" || d.severity === "warning";
|
|
54
|
+
})
|
|
55
|
+
.map((d) => ({
|
|
56
|
+
id: `pyright-${d.range.start.line}-${d.rule}`,
|
|
57
|
+
message: d.message.split("\n")[0], // First line only (pyright has multi-line messages)
|
|
58
|
+
filePath,
|
|
59
|
+
line: d.range.start.line + 1, // Pyright is 0-indexed, we're 1-indexed
|
|
60
|
+
column: d.range.start.character + 1,
|
|
61
|
+
severity: d.severity === "error" ? "error" : "warning",
|
|
62
|
+
semantic: d.severity === "error" ? "blocking" : "warning",
|
|
63
|
+
tool: "pyright",
|
|
64
|
+
rule: d.rule,
|
|
65
|
+
fixable: false, // Pyright can't auto-fix, only suggest
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
export default pyrightRunner;
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
describe("pyright runner", () => {
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
it("should have correct runner definition", async () => {
|
|
21
|
+
const pyrightModule = await import("./pyright.js");
|
|
22
|
+
const runner = pyrightModule.default;
|
|
23
|
+
expect(runner.id).toBe("pyright");
|
|
24
|
+
expect(runner.appliesTo).toEqual(["python"]);
|
|
25
|
+
expect(runner.priority).toBe(5); // Higher priority than ruff
|
|
26
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("should detect pyright availability", () => {
|
|
29
|
+
const { spawnSync } = require("node:child_process");
|
|
30
|
+
const result = spawnSync("npx", ["pyright", "--version"], {
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
timeout: 10000,
|
|
33
|
+
shell: true,
|
|
34
|
+
});
|
|
35
|
+
expect(result.error || result.status !== 0 ? "not available" : "available").toBe("available");
|
|
36
|
+
});
|
|
37
|
+
it("should type-check Python files and find errors", async () => {
|
|
38
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `pyright_test_${Date.now()}.py`);
|
|
39
|
+
fs.writeFileSync(tmpFile, `def add(x: int, y: int) -> int:
|
|
40
|
+
return x + y
|
|
41
|
+
|
|
42
|
+
result: str = add(1, 2)
|
|
43
|
+
|
|
44
|
+
def greet(name: str) -> str:
|
|
45
|
+
return "Hello " + name
|
|
46
|
+
|
|
47
|
+
greet(123)
|
|
48
|
+
`);
|
|
49
|
+
try {
|
|
50
|
+
const pyrightModule = await import("./pyright.js");
|
|
51
|
+
const runner = pyrightModule.default;
|
|
52
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
53
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(2);
|
|
54
|
+
expect(result.diagnostics.some((d) => d.tool === "pyright")).toBe(true);
|
|
55
|
+
expect(result.diagnostics.some((d) => d.severity === "error")).toBe(true);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
fs.unlinkSync(tmpFile);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
it("should pass valid Python files", async () => {
|
|
62
|
+
const tmpFile = path.join(process.env.TEMP || "/tmp", `pyright_test_ok_${Date.now()}.py`);
|
|
63
|
+
fs.writeFileSync(tmpFile, `def add(x: int, y: int) -> int:
|
|
64
|
+
return x + y
|
|
65
|
+
|
|
66
|
+
result: str = str(add(1, 2))
|
|
67
|
+
|
|
68
|
+
def greet(name: str) -> str:
|
|
69
|
+
return "Hello " + name
|
|
70
|
+
|
|
71
|
+
greet("world")
|
|
72
|
+
`);
|
|
73
|
+
try {
|
|
74
|
+
const pyrightModule = await import("./pyright.js");
|
|
75
|
+
const runner = pyrightModule.default;
|
|
76
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
77
|
+
expect(result.status).toBe("succeeded");
|
|
78
|
+
expect(result.diagnostics.length).toBe(0);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
fs.unlinkSync(tmpFile);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
describe("pyright runner", () => {
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
|
|
24
|
+
it("should have correct runner definition", async () => {
|
|
25
|
+
const pyrightModule = await import("./pyright.js");
|
|
26
|
+
const runner = pyrightModule.default;
|
|
27
|
+
|
|
28
|
+
expect(runner.id).toBe("pyright");
|
|
29
|
+
expect(runner.appliesTo).toEqual(["python"]);
|
|
30
|
+
expect(runner.priority).toBe(5); // Higher priority than ruff
|
|
31
|
+
expect(runner.enabledByDefault).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should detect pyright availability", () => {
|
|
35
|
+
const { spawnSync } =
|
|
36
|
+
require("node:child_process") as typeof import("node:child_process");
|
|
37
|
+
const result = spawnSync("npx", ["pyright", "--version"], {
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
timeout: 10000,
|
|
40
|
+
shell: true,
|
|
41
|
+
});
|
|
42
|
+
expect(
|
|
43
|
+
result.error || result.status !== 0 ? "not available" : "available",
|
|
44
|
+
).toBe("available");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should type-check Python files and find errors", async () => {
|
|
48
|
+
const tmpFile = path.join(
|
|
49
|
+
process.env.TEMP || "/tmp",
|
|
50
|
+
`pyright_test_${Date.now()}.py`,
|
|
51
|
+
);
|
|
52
|
+
fs.writeFileSync(
|
|
53
|
+
tmpFile,
|
|
54
|
+
`def add(x: int, y: int) -> int:
|
|
55
|
+
return x + y
|
|
56
|
+
|
|
57
|
+
result: str = add(1, 2)
|
|
58
|
+
|
|
59
|
+
def greet(name: str) -> str:
|
|
60
|
+
return "Hello " + name
|
|
61
|
+
|
|
62
|
+
greet(123)
|
|
63
|
+
`,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const pyrightModule = await import("./pyright.js");
|
|
68
|
+
const runner = pyrightModule.default;
|
|
69
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
70
|
+
|
|
71
|
+
expect(result.diagnostics.length).toBeGreaterThanOrEqual(2);
|
|
72
|
+
expect(result.diagnostics.some((d) => d.tool === "pyright")).toBe(true);
|
|
73
|
+
expect(result.diagnostics.some((d) => d.severity === "error")).toBe(true);
|
|
74
|
+
} finally {
|
|
75
|
+
fs.unlinkSync(tmpFile);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should pass valid Python files", async () => {
|
|
80
|
+
const tmpFile = path.join(
|
|
81
|
+
process.env.TEMP || "/tmp",
|
|
82
|
+
`pyright_test_ok_${Date.now()}.py`,
|
|
83
|
+
);
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
tmpFile,
|
|
86
|
+
`def add(x: int, y: int) -> int:
|
|
87
|
+
return x + y
|
|
88
|
+
|
|
89
|
+
result: str = str(add(1, 2))
|
|
90
|
+
|
|
91
|
+
def greet(name: str) -> str:
|
|
92
|
+
return "Hello " + name
|
|
93
|
+
|
|
94
|
+
greet("world")
|
|
95
|
+
`,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const pyrightModule = await import("./pyright.js");
|
|
100
|
+
const runner = pyrightModule.default;
|
|
101
|
+
const result = await runner.run(createMockContext(tmpFile));
|
|
102
|
+
|
|
103
|
+
expect(result.status).toBe("succeeded");
|
|
104
|
+
expect(result.diagnostics.length).toBe(0);
|
|
105
|
+
} finally {
|
|
106
|
+
fs.unlinkSync(tmpFile);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyright runner for dispatch system
|
|
3
|
+
*
|
|
4
|
+
* Provides real Python type-checking (not just linting).
|
|
5
|
+
* Catches type errors like: result: str = add(1, 2) # Type "int" not assignable to "str"
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import type {
|
|
10
|
+
Diagnostic,
|
|
11
|
+
DispatchContext,
|
|
12
|
+
RunnerDefinition,
|
|
13
|
+
RunnerResult,
|
|
14
|
+
} from "../types.js";
|
|
15
|
+
|
|
16
|
+
const pyrightRunner: RunnerDefinition = {
|
|
17
|
+
id: "pyright",
|
|
18
|
+
appliesTo: ["python"],
|
|
19
|
+
priority: 5, // Higher priority than ruff (10) - type errors are more important
|
|
20
|
+
enabledByDefault: true,
|
|
21
|
+
|
|
22
|
+
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
23
|
+
// Run pyright with JSON output
|
|
24
|
+
const result = spawnSync("npx", ["pyright", "--outputjson", ctx.filePath], {
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
timeout: 60000, // Pyright can be slower on first run
|
|
27
|
+
shell: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Pyright returns non-zero when errors found, that's OK
|
|
31
|
+
if (result.error) {
|
|
32
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const output = (result.stdout || "").trim();
|
|
36
|
+
if (!output) {
|
|
37
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const data = JSON.parse(output);
|
|
42
|
+
const diagnostics = parsePyrightOutput(data, ctx.filePath);
|
|
43
|
+
|
|
44
|
+
if (diagnostics.length === 0) {
|
|
45
|
+
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
status: hasErrors ? "failed" : "succeeded",
|
|
52
|
+
diagnostics,
|
|
53
|
+
semantic: "warning",
|
|
54
|
+
};
|
|
55
|
+
} catch {
|
|
56
|
+
// JSON parse failed, skip
|
|
57
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
interface PyrightDiagnostic {
|
|
63
|
+
file: string;
|
|
64
|
+
severity: "error" | "warning" | "information";
|
|
65
|
+
message: string;
|
|
66
|
+
range: {
|
|
67
|
+
start: { line: number; character: number };
|
|
68
|
+
end: { line: number; character: number };
|
|
69
|
+
};
|
|
70
|
+
rule: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface PyrightResult {
|
|
74
|
+
generalDiagnostics: PyrightDiagnostic[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parsePyrightOutput(
|
|
78
|
+
data: PyrightResult,
|
|
79
|
+
filePath: string,
|
|
80
|
+
): Diagnostic[] {
|
|
81
|
+
if (!data.generalDiagnostics) return [];
|
|
82
|
+
|
|
83
|
+
return data.generalDiagnostics
|
|
84
|
+
.filter((d) => {
|
|
85
|
+
// Only include errors and warnings, skip informational
|
|
86
|
+
return d.severity === "error" || d.severity === "warning";
|
|
87
|
+
})
|
|
88
|
+
.map((d) => ({
|
|
89
|
+
id: `pyright-${d.range.start.line}-${d.rule}`,
|
|
90
|
+
message: d.message.split("\n")[0], // First line only (pyright has multi-line messages)
|
|
91
|
+
filePath,
|
|
92
|
+
line: d.range.start.line + 1, // Pyright is 0-indexed, we're 1-indexed
|
|
93
|
+
column: d.range.start.character + 1,
|
|
94
|
+
severity: d.severity === "error" ? "error" : "warning",
|
|
95
|
+
semantic: d.severity === "error" ? "blocking" : "warning",
|
|
96
|
+
tool: "pyright",
|
|
97
|
+
rule: d.rule,
|
|
98
|
+
fixable: false, // Pyright can't auto-fix, only suggest
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default pyrightRunner;
|