pi-lens 3.6.2 → 3.6.4
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 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { JscpdClient } from "./jscpd-client.js";
|
|
3
|
-
import { createTempFile, setupTestEnvironment } from "./test-utils.js";
|
|
4
|
-
|
|
5
|
-
describe("JscpdClient", () => {
|
|
6
|
-
let client: JscpdClient;
|
|
7
|
-
let tmpDir: string;
|
|
8
|
-
let cleanup: () => void;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
client = new JscpdClient();
|
|
12
|
-
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-jscpd-test-"));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
cleanup();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("isAvailable", () => {
|
|
24
|
-
it("should check jscpd availability", () => {
|
|
25
|
-
const available = client.isAvailable();
|
|
26
|
-
expect(typeof available).toBe("boolean");
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("scan", () => {
|
|
31
|
-
it("should return success=false when not available", () => {
|
|
32
|
-
// Create a mock that returns false
|
|
33
|
-
const mockClient = new JscpdClient();
|
|
34
|
-
if (mockClient.isAvailable()) return; // Skip if available
|
|
35
|
-
|
|
36
|
-
const result = mockClient.scan(tmpDir);
|
|
37
|
-
expect(result.success).toBe(false);
|
|
38
|
-
expect(result.clones).toEqual([]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should detect duplicate code blocks", { timeout: 15000 }, () => {
|
|
42
|
-
if (!client.isAvailable()) return;
|
|
43
|
-
|
|
44
|
-
// Create identical code blocks in different files
|
|
45
|
-
const duplicateCode = `
|
|
46
|
-
function processData(data: number[]): number {
|
|
47
|
-
let sum = 0;
|
|
48
|
-
for (let i = 0; i < data.length; i++) {
|
|
49
|
-
sum += data[i];
|
|
50
|
-
}
|
|
51
|
-
return sum;
|
|
52
|
-
}
|
|
53
|
-
`;
|
|
54
|
-
createTempFile(tmpDir, "file1.ts", duplicateCode);
|
|
55
|
-
createTempFile(tmpDir, "file2.ts", duplicateCode);
|
|
56
|
-
|
|
57
|
-
const result = client.scan(tmpDir, 3, 20); // Lower thresholds for test
|
|
58
|
-
|
|
59
|
-
expect(result.success).toBe(true);
|
|
60
|
-
// May or may not detect clones depending on jscpd behavior
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("formatResult", () => {
|
|
65
|
-
it("should return empty string for no success", () => {
|
|
66
|
-
const result = {
|
|
67
|
-
success: false,
|
|
68
|
-
clones: [],
|
|
69
|
-
duplicatedLines: 0,
|
|
70
|
-
totalLines: 100,
|
|
71
|
-
percentage: 0,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
expect(client.formatResult(result)).toBe("");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should return empty string for no clones", () => {
|
|
78
|
-
const result = {
|
|
79
|
-
success: true,
|
|
80
|
-
clones: [],
|
|
81
|
-
duplicatedLines: 0,
|
|
82
|
-
totalLines: 100,
|
|
83
|
-
percentage: 0,
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
expect(client.formatResult(result)).toBe("");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should format clones for display", () => {
|
|
90
|
-
const result = {
|
|
91
|
-
success: true,
|
|
92
|
-
clones: [
|
|
93
|
-
{
|
|
94
|
-
fileA: "src/file1.ts",
|
|
95
|
-
startA: 10,
|
|
96
|
-
fileB: "src/file2.ts",
|
|
97
|
-
startB: 20,
|
|
98
|
-
lines: 15,
|
|
99
|
-
tokens: 50,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
fileA: "src/file3.ts",
|
|
103
|
-
startA: 5,
|
|
104
|
-
fileB: "src/file4.ts",
|
|
105
|
-
startB: 12,
|
|
106
|
-
lines: 8,
|
|
107
|
-
tokens: 30,
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
duplicatedLines: 23,
|
|
111
|
-
totalLines: 500,
|
|
112
|
-
percentage: 4.6,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const formatted = client.formatResult(result);
|
|
116
|
-
expect(formatted).toContain("jscpd");
|
|
117
|
-
expect(formatted).toContain("2 duplicate block(s)");
|
|
118
|
-
expect(formatted).toContain("4.6%");
|
|
119
|
-
expect(formatted).toContain("15 lines");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("should truncate long clone lists", () => {
|
|
123
|
-
const clones = Array.from({ length: 10 }, (_, i) => ({
|
|
124
|
-
fileA: `file${i}a.ts`,
|
|
125
|
-
startA: 1,
|
|
126
|
-
fileB: `file${i}b.ts`,
|
|
127
|
-
startB: 1,
|
|
128
|
-
lines: 5,
|
|
129
|
-
tokens: 20,
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
const result = {
|
|
133
|
-
success: true,
|
|
134
|
-
clones,
|
|
135
|
-
duplicatedLines: 50,
|
|
136
|
-
totalLines: 1000,
|
|
137
|
-
percentage: 5,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const formatted = client.formatResult(result, 8);
|
|
141
|
-
expect(formatted).toContain("...");
|
|
142
|
-
expect(formatted).toContain("2 more");
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
package/clients/knip-client.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Knip Client for pi-local
|
|
3
|
-
*
|
|
4
|
-
* Detects unused exports, files, dependencies, and more.
|
|
5
|
-
* Essential for safe refactoring — I need to know what's dead code
|
|
6
|
-
* before I can clean it up.
|
|
7
|
-
*
|
|
8
|
-
* Requires: npm install -D knip
|
|
9
|
-
* Docs: https://knip.dev/
|
|
10
|
-
*/
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
13
|
-
// --- Client ---
|
|
14
|
-
export class KnipClient {
|
|
15
|
-
constructor(verbose = false) {
|
|
16
|
-
this.knipAvailable = null;
|
|
17
|
-
this.log = verbose
|
|
18
|
-
? (msg) => console.error(`[knip] ${msg}`)
|
|
19
|
-
: () => { };
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Check if knip CLI is available, auto-install if not
|
|
23
|
-
*/
|
|
24
|
-
async ensureAvailable() {
|
|
25
|
-
// Fast path: already checked
|
|
26
|
-
if (this.knipAvailable !== null)
|
|
27
|
-
return this.knipAvailable;
|
|
28
|
-
// Check if available in PATH (fast)
|
|
29
|
-
const pathResult = safeSpawn("knip", ["--version"], {
|
|
30
|
-
timeout: 5000,
|
|
31
|
-
});
|
|
32
|
-
if (!pathResult.error && pathResult.status === 0) {
|
|
33
|
-
this.knipAvailable = true;
|
|
34
|
-
this.log("Knip found in PATH");
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
// Auto-install via pi-lens installer
|
|
38
|
-
this.log("Knip not found, attempting auto-install...");
|
|
39
|
-
const { ensureTool } = await import("./installer/index.js");
|
|
40
|
-
const installedPath = await ensureTool("knip");
|
|
41
|
-
if (installedPath) {
|
|
42
|
-
this.knipAvailable = true;
|
|
43
|
-
this.log(`Knip auto-installed: ${installedPath}`);
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
this.knipAvailable = false;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Check if knip CLI is available (legacy sync method)
|
|
51
|
-
* Prefer ensureAvailable() for auto-install behavior
|
|
52
|
-
*/
|
|
53
|
-
isAvailable() {
|
|
54
|
-
if (this.knipAvailable !== null)
|
|
55
|
-
return this.knipAvailable;
|
|
56
|
-
const result = safeSpawn("npx", ["knip", "--version"], {
|
|
57
|
-
timeout: 10000,
|
|
58
|
-
});
|
|
59
|
-
this.knipAvailable = !result.error && result.status === 0;
|
|
60
|
-
if (this.knipAvailable) {
|
|
61
|
-
this.log(`Knip available`);
|
|
62
|
-
}
|
|
63
|
-
return this.knipAvailable;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Run knip analysis on the project
|
|
67
|
-
*/
|
|
68
|
-
analyze(cwd, ignore) {
|
|
69
|
-
if (!this.isAvailable()) {
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
issues: [],
|
|
73
|
-
unusedExports: [],
|
|
74
|
-
unusedFiles: [],
|
|
75
|
-
unusedDeps: [],
|
|
76
|
-
unlistedDeps: [],
|
|
77
|
-
summary: "Knip not available. Install with: npm install -D knip",
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const targetDir = cwd || process.cwd();
|
|
81
|
-
try {
|
|
82
|
-
const args = [
|
|
83
|
-
"knip",
|
|
84
|
-
"--reporter=json",
|
|
85
|
-
"--include",
|
|
86
|
-
"files,exports,types,dependencies,unlisted",
|
|
87
|
-
];
|
|
88
|
-
if (ignore && ignore.length > 0) {
|
|
89
|
-
args.push("--ignore", ignore.join(","));
|
|
90
|
-
}
|
|
91
|
-
const result = safeSpawn("npx", args, {
|
|
92
|
-
timeout: 30000,
|
|
93
|
-
cwd: targetDir,
|
|
94
|
-
});
|
|
95
|
-
// Knip exits 0 on success (even with issues), 1 on errors
|
|
96
|
-
const output = result.stdout || "";
|
|
97
|
-
this.log(`Knip output length: ${output.length}`);
|
|
98
|
-
if (output.length < 500) {
|
|
99
|
-
this.log(`Knip output sample: ${output}`);
|
|
100
|
-
}
|
|
101
|
-
if (!output.trim()) {
|
|
102
|
-
return {
|
|
103
|
-
success: true,
|
|
104
|
-
issues: [],
|
|
105
|
-
unusedExports: [],
|
|
106
|
-
unusedFiles: [],
|
|
107
|
-
unusedDeps: [],
|
|
108
|
-
unlistedDeps: [],
|
|
109
|
-
summary: "No issues found",
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
return this.parseOutput(output);
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
this.log(`Analysis error: ${err.message}`);
|
|
116
|
-
return {
|
|
117
|
-
success: false,
|
|
118
|
-
issues: [],
|
|
119
|
-
unusedExports: [],
|
|
120
|
-
unusedFiles: [],
|
|
121
|
-
unusedDeps: [],
|
|
122
|
-
unlistedDeps: [],
|
|
123
|
-
summary: `Error: ${err.message}`,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Find unused exports in a specific file
|
|
129
|
-
*/
|
|
130
|
-
findUnusedExports(filePath) {
|
|
131
|
-
const result = this.analyze(path.dirname(filePath));
|
|
132
|
-
const basename = path.basename(filePath);
|
|
133
|
-
return result.unusedExports
|
|
134
|
-
.filter((e) => e.file?.includes(basename))
|
|
135
|
-
.map((e) => e.name);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Format results for LLM consumption
|
|
139
|
-
*/
|
|
140
|
-
formatResult(result, maxItems = 20) {
|
|
141
|
-
if (!result.success)
|
|
142
|
-
return `[Knip] ${result.summary}`;
|
|
143
|
-
if (result.issues.length === 0)
|
|
144
|
-
return "";
|
|
145
|
-
let output = `[Knip] ${result.issues.length} issue(s)`;
|
|
146
|
-
if (result.unusedExports.length)
|
|
147
|
-
output += ` — ${result.unusedExports.length} unused export(s)`;
|
|
148
|
-
if (result.unusedFiles.length)
|
|
149
|
-
output += ` — ${result.unusedFiles.length} unused file(s)`;
|
|
150
|
-
if (result.unusedDeps.length)
|
|
151
|
-
output += ` — ${result.unusedDeps.length} unused dep(s)`;
|
|
152
|
-
if (result.unlistedDeps.length)
|
|
153
|
-
output += ` — ${result.unlistedDeps.length} unlisted dep(s)`;
|
|
154
|
-
output += ":\n";
|
|
155
|
-
// Show unused exports first (most useful for refactoring)
|
|
156
|
-
if (result.unusedExports.length > 0) {
|
|
157
|
-
output += "\n Unused exports:\n";
|
|
158
|
-
for (const issue of result.unusedExports.slice(0, maxItems)) {
|
|
159
|
-
const loc = issue.file ? ` (${path.basename(issue.file)})` : "";
|
|
160
|
-
output += ` - ${issue.name}${loc}\n`;
|
|
161
|
-
}
|
|
162
|
-
if (result.unusedExports.length > maxItems) {
|
|
163
|
-
output += ` ... and ${result.unusedExports.length - maxItems} more\n`;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// Show unused files
|
|
167
|
-
if (result.unusedFiles.length > 0) {
|
|
168
|
-
output += "\n Unused files:\n";
|
|
169
|
-
for (const issue of result.unusedFiles.slice(0, 10)) {
|
|
170
|
-
output += ` - ${issue.name}\n`;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Show unused deps (might be worth removing)
|
|
174
|
-
if (result.unusedDeps.length > 0) {
|
|
175
|
-
output += "\n Unused dependencies:\n";
|
|
176
|
-
for (const issue of result.unusedDeps) {
|
|
177
|
-
output += ` - ${issue.package || issue.name}\n`;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return output;
|
|
181
|
-
}
|
|
182
|
-
// --- Internal ---
|
|
183
|
-
parseOutput(output) {
|
|
184
|
-
try {
|
|
185
|
-
const data = JSON.parse(output);
|
|
186
|
-
const issues = [];
|
|
187
|
-
const unusedExports = [];
|
|
188
|
-
const unusedFiles = [];
|
|
189
|
-
const unusedDeps = [];
|
|
190
|
-
const unlistedDeps = [];
|
|
191
|
-
// Knip JSON format: { issues: [ { file, exports:[], files:[], dependencies:[], ... } ] }
|
|
192
|
-
const fileEntries = data.issues ?? [];
|
|
193
|
-
for (const entry of fileEntries) {
|
|
194
|
-
const file = entry.file ?? "";
|
|
195
|
-
const push = (arr, type, target) => {
|
|
196
|
-
for (const item of arr) {
|
|
197
|
-
const issue = {
|
|
198
|
-
type,
|
|
199
|
-
name: item.name ?? item.symbol ?? String(item),
|
|
200
|
-
file,
|
|
201
|
-
line: item.line,
|
|
202
|
-
package: item.package,
|
|
203
|
-
};
|
|
204
|
-
issues.push(issue);
|
|
205
|
-
target.push(issue);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
push(entry.exports ?? [], "export", unusedExports);
|
|
209
|
-
push(entry.types ?? [], "export", unusedExports);
|
|
210
|
-
push(entry.files ?? [], "file", unusedFiles);
|
|
211
|
-
push(entry.dependencies ?? [], "dependency", unusedDeps);
|
|
212
|
-
push(entry.devDependencies ?? [], "devDependency", unusedDeps);
|
|
213
|
-
push(entry.unlisted ?? [], "unlisted", unlistedDeps);
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
success: true,
|
|
217
|
-
issues,
|
|
218
|
-
unusedExports,
|
|
219
|
-
unusedFiles,
|
|
220
|
-
unusedDeps,
|
|
221
|
-
unlistedDeps,
|
|
222
|
-
summary: `Found ${issues.length} issues`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
void err;
|
|
227
|
-
this.log("Failed to parse knip JSON output");
|
|
228
|
-
return {
|
|
229
|
-
success: false,
|
|
230
|
-
issues: [],
|
|
231
|
-
unusedExports: [],
|
|
232
|
-
unusedFiles: [],
|
|
233
|
-
unusedDeps: [],
|
|
234
|
-
unlistedDeps: [],
|
|
235
|
-
summary: "Failed to parse output",
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { KnipClient } from "./knip-client.js";
|
|
3
|
-
import { setupTestEnvironment } from "./test-utils.js";
|
|
4
|
-
describe("KnipClient", () => {
|
|
5
|
-
let client;
|
|
6
|
-
let tmpDir;
|
|
7
|
-
let cleanup;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
client = new KnipClient();
|
|
10
|
-
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-knip-test-"));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
cleanup();
|
|
14
|
-
});
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
describe("isAvailable", () => {
|
|
19
|
-
it("should check knip availability", () => {
|
|
20
|
-
const available = client.isAvailable();
|
|
21
|
-
expect(typeof available).toBe("boolean");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
describe("analyze", () => {
|
|
25
|
-
it("should return success=false when not available", () => {
|
|
26
|
-
const mockClient = new KnipClient();
|
|
27
|
-
if (mockClient.isAvailable())
|
|
28
|
-
return;
|
|
29
|
-
const result = mockClient.analyze(tmpDir);
|
|
30
|
-
expect(result.success).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe("formatResult", () => {
|
|
34
|
-
it("should return empty string for no issues", () => {
|
|
35
|
-
const result = {
|
|
36
|
-
success: true,
|
|
37
|
-
issues: [],
|
|
38
|
-
unusedExports: [],
|
|
39
|
-
unusedFiles: [],
|
|
40
|
-
unusedDeps: [],
|
|
41
|
-
unlistedDeps: [],
|
|
42
|
-
summary: "",
|
|
43
|
-
};
|
|
44
|
-
expect(client.formatResult(result)).toBe("");
|
|
45
|
-
});
|
|
46
|
-
it("should format unused exports", () => {
|
|
47
|
-
const result = {
|
|
48
|
-
success: true,
|
|
49
|
-
issues: [
|
|
50
|
-
{ type: "export", name: "unusedFunc", file: "utils.ts" },
|
|
51
|
-
],
|
|
52
|
-
unusedExports: [
|
|
53
|
-
{ type: "export", name: "unusedFunc", file: "utils.ts" },
|
|
54
|
-
],
|
|
55
|
-
unusedFiles: [],
|
|
56
|
-
unusedDeps: [],
|
|
57
|
-
unlistedDeps: [],
|
|
58
|
-
summary: "Found 1 issue",
|
|
59
|
-
};
|
|
60
|
-
const formatted = client.formatResult(result);
|
|
61
|
-
expect(formatted).toContain("Knip");
|
|
62
|
-
expect(formatted).toContain("unusedFunc");
|
|
63
|
-
});
|
|
64
|
-
it("should format unused dependencies", () => {
|
|
65
|
-
const result = {
|
|
66
|
-
success: true,
|
|
67
|
-
issues: [{ type: "dependency", name: "lodash" }],
|
|
68
|
-
unusedExports: [],
|
|
69
|
-
unusedFiles: [],
|
|
70
|
-
unusedDeps: [{ type: "dependency", name: "lodash" }],
|
|
71
|
-
unlistedDeps: [],
|
|
72
|
-
summary: "",
|
|
73
|
-
};
|
|
74
|
-
const formatted = client.formatResult(result);
|
|
75
|
-
expect(formatted).toContain("lodash");
|
|
76
|
-
expect(formatted).toContain("unused dep");
|
|
77
|
-
});
|
|
78
|
-
it("should show unlisted dependencies count", () => {
|
|
79
|
-
const result = {
|
|
80
|
-
success: true,
|
|
81
|
-
issues: [{ type: "unlisted", name: "axios" }],
|
|
82
|
-
unusedExports: [],
|
|
83
|
-
unusedFiles: [],
|
|
84
|
-
unusedDeps: [],
|
|
85
|
-
unlistedDeps: [{ type: "unlisted", name: "axios" }],
|
|
86
|
-
summary: "",
|
|
87
|
-
};
|
|
88
|
-
const formatted = client.formatResult(result);
|
|
89
|
-
expect(formatted).toContain("unlisted dep");
|
|
90
|
-
});
|
|
91
|
-
it("should format multiple issue types", () => {
|
|
92
|
-
const result = {
|
|
93
|
-
success: true,
|
|
94
|
-
issues: [
|
|
95
|
-
{ type: "export", name: "func1", file: "a.ts" },
|
|
96
|
-
{ type: "file", name: "old.ts" },
|
|
97
|
-
],
|
|
98
|
-
unusedExports: [
|
|
99
|
-
{ type: "export", name: "func1", file: "a.ts" },
|
|
100
|
-
{ type: "export", name: "func2", file: "b.ts" },
|
|
101
|
-
],
|
|
102
|
-
unusedFiles: [{ type: "file", name: "old.ts" }],
|
|
103
|
-
unusedDeps: [],
|
|
104
|
-
unlistedDeps: [],
|
|
105
|
-
summary: "",
|
|
106
|
-
};
|
|
107
|
-
const formatted = client.formatResult(result);
|
|
108
|
-
expect(formatted).toContain("2 unused export(s)");
|
|
109
|
-
expect(formatted).toContain("1 unused file(s)");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { KnipClient } from "./knip-client.js";
|
|
3
|
-
import { setupTestEnvironment } from "./test-utils.js";
|
|
4
|
-
|
|
5
|
-
describe("KnipClient", () => {
|
|
6
|
-
let client: KnipClient;
|
|
7
|
-
let tmpDir: string;
|
|
8
|
-
let cleanup: () => void;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
client = new KnipClient();
|
|
12
|
-
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-knip-test-"));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
cleanup();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("isAvailable", () => {
|
|
24
|
-
it("should check knip availability", () => {
|
|
25
|
-
const available = client.isAvailable();
|
|
26
|
-
expect(typeof available).toBe("boolean");
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("analyze", () => {
|
|
31
|
-
it("should return success=false when not available", () => {
|
|
32
|
-
const mockClient = new KnipClient();
|
|
33
|
-
if (mockClient.isAvailable()) return;
|
|
34
|
-
|
|
35
|
-
const result = mockClient.analyze(tmpDir);
|
|
36
|
-
expect(result.success).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("formatResult", () => {
|
|
41
|
-
it("should return empty string for no issues", () => {
|
|
42
|
-
const result = {
|
|
43
|
-
success: true,
|
|
44
|
-
issues: [],
|
|
45
|
-
unusedExports: [],
|
|
46
|
-
unusedFiles: [],
|
|
47
|
-
unusedDeps: [],
|
|
48
|
-
unlistedDeps: [],
|
|
49
|
-
summary: "",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
expect(client.formatResult(result)).toBe("");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("should format unused exports", () => {
|
|
56
|
-
const result = {
|
|
57
|
-
success: true,
|
|
58
|
-
issues: [
|
|
59
|
-
{ type: "export" as const, name: "unusedFunc", file: "utils.ts" },
|
|
60
|
-
],
|
|
61
|
-
unusedExports: [
|
|
62
|
-
{ type: "export" as const, name: "unusedFunc", file: "utils.ts" },
|
|
63
|
-
],
|
|
64
|
-
unusedFiles: [],
|
|
65
|
-
unusedDeps: [],
|
|
66
|
-
unlistedDeps: [],
|
|
67
|
-
summary: "Found 1 issue",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const formatted = client.formatResult(result);
|
|
71
|
-
expect(formatted).toContain("Knip");
|
|
72
|
-
expect(formatted).toContain("unusedFunc");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should format unused dependencies", () => {
|
|
76
|
-
const result = {
|
|
77
|
-
success: true,
|
|
78
|
-
issues: [{ type: "dependency" as const, name: "lodash" }],
|
|
79
|
-
unusedExports: [],
|
|
80
|
-
unusedFiles: [],
|
|
81
|
-
unusedDeps: [{ type: "dependency" as const, name: "lodash" }],
|
|
82
|
-
unlistedDeps: [],
|
|
83
|
-
summary: "",
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const formatted = client.formatResult(result);
|
|
87
|
-
expect(formatted).toContain("lodash");
|
|
88
|
-
expect(formatted).toContain("unused dep");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("should show unlisted dependencies count", () => {
|
|
92
|
-
const result = {
|
|
93
|
-
success: true,
|
|
94
|
-
issues: [{ type: "unlisted" as const, name: "axios" }],
|
|
95
|
-
unusedExports: [],
|
|
96
|
-
unusedFiles: [],
|
|
97
|
-
unusedDeps: [],
|
|
98
|
-
unlistedDeps: [{ type: "unlisted" as const, name: "axios" }],
|
|
99
|
-
summary: "",
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const formatted = client.formatResult(result);
|
|
103
|
-
expect(formatted).toContain("unlisted dep");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should format multiple issue types", () => {
|
|
107
|
-
const result = {
|
|
108
|
-
success: true,
|
|
109
|
-
issues: [
|
|
110
|
-
{ type: "export" as const, name: "func1", file: "a.ts" },
|
|
111
|
-
{ type: "file" as const, name: "old.ts" },
|
|
112
|
-
],
|
|
113
|
-
unusedExports: [
|
|
114
|
-
{ type: "export" as const, name: "func1", file: "a.ts" },
|
|
115
|
-
{ type: "export" as const, name: "func2", file: "b.ts" },
|
|
116
|
-
],
|
|
117
|
-
unusedFiles: [{ type: "file" as const, name: "old.ts" }],
|
|
118
|
-
unusedDeps: [],
|
|
119
|
-
unlistedDeps: [],
|
|
120
|
-
summary: "",
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const formatted = client.formatResult(result);
|
|
124
|
-
expect(formatted).toContain("2 unused export(s)");
|
|
125
|
-
expect(formatted).toContain("1 unused file(s)");
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
const LATENCY_LOG_DIR = path.join(os.homedir(), ".pi-lens");
|
|
5
|
-
const LATENCY_LOG_FILE = path.join(LATENCY_LOG_DIR, "latency.log");
|
|
6
|
-
try {
|
|
7
|
-
if (!fs.existsSync(LATENCY_LOG_DIR)) {
|
|
8
|
-
fs.mkdirSync(LATENCY_LOG_DIR, { recursive: true });
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
catch { }
|
|
12
|
-
export function logLatency(entry) {
|
|
13
|
-
const line = `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`;
|
|
14
|
-
try {
|
|
15
|
-
fs.appendFileSync(LATENCY_LOG_FILE, line);
|
|
16
|
-
}
|
|
17
|
-
catch { }
|
|
18
|
-
}
|
|
19
|
-
export function getLatencyLogPath() {
|
|
20
|
-
return LATENCY_LOG_FILE;
|
|
21
|
-
}
|
|
22
|
-
export function readLatencyLog(limit = 100) {
|
|
23
|
-
try {
|
|
24
|
-
const content = fs.readFileSync(LATENCY_LOG_FILE, "utf-8");
|
|
25
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
26
|
-
return lines
|
|
27
|
-
.slice(-limit)
|
|
28
|
-
.map((line) => JSON.parse(line))
|
|
29
|
-
.reverse();
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export function clearLatencyLog() {
|
|
36
|
-
try {
|
|
37
|
-
fs.writeFileSync(LATENCY_LOG_FILE, "");
|
|
38
|
-
}
|
|
39
|
-
catch { }
|
|
40
|
-
}
|