pi-lens 3.1.2 → 3.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 +55 -0
- package/README.md +16 -12
- package/clients/ast-grep-client.js +8 -1
- package/clients/ast-grep-client.ts +9 -1
- package/clients/biome-client.js +51 -38
- package/clients/biome-client.ts +60 -58
- package/clients/dependency-checker.js +30 -1
- package/clients/dependency-checker.ts +35 -1
- package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
- package/clients/dispatch/bus-dispatcher.js +15 -14
- package/clients/dispatch/bus-dispatcher.ts +32 -25
- package/clients/dispatch/dispatcher.js +18 -25
- package/clients/dispatch/dispatcher.test.ts +2 -1
- package/clients/dispatch/dispatcher.ts +17 -28
- package/clients/dispatch/plan.js +77 -32
- package/clients/dispatch/plan.ts +78 -32
- package/clients/dispatch/runners/ast-grep-napi.js +36 -376
- package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
- package/clients/dispatch/runners/index.js +8 -4
- package/clients/dispatch/runners/index.ts +8 -4
- package/clients/dispatch/runners/lsp.js +65 -0
- package/clients/dispatch/runners/lsp.ts +125 -0
- package/clients/dispatch/runners/oxlint.js +2 -2
- package/clients/dispatch/runners/oxlint.ts +2 -2
- package/clients/dispatch/runners/pyright.js +24 -8
- package/clients/dispatch/runners/pyright.ts +28 -14
- package/clients/dispatch/runners/rust-clippy.js +2 -2
- package/clients/dispatch/runners/rust-clippy.ts +2 -4
- package/clients/dispatch/runners/tree-sitter.js +14 -2
- package/clients/dispatch/runners/tree-sitter.ts +15 -2
- package/clients/dispatch/runners/ts-lsp.js +3 -3
- package/clients/dispatch/runners/ts-lsp.ts +8 -5
- package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
- package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
- package/clients/dispatch/types.js +3 -0
- package/clients/dispatch/types.ts +3 -0
- package/clients/formatters.js +67 -14
- package/clients/formatters.ts +68 -15
- package/clients/installer/index.js +78 -10
- package/clients/installer/index.ts +519 -426
- package/clients/jscpd-client.js +28 -0
- package/clients/jscpd-client.ts +41 -3
- package/clients/knip-client.js +30 -1
- package/clients/knip-client.ts +34 -2
- package/clients/lsp/__tests__/client.test.ts +64 -41
- package/clients/lsp/__tests__/config.test.ts +25 -17
- package/clients/lsp/__tests__/launch.test.ts +108 -43
- package/clients/lsp/__tests__/service.test.ts +76 -48
- package/clients/lsp/client.js +87 -2
- package/clients/lsp/client.ts +150 -6
- package/clients/lsp/config.js +8 -11
- package/clients/lsp/config.ts +24 -21
- package/clients/lsp/index.js +69 -0
- package/clients/lsp/index.ts +82 -0
- package/clients/lsp/interactive-install.js +19 -8
- package/clients/lsp/interactive-install.ts +52 -27
- package/clients/lsp/launch.js +182 -32
- package/clients/lsp/launch.ts +241 -38
- package/clients/lsp/path-utils.js +3 -46
- package/clients/lsp/path-utils.ts +11 -51
- package/clients/lsp/server.js +93 -71
- package/clients/lsp/server.ts +173 -131
- package/clients/path-utils.js +142 -0
- package/clients/path-utils.ts +153 -0
- package/clients/ruff-client.js +33 -4
- package/clients/ruff-client.ts +44 -13
- package/clients/safe-spawn.js +3 -1
- package/clients/safe-spawn.ts +3 -1
- package/clients/services/effect-integration.js +11 -7
- package/clients/services/effect-integration.ts +34 -26
- package/clients/sg-runner.js +51 -9
- package/clients/sg-runner.ts +58 -15
- package/clients/tree-sitter-client.js +12 -0
- package/clients/tree-sitter-client.ts +12 -0
- package/clients/typescript-client.js +6 -2
- package/clients/typescript-client.ts +9 -2
- package/commands/booboo.js +2 -4
- package/commands/booboo.ts +2 -4
- package/index.ts +377 -93
- package/package.json +2 -1
- package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
- package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
- package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/ast-grep-client.test.js +0 -129
- package/clients/ast-grep-client.test.ts +0 -155
- package/clients/biome-client.test.js +0 -144
- package/clients/cache-manager.test.js +0 -197
- package/clients/complexity-client.test.js +0 -234
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.test.js +0 -115
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/ts-slop.test.js +0 -180
- package/clients/dispatch/runners/ts-slop.test.ts +0 -230
- package/clients/dogfood.test.js +0 -201
- package/clients/file-kinds.test.js +0 -169
- package/clients/go-client.test.js +0 -127
- package/clients/jscpd-client.test.js +0 -127
- package/clients/knip-client.test.js +0 -112
- package/clients/lsp/__tests__/client.test.js +0 -325
- package/clients/lsp/__tests__/config.test.js +0 -166
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/launch.test.js +0 -260
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/service.test.js +0 -417
- package/clients/metrics-client.test.js +0 -141
- package/clients/ruff-client.test.js +0 -132
- package/clients/rust-client.test.js +0 -108
- package/clients/sanitize.test.js +0 -177
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/services/__tests__/effect-integration.test.js +0 -86
- package/clients/test-runner-client.test.js +0 -192
- package/clients/todo-scanner.test.js +0 -301
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.test.js +0 -105
- package/commands/clients/ast-grep-client.js +0 -250
- package/commands/clients/ast-grep-parser.js +0 -86
- package/commands/clients/ast-grep-rule-manager.js +0 -91
- package/commands/clients/ast-grep-types.js +0 -9
- package/commands/clients/biome-client.js +0 -380
- package/commands/clients/complexity-client.js +0 -667
- package/commands/clients/file-kinds.js +0 -177
- package/commands/clients/file-utils.js +0 -40
- package/commands/clients/jscpd-client.js +0 -169
- package/commands/clients/knip-client.js +0 -211
- package/commands/clients/ruff-client.js +0 -297
- package/commands/clients/safe-spawn.js +0 -88
- package/commands/clients/scan-utils.js +0 -83
- package/commands/clients/sg-runner.js +0 -190
- package/commands/clients/types.js +0 -11
- package/commands/clients/typescript-client.js +0 -505
- package/commands/rate.test.js +0 -119
- package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
- package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
- package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
|
@@ -1,203 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
// Find all TS files
|
|
5
|
-
function findTsFiles(dir) {
|
|
6
|
-
const files = [];
|
|
7
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
8
|
-
for (const entry of entries) {
|
|
9
|
-
const fullPath = path.join(dir, entry.name);
|
|
10
|
-
// Skip node_modules, .git, etc
|
|
11
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === ".pi-lens") {
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
if (entry.isDirectory()) {
|
|
15
|
-
files.push(...findTsFiles(fullPath));
|
|
16
|
-
}
|
|
17
|
-
else if (entry.isFile() && fullPath.endsWith(".ts") && !fullPath.endsWith(".test.ts")) {
|
|
18
|
-
files.push(fullPath);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return files;
|
|
22
|
-
}
|
|
23
|
-
function createContext(filePath) {
|
|
24
|
-
return {
|
|
25
|
-
filePath,
|
|
26
|
-
cwd: process.cwd(),
|
|
27
|
-
kind: "jsts",
|
|
28
|
-
autofix: false,
|
|
29
|
-
deltaMode: false,
|
|
30
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
31
|
-
pi: {},
|
|
32
|
-
hasTool: async () => false,
|
|
33
|
-
log: () => { },
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
describe("Codebase scan with NAPI runner", () => {
|
|
37
|
-
it("should scan all TypeScript files and report findings", async () => {
|
|
38
|
-
const tsFiles = findTsFiles(process.cwd());
|
|
39
|
-
console.log(`\nFound ${tsFiles.length} TypeScript files to scan\n`);
|
|
40
|
-
const runner = (await import("./ast-grep-napi.js")).default;
|
|
41
|
-
const allIssues = [];
|
|
42
|
-
let totalTime = 0;
|
|
43
|
-
let filesWithIssues = 0;
|
|
44
|
-
for (let i = 0; i < Math.min(tsFiles.length, 50); i++) { // Limit to 50 for test speed
|
|
45
|
-
const file = tsFiles[i];
|
|
46
|
-
const ctx = createContext(file);
|
|
47
|
-
const start = Date.now();
|
|
48
|
-
const result = await runner.run(ctx);
|
|
49
|
-
const elapsed = Date.now() - start;
|
|
50
|
-
totalTime += elapsed;
|
|
51
|
-
if (result.diagnostics.length > 0) {
|
|
52
|
-
filesWithIssues++;
|
|
53
|
-
console.log(`${path.relative(process.cwd(), file)} (${elapsed}ms):`);
|
|
54
|
-
for (const d of result.diagnostics.slice(0, 5)) { // Show max 5 per file
|
|
55
|
-
const line = d.line ?? 0;
|
|
56
|
-
const rule = d.rule ?? "unknown";
|
|
57
|
-
const message = d.message?.split('\n')[0] ?? "";
|
|
58
|
-
console.log(` Line ${line}: [${rule}] ${message}`);
|
|
59
|
-
allIssues.push({
|
|
60
|
-
file: path.relative(process.cwd(), file),
|
|
61
|
-
line,
|
|
62
|
-
rule,
|
|
63
|
-
message,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
if (result.diagnostics.length > 5) {
|
|
67
|
-
console.log(` ... and ${result.diagnostics.length - 5} more`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
console.log(`\n=== SUMMARY (first 50 files) ===`);
|
|
72
|
-
console.log(`Files scanned: ${Math.min(tsFiles.length, 50)}/${tsFiles.length}`);
|
|
73
|
-
console.log(`Total time: ${totalTime}ms`);
|
|
74
|
-
console.log(`Files with issues: ${filesWithIssues}`);
|
|
75
|
-
console.log(`Total issues: ${allIssues.length}`);
|
|
76
|
-
console.log(`Avg time per file: ${(totalTime / Math.min(tsFiles.length, 50)).toFixed(1)}ms`);
|
|
77
|
-
// Group by rule
|
|
78
|
-
const byRule = {};
|
|
79
|
-
for (const issue of allIssues) {
|
|
80
|
-
byRule[issue.rule] = (byRule[issue.rule] || 0) + 1;
|
|
81
|
-
}
|
|
82
|
-
console.log(`\n=== BY RULE ===`);
|
|
83
|
-
for (const [rule, count] of Object.entries(byRule).sort((a, b) => b[1] - a[1])) {
|
|
84
|
-
console.log(` ${rule}: ${count}`);
|
|
85
|
-
}
|
|
86
|
-
// This test should pass - we're just scanning
|
|
87
|
-
expect(true).toBe(true);
|
|
88
|
-
}, 60000); // 60 second timeout for scanning
|
|
89
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for shellcheck runner
|
|
3
|
-
*/
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
function createMockContext(filePath) {
|
|
9
|
-
return {
|
|
10
|
-
filePath,
|
|
11
|
-
cwd: process.cwd(),
|
|
12
|
-
kind: "shell",
|
|
13
|
-
autofix: false,
|
|
14
|
-
deltaMode: false,
|
|
15
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
16
|
-
pi: {},
|
|
17
|
-
hasTool: async () => false,
|
|
18
|
-
log: () => { },
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
// Helper for safe file cleanup
|
|
22
|
-
function safeUnlink(filePath) {
|
|
23
|
-
try {
|
|
24
|
-
if (fs.existsSync(filePath)) {
|
|
25
|
-
fs.unlinkSync(filePath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// Ignore cleanup errors on Windows
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
describe("shellcheck runner", () => {
|
|
33
|
-
const require = createRequire(import.meta.url);
|
|
34
|
-
it("should have correct runner definition", async () => {
|
|
35
|
-
const shellcheckModule = await import("./shellcheck.js");
|
|
36
|
-
const runner = shellcheckModule.default;
|
|
37
|
-
expect(runner.id).toBe("shellcheck");
|
|
38
|
-
expect(runner.appliesTo).toEqual(["shell"]);
|
|
39
|
-
expect(runner.priority).toBe(20);
|
|
40
|
-
expect(runner.enabledByDefault).toBe(true);
|
|
41
|
-
expect(runner.skipTestFiles).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
it("should detect shellcheck availability", () => {
|
|
44
|
-
const { spawnSync } = require("node:child_process");
|
|
45
|
-
const result = spawnSync("shellcheck", ["--version"], {
|
|
46
|
-
encoding: "utf-8",
|
|
47
|
-
timeout: 10000,
|
|
48
|
-
shell: true,
|
|
49
|
-
});
|
|
50
|
-
expect(result.error || result.status !== 0 ? "not available" : "available").toBeTruthy();
|
|
51
|
-
});
|
|
52
|
-
it("should detect undefined variable", async () => {
|
|
53
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `shellcheck_test_${Date.now()}.sh`);
|
|
54
|
-
fs.writeFileSync(tmpFile, ["#!/bin/bash", "# Test script with issues", 'echo "\$UNDEFINED_VAR"', ""].join("\n"));
|
|
55
|
-
try {
|
|
56
|
-
const shellcheckModule = await import("./shellcheck.js");
|
|
57
|
-
const runner = shellcheckModule.default;
|
|
58
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
59
|
-
if (result.status !== "skipped") {
|
|
60
|
-
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
61
|
-
expect(result.diagnostics.some((d) => d.tool === "shellcheck" &&
|
|
62
|
-
(d.message.includes("undefined") ||
|
|
63
|
-
d.message.includes("SC2154")))).toBe(true);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
safeUnlink(tmpFile);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
it("should pass clean shell scripts", async () => {
|
|
71
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `shellcheck_ok_${Date.now()}.sh`);
|
|
72
|
-
fs.writeFileSync(tmpFile, [
|
|
73
|
-
"#!/bin/bash",
|
|
74
|
-
"# Clean shell script",
|
|
75
|
-
"set -euo pipefail",
|
|
76
|
-
"",
|
|
77
|
-
"main() {",
|
|
78
|
-
' local name="\${1:-world}"',
|
|
79
|
-
' echo "Hello, \${name}!"',
|
|
80
|
-
"}",
|
|
81
|
-
"",
|
|
82
|
-
'main "\$@"',
|
|
83
|
-
"",
|
|
84
|
-
].join("\n"));
|
|
85
|
-
try {
|
|
86
|
-
const shellcheckModule = await import("./shellcheck.js");
|
|
87
|
-
const runner = shellcheckModule.default;
|
|
88
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
89
|
-
if (result.status !== "skipped") {
|
|
90
|
-
expect(result.diagnostics.length).toBe(0);
|
|
91
|
-
expect(result.status).toBe("succeeded");
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
safeUnlink(tmpFile);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for spellcheck runner (typos-cli)
|
|
3
|
-
*/
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
-
function createMockContext(filePath) {
|
|
9
|
-
return {
|
|
10
|
-
filePath,
|
|
11
|
-
cwd: process.cwd(),
|
|
12
|
-
kind: "markdown",
|
|
13
|
-
autofix: false,
|
|
14
|
-
deltaMode: false,
|
|
15
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
16
|
-
pi: {},
|
|
17
|
-
hasTool: async () => false,
|
|
18
|
-
log: () => { },
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
describe("spellcheck runner", () => {
|
|
22
|
-
const require = createRequire(import.meta.url);
|
|
23
|
-
it("should have correct runner definition", async () => {
|
|
24
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
25
|
-
const runner = spellcheckModule.default;
|
|
26
|
-
expect(runner.id).toBe("spellcheck");
|
|
27
|
-
expect(runner.appliesTo).toEqual(["markdown"]);
|
|
28
|
-
expect(runner.priority).toBe(30);
|
|
29
|
-
expect(runner.enabledByDefault).toBe(true);
|
|
30
|
-
expect(runner.skipTestFiles).toBe(false); // Check docs in test files too
|
|
31
|
-
});
|
|
32
|
-
it("should detect typos-cli availability", () => {
|
|
33
|
-
const { spawnSync } = require("node:child_process");
|
|
34
|
-
const result = spawnSync("typos", ["--version"], {
|
|
35
|
-
encoding: "utf-8",
|
|
36
|
-
timeout: 10000,
|
|
37
|
-
shell: true,
|
|
38
|
-
});
|
|
39
|
-
expect(result.error || result.status !== 0 ? "not available" : "available").toBeTruthy(); // May or may not be installed
|
|
40
|
-
});
|
|
41
|
-
it("should detect typos in markdown content", async () => {
|
|
42
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_test_${Date.now()}.md`);
|
|
43
|
-
fs.writeFileSync(tmpFile, `# README
|
|
44
|
-
|
|
45
|
-
This is a documnet about recieving data.
|
|
46
|
-
The seperation of concerns is important.
|
|
47
|
-
`);
|
|
48
|
-
try {
|
|
49
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
50
|
-
const runner = spellcheckModule.default;
|
|
51
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
52
|
-
// If typos-cli is installed, should detect typos
|
|
53
|
-
// If not installed, will be skipped
|
|
54
|
-
if (result.status !== "skipped") {
|
|
55
|
-
// Should detect at least "documnet" and "recieving"
|
|
56
|
-
expect(result.diagnostics.length).toBeGreaterThanOrEqual(1);
|
|
57
|
-
expect(result.diagnostics.some((d) => d.tool === "typos" &&
|
|
58
|
-
(d.message.includes("documnet") ||
|
|
59
|
-
d.message.includes("recieving") ||
|
|
60
|
-
d.message.includes("seperation")))).toBe(true);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
finally {
|
|
64
|
-
if (fs.existsSync(tmpFile)) {
|
|
65
|
-
fs.unlinkSync(tmpFile);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
it("should suggest corrections for typos", async () => {
|
|
70
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_fix_${Date.now()}.md`);
|
|
71
|
-
fs.writeFileSync(tmpFile, `# Test
|
|
72
|
-
|
|
73
|
-
This is a recieving test.
|
|
74
|
-
`);
|
|
75
|
-
try {
|
|
76
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
77
|
-
const runner = spellcheckModule.default;
|
|
78
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
79
|
-
if (result.status !== "skipped" && result.diagnostics.length > 0) {
|
|
80
|
-
// Should have fix suggestions
|
|
81
|
-
const fixableDiags = result.diagnostics.filter((d) => d.fixable);
|
|
82
|
-
expect(fixableDiags.length).toBeGreaterThanOrEqual(1);
|
|
83
|
-
expect(fixableDiags.some((d) => d.fixSuggestion?.toLowerCase().includes("receive"))).toBe(true);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
if (fs.existsSync(tmpFile)) {
|
|
88
|
-
fs.unlinkSync(tmpFile);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
it("should pass clean markdown files", async () => {
|
|
93
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_ok_${Date.now()}.md`);
|
|
94
|
-
fs.writeFileSync(tmpFile, `# Clean README
|
|
95
|
-
|
|
96
|
-
This is a correct document about receiving data.
|
|
97
|
-
The separation of concerns is important.
|
|
98
|
-
All spelling is proper in this file.
|
|
99
|
-
`);
|
|
100
|
-
try {
|
|
101
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
102
|
-
const runner = spellcheckModule.default;
|
|
103
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
104
|
-
if (result.status !== "skipped") {
|
|
105
|
-
// Should have no typos
|
|
106
|
-
expect(result.diagnostics.length).toBe(0);
|
|
107
|
-
expect(result.status).toBe("succeeded");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
if (fs.existsSync(tmpFile)) {
|
|
112
|
-
fs.unlinkSync(tmpFile);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
it("should handle JSON parse errors gracefully", async () => {
|
|
117
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_json_${Date.now()}.md`);
|
|
118
|
-
fs.writeFileSync(tmpFile, `# Test\n\nSimple file.`);
|
|
119
|
-
try {
|
|
120
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
121
|
-
const runner = spellcheckModule.default;
|
|
122
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
123
|
-
// Should not crash on JSON parse issues
|
|
124
|
-
expect(["succeeded", "failed", "skipped"]).toContain(result.status);
|
|
125
|
-
}
|
|
126
|
-
finally {
|
|
127
|
-
if (fs.existsSync(tmpFile)) {
|
|
128
|
-
fs.unlinkSync(tmpFile);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
it("should skip when typos-cli is not available", async () => {
|
|
133
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `spellcheck_skip_${Date.now()}.md`);
|
|
134
|
-
fs.writeFileSync(tmpFile, `# Test\n\nContent with typo: recieve.`);
|
|
135
|
-
try {
|
|
136
|
-
const spellcheckModule = await import("./spellcheck.js");
|
|
137
|
-
const runner = spellcheckModule.default;
|
|
138
|
-
// Check if typos is available
|
|
139
|
-
const { spawnSync } = require("node:child_process");
|
|
140
|
-
const checkResult = spawnSync("typos", ["--version"], {
|
|
141
|
-
encoding: "utf-8",
|
|
142
|
-
timeout: 5000,
|
|
143
|
-
shell: true,
|
|
144
|
-
});
|
|
145
|
-
const isAvailable = !checkResult.error && checkResult.status === 0;
|
|
146
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
147
|
-
if (!isAvailable) {
|
|
148
|
-
expect(result.status).toBe("skipped");
|
|
149
|
-
expect(result.diagnostics).toHaveLength(0);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
if (fs.existsSync(tmpFile)) {
|
|
154
|
-
fs.unlinkSync(tmpFile);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
});
|