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,180 +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: "jsts",
|
|
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("ts-slop runner", () => {
|
|
30
|
-
const require = createRequire(import.meta.url);
|
|
31
|
-
it("should have correct runner definition", async () => {
|
|
32
|
-
const slopModule = await import("./ts-slop.js");
|
|
33
|
-
const runner = slopModule.default;
|
|
34
|
-
expect(runner.id).toBe("ts-slop");
|
|
35
|
-
// NOTE: TS/JS slop is now handled by ast-grep-napi
|
|
36
|
-
// This CLI runner is disabled by default as fallback
|
|
37
|
-
expect(runner.appliesTo).toEqual([]); // Disabled - use ast-grep-napi
|
|
38
|
-
expect(runner.priority).toBe(20);
|
|
39
|
-
expect(runner.enabledByDefault).toBe(false);
|
|
40
|
-
expect(runner.skipTestFiles).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
it("should detect ast-grep availability", () => {
|
|
43
|
-
const { spawnSync } = require("node:child_process");
|
|
44
|
-
const result = spawnSync("npx", ["sg", "--version"], {
|
|
45
|
-
encoding: "utf-8",
|
|
46
|
-
timeout: 10000,
|
|
47
|
-
shell: true,
|
|
48
|
-
});
|
|
49
|
-
expect(result.error || result.status !== 0 ? "not available" : "available").toBe("available");
|
|
50
|
-
});
|
|
51
|
-
it("should detect for-index-length pattern (or other slop)", async () => {
|
|
52
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_for_${Date.now()}.ts`);
|
|
53
|
-
fs.writeFileSync(tmpFile, `// Slop: using index loop instead of for-of
|
|
54
|
-
function processItems(items: string[]) {
|
|
55
|
-
for (let i = 0; i < items.length; i++) {
|
|
56
|
-
console.log(items[i]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
`);
|
|
60
|
-
try {
|
|
61
|
-
const slopModule = await import("./ts-slop.js");
|
|
62
|
-
const runner = slopModule.default;
|
|
63
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
64
|
-
// Should detect at least some slop patterns
|
|
65
|
-
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
66
|
-
expect(result.status).not.toBe("skipped");
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
safeUnlink(tmpFile);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
it("should detect manual Math min/max pattern (or other slop)", async () => {
|
|
73
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_minmax_${Date.now()}.ts`);
|
|
74
|
-
fs.writeFileSync(tmpFile, `// Slop: manual min/max instead of Math
|
|
75
|
-
function getMax(a: number, b: number): number {
|
|
76
|
-
if (a > b) {
|
|
77
|
-
const m = a;
|
|
78
|
-
} else {
|
|
79
|
-
const m = b;
|
|
80
|
-
}
|
|
81
|
-
return m;
|
|
82
|
-
}
|
|
83
|
-
`);
|
|
84
|
-
try {
|
|
85
|
-
const slopModule = await import("./ts-slop.js");
|
|
86
|
-
const runner = slopModule.default;
|
|
87
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
88
|
-
// Should detect at least some slop patterns
|
|
89
|
-
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
90
|
-
expect(result.status).not.toBe("skipped");
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
safeUnlink(tmpFile);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
it("should detect indexOf !== -1 pattern (or other slop)", async () => {
|
|
97
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_indexof_${Date.now()}.ts`);
|
|
98
|
-
fs.writeFileSync(tmpFile, `// Slop: indexOf check instead of includes
|
|
99
|
-
function hasItem(arr: string[], item: string): boolean {
|
|
100
|
-
if (arr.indexOf(item) !== -1) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
`);
|
|
106
|
-
try {
|
|
107
|
-
const slopModule = await import("./ts-slop.js");
|
|
108
|
-
const runner = slopModule.default;
|
|
109
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
110
|
-
// Should detect at least some slop patterns
|
|
111
|
-
// (specific patterns may vary based on ast-grep rule accuracy)
|
|
112
|
-
expect(result.status).not.toBe("skipped");
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
safeUnlink(tmpFile);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
it("should detect array length > 0 pattern", async () => {
|
|
119
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_length_${Date.now()}.ts`);
|
|
120
|
-
fs.writeFileSync(tmpFile, `// Slop: length check instead of truthiness
|
|
121
|
-
function processItems(arr: string[]): void {
|
|
122
|
-
if (arr.length > 0) {
|
|
123
|
-
console.log("has items");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
`);
|
|
127
|
-
try {
|
|
128
|
-
const slopModule = await import("./ts-slop.js");
|
|
129
|
-
const runner = slopModule.default;
|
|
130
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
131
|
-
// This pattern may or may not be detected depending on rule specificity
|
|
132
|
-
// Just verify the scan ran without errors
|
|
133
|
-
expect(result.status).toBe("succeeded");
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
safeUnlink(tmpFile);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
it("should pass clean TypeScript files", async () => {
|
|
140
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_ok_${Date.now()}.ts`);
|
|
141
|
-
fs.writeFileSync(tmpFile, `// Clean TypeScript code
|
|
142
|
-
function processItems(items: string[]): void {
|
|
143
|
-
for (const item of items) {
|
|
144
|
-
console.log(item);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function findMax(a: number, b: number): number {
|
|
149
|
-
return Math.max(a, b);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function contains(arr: string[], item: string): boolean {
|
|
153
|
-
return arr.includes(item);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function hasItems(arr: string[]): boolean {
|
|
157
|
-
return arr.length > 0; // This is actually OK, but let's see
|
|
158
|
-
}
|
|
159
|
-
`);
|
|
160
|
-
try {
|
|
161
|
-
const slopModule = await import("./ts-slop.js");
|
|
162
|
-
const runner = slopModule.default;
|
|
163
|
-
const result = await runner.run(createMockContext(tmpFile));
|
|
164
|
-
// Should have minimal or no slop issues for clean code
|
|
165
|
-
const slopIssues = result.diagnostics.filter((d) => d.tool === "ts-slop");
|
|
166
|
-
// Allow for minor issues - the length check might still trigger
|
|
167
|
-
expect(slopIssues.length).toBeLessThanOrEqual(1);
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
170
|
-
try {
|
|
171
|
-
if (fs.existsSync(tmpFile)) {
|
|
172
|
-
safeUnlink(tmpFile);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
// Ignore cleanup errors
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
});
|
|
@@ -1,230 +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
|
-
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
|
-
});
|
package/clients/dogfood.test.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Meta-test: Run similarity detection on pi-lens codebase
|
|
3
|
-
*
|
|
4
|
-
* This is a "dogfood" test - we run the reuse detection on our own code
|
|
5
|
-
* to see what it finds. Educational and useful for improving the algorithm!
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "node:fs/promises";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { glob } from "glob";
|
|
11
|
-
import { beforeAll, describe, expect, it } from "vitest";
|
|
12
|
-
import { buildProjectIndex, findSimilarFunctions, } from "./project-index.js";
|
|
13
|
-
import { calculateSimilarity as calcMatrixSimilarity } from "./state-matrix.js";
|
|
14
|
-
// Find project root by looking for package.json
|
|
15
|
-
async function findProjectRoot(startDir) {
|
|
16
|
-
let dir = startDir;
|
|
17
|
-
while (dir !== path.dirname(dir)) {
|
|
18
|
-
try {
|
|
19
|
-
await fs.access(path.join(dir, "package.json"));
|
|
20
|
-
return dir;
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
dir = path.dirname(dir);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
throw new Error("Could not find project root (no package.json)");
|
|
27
|
-
}
|
|
28
|
-
// Test a known similar pair
|
|
29
|
-
const _SIMILAR_FUNCTIONS = {
|
|
30
|
-
description: "Extracting similar logic patterns in pi-lens",
|
|
31
|
-
pairs: [
|
|
32
|
-
{
|
|
33
|
-
name: "runners/index.ts pattern",
|
|
34
|
-
files: [
|
|
35
|
-
"clients/dispatch/runners/index.ts",
|
|
36
|
-
"clients/dispatch/runners/architect.ts",
|
|
37
|
-
],
|
|
38
|
-
expected: "High similarity in runner registration patterns",
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: "Client pattern",
|
|
42
|
-
files: ["clients/typescript-client.ts", "clients/biome-client.ts"],
|
|
43
|
-
expected: "Similar client structures",
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
};
|
|
47
|
-
describe("🐶 Dogfood Test: Similarity on pi-lens codebase", () => {
|
|
48
|
-
let index;
|
|
49
|
-
let projectRoot;
|
|
50
|
-
beforeAll(async () => {
|
|
51
|
-
// Find project root
|
|
52
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
53
|
-
projectRoot = await findProjectRoot(__dirname);
|
|
54
|
-
// Build index of the entire codebase
|
|
55
|
-
console.log("\n🏗️ Building index of pi-lens codebase...");
|
|
56
|
-
console.log(` Project root: ${projectRoot}`);
|
|
57
|
-
const files = await glob("clients/**/*.ts", {
|
|
58
|
-
cwd: projectRoot,
|
|
59
|
-
ignore: ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"],
|
|
60
|
-
});
|
|
61
|
-
console.log(` Found ${files.length} source files`);
|
|
62
|
-
const absoluteFiles = files.map((f) => path.join(projectRoot, f));
|
|
63
|
-
index = await buildProjectIndex(projectRoot, absoluteFiles);
|
|
64
|
-
console.log(` Indexed ${index.entries.size} functions`);
|
|
65
|
-
// Show some indexed functions
|
|
66
|
-
const sample = Array.from(index.entries.values()).slice(0, 5);
|
|
67
|
-
console.log("\n📋 Sample indexed functions:");
|
|
68
|
-
sample.forEach((e, i) => {
|
|
69
|
-
console.log(` ${i + 1}. ${e.id} (${e.transitionCount} transitions)`);
|
|
70
|
-
});
|
|
71
|
-
}, 30000); // 30s timeout for indexing
|
|
72
|
-
describe("Index validation", () => {
|
|
73
|
-
it("should have indexed functions", () => {
|
|
74
|
-
expect(index.entries.size).toBeGreaterThan(0);
|
|
75
|
-
console.log(`\n✅ Indexed ${index.entries.size} functions`);
|
|
76
|
-
});
|
|
77
|
-
it("should have functions with >20 transitions", () => {
|
|
78
|
-
const complex = Array.from(index.entries.values()).filter((e) => e.transitionCount >= 20);
|
|
79
|
-
expect(complex.length).toBeGreaterThan(0);
|
|
80
|
-
console.log(`\n✅ ${complex.length} functions pass complexity guardrail`);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
describe("Find similar functions in our own codebase", () => {
|
|
84
|
-
it("should find similar patterns among runners", async () => {
|
|
85
|
-
// Find runner files
|
|
86
|
-
const runnerEntries = Array.from(index.entries.values()).filter((e) => e.filePath.includes("dispatch/runners/"));
|
|
87
|
-
console.log(`\n🔍 Testing ${runnerEntries.length} runner functions`);
|
|
88
|
-
const similarities = [];
|
|
89
|
-
// Compare each pair
|
|
90
|
-
for (let i = 0; i < runnerEntries.length; i++) {
|
|
91
|
-
for (let j = i + 1; j < runnerEntries.length; j++) {
|
|
92
|
-
const entry1 = runnerEntries[i];
|
|
93
|
-
const entry2 = runnerEntries[j];
|
|
94
|
-
// Skip if same file
|
|
95
|
-
if (entry1.filePath === entry2.filePath)
|
|
96
|
-
continue;
|
|
97
|
-
const sim = calcMatrixSimilarity(entry1.matrix, entry2.matrix);
|
|
98
|
-
if (sim >= 0.75) {
|
|
99
|
-
similarities.push({
|
|
100
|
-
func1: entry1.id,
|
|
101
|
-
func2: entry2.id,
|
|
102
|
-
similarity: sim,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Sort by similarity
|
|
108
|
-
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
109
|
-
console.log(`\n📊 Found ${similarities.length} similar pairs (>75%):`);
|
|
110
|
-
similarities.slice(0, 5).forEach((s, i) => {
|
|
111
|
-
console.log(` ${i + 1}. ${s.func1} ↔ ${s.func2}`);
|
|
112
|
-
console.log(` Similarity: ${(s.similarity * 100).toFixed(1)}%`);
|
|
113
|
-
});
|
|
114
|
-
// Log findings but don't fail - this is exploratory
|
|
115
|
-
expect(similarities.length).toBeGreaterThanOrEqual(0);
|
|
116
|
-
});
|
|
117
|
-
it("should find similar client patterns", async () => {
|
|
118
|
-
const clientEntries = Array.from(index.entries.values()).filter((e) => e.filePath.includes("clients/") &&
|
|
119
|
-
e.filePath.includes("-client.ts") &&
|
|
120
|
-
!e.filePath.includes("test"));
|
|
121
|
-
console.log(`\n🔍 Testing ${clientEntries.length} client functions`);
|
|
122
|
-
const similarities = [];
|
|
123
|
-
for (let i = 0; i < clientEntries.length; i++) {
|
|
124
|
-
for (let j = i + 1; j < clientEntries.length; j++) {
|
|
125
|
-
const entry1 = clientEntries[i];
|
|
126
|
-
const entry2 = clientEntries[j];
|
|
127
|
-
if (entry1.filePath === entry2.filePath)
|
|
128
|
-
continue;
|
|
129
|
-
const sim = calcMatrixSimilarity(entry1.matrix, entry2.matrix);
|
|
130
|
-
if (sim >= 0.75) {
|
|
131
|
-
similarities.push({
|
|
132
|
-
func1: entry1.id,
|
|
133
|
-
func2: entry2.id,
|
|
134
|
-
similarity: sim,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
140
|
-
console.log(`\n📊 Found ${similarities.length} similar client patterns (>75%):`);
|
|
141
|
-
similarities.slice(0, 3).forEach((s, i) => {
|
|
142
|
-
console.log(` ${i + 1}. ${s.func1} ↔ ${s.func2}`);
|
|
143
|
-
console.log(` Similarity: ${(s.similarity * 100).toFixed(1)}%`);
|
|
144
|
-
});
|
|
145
|
-
expect(similarities.length).toBeGreaterThanOrEqual(0);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe("Find potential refactor opportunities", () => {
|
|
149
|
-
it("should identify duplicate utility functions", () => {
|
|
150
|
-
// Look for functions with very high similarity (>90%)
|
|
151
|
-
const entries = Array.from(index.entries.values());
|
|
152
|
-
const seenPairs = new Set(); // Deduplicate A→B and B→A
|
|
153
|
-
const duplicates = [];
|
|
154
|
-
for (const entry of entries) {
|
|
155
|
-
const matches = findSimilarFunctions(entry.matrix, index, 0.9, 3);
|
|
156
|
-
for (const match of matches) {
|
|
157
|
-
if (match.targetId === entry.id)
|
|
158
|
-
continue;
|
|
159
|
-
// Canonical pair key (sorted to avoid A,B and B,A)
|
|
160
|
-
const pairKey = [entry.id, match.targetId].sort().join("::");
|
|
161
|
-
if (seenPairs.has(pairKey))
|
|
162
|
-
continue;
|
|
163
|
-
seenPairs.add(pairKey);
|
|
164
|
-
duplicates.push({
|
|
165
|
-
func: entry.id,
|
|
166
|
-
similarTo: match.targetId,
|
|
167
|
-
similarity: match.similarity,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
console.log(`\n🎯 Found ${duplicates.length} unique potential duplicates (>90%):`);
|
|
172
|
-
duplicates.slice(0, 5).forEach((d, i) => {
|
|
173
|
-
console.log(` ${i + 1}. ${d.func}`);
|
|
174
|
-
console.log(` Similar to: ${d.similarTo}`);
|
|
175
|
-
console.log(` Match: ${(d.similarity * 100).toFixed(1)}%`);
|
|
176
|
-
});
|
|
177
|
-
// This is informational - we don't assert on it
|
|
178
|
-
expect(true).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
describe("Complexity distribution", () => {
|
|
182
|
-
it("should show transition count distribution", () => {
|
|
183
|
-
const entries = Array.from(index.entries.values());
|
|
184
|
-
const transitionCounts = entries.map((e) => e.transitionCount);
|
|
185
|
-
const avg = transitionCounts.reduce((a, b) => a + b, 0) / transitionCounts.length;
|
|
186
|
-
const min = Math.min(...transitionCounts);
|
|
187
|
-
const max = Math.max(...transitionCounts);
|
|
188
|
-
const belowThreshold = transitionCounts.filter((c) => c < 20).length;
|
|
189
|
-
const aboveThreshold = transitionCounts.filter((c) => c >= 20).length;
|
|
190
|
-
console.log("\n📊 Complexity Distribution:");
|
|
191
|
-
console.log(` Total functions: ${entries.length}`);
|
|
192
|
-
console.log(` Below threshold (<20): ${belowThreshold}`);
|
|
193
|
-
console.log(` Above threshold (≥20): ${aboveThreshold}`);
|
|
194
|
-
console.log(` Min transitions: ${min}`);
|
|
195
|
-
console.log(` Max transitions: ${max}`);
|
|
196
|
-
console.log(` Average: ${avg.toFixed(1)}`);
|
|
197
|
-
// Most functions should pass the guardrail
|
|
198
|
-
expect(aboveThreshold).toBeGreaterThan(0);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
});
|