pi-lens 2.0.7 → 2.0.8
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/clients/ast-grep-client.test.ts +146 -116
- package/clients/ast-grep-client.ts +645 -551
- package/clients/biome-client.test.ts +154 -137
- package/clients/biome-client.ts +397 -337
- package/clients/complexity-client.test.ts +188 -200
- package/clients/complexity-client.ts +815 -667
- package/clients/dependency-checker.test.ts +55 -55
- package/clients/dependency-checker.ts +358 -333
- package/clients/go-client.test.ts +121 -111
- package/clients/go-client.ts +218 -216
- package/clients/jscpd-client.test.ts +132 -132
- package/clients/jscpd-client.ts +155 -118
- package/clients/knip-client.test.ts +123 -133
- package/clients/knip-client.ts +231 -218
- package/clients/metrics-client.test.ts +171 -167
- package/clients/metrics-client.ts +283 -252
- package/clients/ruff-client.test.ts +128 -117
- package/clients/ruff-client.ts +300 -269
- package/clients/rust-client.test.ts +104 -85
- package/clients/rust-client.ts +241 -234
- package/clients/subprocess-client.ts +1 -1
- package/clients/test-runner-client.test.ts +248 -215
- package/clients/test-runner-client.ts +728 -608
- package/clients/test-utils.ts +10 -3
- package/clients/todo-scanner.test.ts +288 -202
- package/clients/todo-scanner.ts +225 -187
- package/clients/type-coverage-client.test.ts +119 -119
- package/clients/type-coverage-client.ts +142 -115
- package/clients/types.ts +28 -28
- package/clients/typescript-client.test.ts +99 -93
- package/clients/typescript-client.ts +527 -502
- package/index.ts +662 -212
- package/package.json +1 -1
- package/tsconfig.json +12 -12
|
@@ -1,72 +1,69 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { ComplexityClient } from "./complexity-client.js";
|
|
3
3
|
import { createTempFile, setupTestEnvironment } from "./test-utils.js";
|
|
4
4
|
|
|
5
5
|
describe("ComplexityClient", () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const content = `
|
|
6
|
+
let client: ComplexityClient;
|
|
7
|
+
let tmpDir: string;
|
|
8
|
+
let cleanup: () => void;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
client = new ComplexityClient();
|
|
12
|
+
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-complexity-test-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
cleanup();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("isSupportedFile", () => {
|
|
20
|
+
it("should support TypeScript files", () => {
|
|
21
|
+
expect(client.isSupportedFile("test.ts")).toBe(true);
|
|
22
|
+
expect(client.isSupportedFile("test.tsx")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should support JavaScript files", () => {
|
|
26
|
+
expect(client.isSupportedFile("test.js")).toBe(true);
|
|
27
|
+
expect(client.isSupportedFile("test.jsx")).toBe(true);
|
|
28
|
+
expect(client.isSupportedFile("test.mjs")).toBe(true);
|
|
29
|
+
expect(client.isSupportedFile("test.cjs")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should not support non-TS/JS files", () => {
|
|
33
|
+
expect(client.isSupportedFile("test.py")).toBe(false);
|
|
34
|
+
expect(client.isSupportedFile("test.json")).toBe(false);
|
|
35
|
+
expect(client.isSupportedFile("test.md")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("analyzeFile", () => {
|
|
40
|
+
it("should return null for non-existent files", () => {
|
|
41
|
+
const result = client.analyzeFile("/nonexistent/file.ts");
|
|
42
|
+
expect(result).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should analyze a simple function", () => {
|
|
46
|
+
try {
|
|
47
|
+
const content = `
|
|
49
48
|
function greet(name: string): string {
|
|
50
49
|
return "Hello, " + name;
|
|
51
50
|
}
|
|
52
51
|
`;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const content = `
|
|
52
|
+
const filePath = createTempFile(tmpDir, "simple.ts", content);
|
|
53
|
+
const result = client.analyzeFile(filePath);
|
|
54
|
+
|
|
55
|
+
expect(result).not.toBeNull();
|
|
56
|
+
expect(result?.functionCount).toBe(1);
|
|
57
|
+
expect(result?.cyclomaticComplexity).toBe(1);
|
|
58
|
+
expect(result?.cognitiveComplexity).toBe(0);
|
|
59
|
+
expect(result?.maxNestingDepth).toBeGreaterThanOrEqual(1);
|
|
60
|
+
} finally {
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should detect if statements in cyclomatic complexity", () => {
|
|
65
|
+
try {
|
|
66
|
+
const content = `
|
|
70
67
|
function check(x: number): string {
|
|
71
68
|
if (x > 0) {
|
|
72
69
|
return "positive";
|
|
@@ -77,40 +74,36 @@ function check(x: number): string {
|
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
`;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const content = `
|
|
77
|
+
const filePath = createTempFile(tmpDir, "if-test.ts", content);
|
|
78
|
+
const result = client.analyzeFile(filePath);
|
|
79
|
+
|
|
80
|
+
expect(result).not.toBeNull();
|
|
81
|
+
// 1 base + 1 if + 1 else-if = 3
|
|
82
|
+
expect(result?.cyclomaticComplexity).toBeGreaterThanOrEqual(3);
|
|
83
|
+
} finally {
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should calculate maintainability index", () => {
|
|
88
|
+
try {
|
|
89
|
+
const content = `
|
|
95
90
|
function simple(): number {
|
|
96
91
|
return 42;
|
|
97
92
|
}
|
|
98
93
|
`;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
const content = `
|
|
94
|
+
const filePath = createTempFile(tmpDir, "mi-test.ts", content);
|
|
95
|
+
const result = client.analyzeFile(filePath);
|
|
96
|
+
|
|
97
|
+
expect(result).not.toBeNull();
|
|
98
|
+
expect(result?.maintainabilityIndex).toBeGreaterThan(0);
|
|
99
|
+
expect(result?.maintainabilityIndex).toBeLessThanOrEqual(100);
|
|
100
|
+
} finally {
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should detect deep nesting", () => {
|
|
105
|
+
try {
|
|
106
|
+
const content = `
|
|
114
107
|
function deepNest(arr: number[][][][]): number {
|
|
115
108
|
for (let i = 0; i < arr.length; i++) {
|
|
116
109
|
for (let j = 0; j < arr[i].length; j++) {
|
|
@@ -126,20 +119,18 @@ function deepNest(arr: number[][][][]): number {
|
|
|
126
119
|
return 0;
|
|
127
120
|
}
|
|
128
121
|
`;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const content = `
|
|
122
|
+
const filePath = createTempFile(tmpDir, "nesting-test.ts", content);
|
|
123
|
+
const result = client.analyzeFile(filePath);
|
|
124
|
+
|
|
125
|
+
expect(result).not.toBeNull();
|
|
126
|
+
expect(result?.maxNestingDepth).toBeGreaterThanOrEqual(5);
|
|
127
|
+
} finally {
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should count cognitive complexity with nesting penalty", () => {
|
|
132
|
+
try {
|
|
133
|
+
const content = `
|
|
143
134
|
function nested(x: number, y: number): number {
|
|
144
135
|
if (x > 0) {
|
|
145
136
|
if (y > 0) {
|
|
@@ -151,40 +142,36 @@ function nested(x: number, y: number): number {
|
|
|
151
142
|
return 0;
|
|
152
143
|
}
|
|
153
144
|
`;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
const content = `
|
|
145
|
+
const filePath = createTempFile(tmpDir, "cognitive-test.ts", content);
|
|
146
|
+
const result = client.analyzeFile(filePath);
|
|
147
|
+
|
|
148
|
+
expect(result).not.toBeNull();
|
|
149
|
+
// Cognitive: 1 (if) + 2 (nested if) + 3 (deeply nested if) = 6
|
|
150
|
+
expect(result?.cognitiveComplexity).toBeGreaterThanOrEqual(6);
|
|
151
|
+
} finally {
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should calculate halstead volume", () => {
|
|
156
|
+
try {
|
|
157
|
+
const content = `
|
|
169
158
|
function add(a: number, b: number): number {
|
|
170
159
|
return a + b;
|
|
171
160
|
}
|
|
172
161
|
`;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const shortContent = `function short() { return 1; }`;
|
|
187
|
-
const longContent = `
|
|
162
|
+
const filePath = createTempFile(tmpDir, "halstead-test.ts", content);
|
|
163
|
+
const result = client.analyzeFile(filePath);
|
|
164
|
+
|
|
165
|
+
expect(result).not.toBeNull();
|
|
166
|
+
expect(result?.halsteadVolume).toBeGreaterThan(0);
|
|
167
|
+
} finally {
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should measure function length", () => {
|
|
172
|
+
try {
|
|
173
|
+
const shortContent = `function short() { return 1; }`;
|
|
174
|
+
const longContent = `
|
|
188
175
|
function long(): number {
|
|
189
176
|
const a = 1;
|
|
190
177
|
const b = 2;
|
|
@@ -199,69 +186,70 @@ function long(): number {
|
|
|
199
186
|
return a + b + c + d + e + f + g + h + i + j;
|
|
200
187
|
}
|
|
201
188
|
`;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
189
|
+
const shortPath = createTempFile(tmpDir, "short.ts", shortContent);
|
|
190
|
+
const longPath = createTempFile(tmpDir, "long.ts", longContent);
|
|
191
|
+
|
|
192
|
+
const shortResult = client.analyzeFile(shortPath);
|
|
193
|
+
const longResult = client.analyzeFile(longPath);
|
|
194
|
+
|
|
195
|
+
expect(shortResult?.maxFunctionLength ?? 0).toBeLessThan(
|
|
196
|
+
longResult?.maxFunctionLength ?? 0,
|
|
197
|
+
);
|
|
198
|
+
} finally {
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("formatMetrics", () => {
|
|
204
|
+
it("should format metrics for display", () => {
|
|
205
|
+
const metrics = {
|
|
206
|
+
filePath: "test.ts",
|
|
207
|
+
maxNestingDepth: 4,
|
|
208
|
+
avgFunctionLength: 15,
|
|
209
|
+
maxFunctionLength: 30,
|
|
210
|
+
functionCount: 3,
|
|
211
|
+
cyclomaticComplexity: 4,
|
|
212
|
+
maxCyclomaticComplexity: 8,
|
|
213
|
+
cognitiveComplexity: 12,
|
|
214
|
+
halsteadVolume: 200,
|
|
215
|
+
maintainabilityIndex: 75,
|
|
216
|
+
linesOfCode: 100,
|
|
217
|
+
commentLines: 10,
|
|
218
|
+
codeEntropy: 0.5,
|
|
219
|
+
maxParamsInFunction: 3,
|
|
220
|
+
aiCommentPatterns: 1,
|
|
221
|
+
singleUseFunctions: 0,
|
|
222
|
+
tryCatchCount: 1,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const formatted = client.formatMetrics(metrics);
|
|
226
|
+
expect(formatted).toContain("test.ts");
|
|
227
|
+
expect(formatted).toContain("75/100");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should warn about low maintainability", () => {
|
|
231
|
+
const metrics = {
|
|
232
|
+
filePath: "bad.ts",
|
|
233
|
+
maxNestingDepth: 8,
|
|
234
|
+
avgFunctionLength: 60,
|
|
235
|
+
maxFunctionLength: 100,
|
|
236
|
+
functionCount: 5,
|
|
237
|
+
cyclomaticComplexity: 15,
|
|
238
|
+
maxCyclomaticComplexity: 25,
|
|
239
|
+
cognitiveComplexity: 50,
|
|
240
|
+
halsteadVolume: 800,
|
|
241
|
+
maintainabilityIndex: 25,
|
|
242
|
+
linesOfCode: 500,
|
|
243
|
+
commentLines: 10,
|
|
244
|
+
codeEntropy: 0.5,
|
|
245
|
+
maxParamsInFunction: 4,
|
|
246
|
+
aiCommentPatterns: 2,
|
|
247
|
+
singleUseFunctions: 1,
|
|
248
|
+
tryCatchCount: 2,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const formatted = client.formatMetrics(metrics);
|
|
252
|
+
expect(formatted).toContain("✗"); // Low MI indicator
|
|
253
|
+
});
|
|
254
|
+
});
|
|
267
255
|
});
|