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.
Files changed (154) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +16 -12
  3. package/clients/ast-grep-client.js +8 -1
  4. package/clients/ast-grep-client.ts +9 -1
  5. package/clients/biome-client.js +51 -38
  6. package/clients/biome-client.ts +60 -58
  7. package/clients/dependency-checker.js +30 -1
  8. package/clients/dependency-checker.ts +35 -1
  9. package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
  10. package/clients/dispatch/bus-dispatcher.js +15 -14
  11. package/clients/dispatch/bus-dispatcher.ts +32 -25
  12. package/clients/dispatch/dispatcher.js +18 -25
  13. package/clients/dispatch/dispatcher.test.ts +2 -1
  14. package/clients/dispatch/dispatcher.ts +17 -28
  15. package/clients/dispatch/plan.js +77 -32
  16. package/clients/dispatch/plan.ts +78 -32
  17. package/clients/dispatch/runners/ast-grep-napi.js +36 -376
  18. package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
  19. package/clients/dispatch/runners/index.js +8 -4
  20. package/clients/dispatch/runners/index.ts +8 -4
  21. package/clients/dispatch/runners/lsp.js +65 -0
  22. package/clients/dispatch/runners/lsp.ts +125 -0
  23. package/clients/dispatch/runners/oxlint.js +2 -2
  24. package/clients/dispatch/runners/oxlint.ts +2 -2
  25. package/clients/dispatch/runners/pyright.js +24 -8
  26. package/clients/dispatch/runners/pyright.ts +28 -14
  27. package/clients/dispatch/runners/rust-clippy.js +2 -2
  28. package/clients/dispatch/runners/rust-clippy.ts +2 -4
  29. package/clients/dispatch/runners/tree-sitter.js +14 -2
  30. package/clients/dispatch/runners/tree-sitter.ts +15 -2
  31. package/clients/dispatch/runners/ts-lsp.js +3 -3
  32. package/clients/dispatch/runners/ts-lsp.ts +8 -5
  33. package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
  34. package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
  35. package/clients/dispatch/types.js +3 -0
  36. package/clients/dispatch/types.ts +3 -0
  37. package/clients/formatters.js +67 -14
  38. package/clients/formatters.ts +68 -15
  39. package/clients/installer/index.js +78 -10
  40. package/clients/installer/index.ts +519 -426
  41. package/clients/jscpd-client.js +28 -0
  42. package/clients/jscpd-client.ts +41 -3
  43. package/clients/knip-client.js +30 -1
  44. package/clients/knip-client.ts +34 -2
  45. package/clients/lsp/__tests__/client.test.ts +64 -41
  46. package/clients/lsp/__tests__/config.test.ts +25 -17
  47. package/clients/lsp/__tests__/launch.test.ts +108 -43
  48. package/clients/lsp/__tests__/service.test.ts +76 -48
  49. package/clients/lsp/client.js +87 -2
  50. package/clients/lsp/client.ts +150 -6
  51. package/clients/lsp/config.js +8 -11
  52. package/clients/lsp/config.ts +24 -21
  53. package/clients/lsp/index.js +69 -0
  54. package/clients/lsp/index.ts +82 -0
  55. package/clients/lsp/interactive-install.js +19 -8
  56. package/clients/lsp/interactive-install.ts +52 -27
  57. package/clients/lsp/launch.js +182 -32
  58. package/clients/lsp/launch.ts +241 -38
  59. package/clients/lsp/path-utils.js +3 -46
  60. package/clients/lsp/path-utils.ts +11 -51
  61. package/clients/lsp/server.js +93 -71
  62. package/clients/lsp/server.ts +173 -131
  63. package/clients/path-utils.js +142 -0
  64. package/clients/path-utils.ts +153 -0
  65. package/clients/ruff-client.js +33 -4
  66. package/clients/ruff-client.ts +44 -13
  67. package/clients/safe-spawn.js +3 -1
  68. package/clients/safe-spawn.ts +3 -1
  69. package/clients/services/effect-integration.js +11 -7
  70. package/clients/services/effect-integration.ts +34 -26
  71. package/clients/sg-runner.js +51 -9
  72. package/clients/sg-runner.ts +58 -15
  73. package/clients/tree-sitter-client.js +12 -0
  74. package/clients/tree-sitter-client.ts +12 -0
  75. package/clients/typescript-client.js +6 -2
  76. package/clients/typescript-client.ts +9 -2
  77. package/commands/booboo.js +2 -4
  78. package/commands/booboo.ts +2 -4
  79. package/index.ts +377 -93
  80. package/package.json +2 -1
  81. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
  82. package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
  83. package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
  84. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
  85. package/tsconfig.json +1 -1
  86. package/clients/__tests__/file-time.test.js +0 -216
  87. package/clients/__tests__/format-service.test.js +0 -245
  88. package/clients/__tests__/formatters.test.js +0 -271
  89. package/clients/agent-behavior-client.test.js +0 -94
  90. package/clients/ast-grep-client.test.js +0 -129
  91. package/clients/ast-grep-client.test.ts +0 -155
  92. package/clients/biome-client.test.js +0 -144
  93. package/clients/cache-manager.test.js +0 -197
  94. package/clients/complexity-client.test.js +0 -234
  95. package/clients/dependency-checker.test.js +0 -60
  96. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  97. package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
  98. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  99. package/clients/dispatch/dispatcher.format.test.js +0 -46
  100. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  101. package/clients/dispatch/dispatcher.test.js +0 -115
  102. package/clients/dispatch/runners/architect.test.js +0 -138
  103. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
  104. package/clients/dispatch/runners/oxlint.test.js +0 -230
  105. package/clients/dispatch/runners/pyright.test.js +0 -98
  106. package/clients/dispatch/runners/python-slop.test.js +0 -203
  107. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  108. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  109. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  110. package/clients/dispatch/runners/ts-slop.test.js +0 -180
  111. package/clients/dispatch/runners/ts-slop.test.ts +0 -230
  112. package/clients/dogfood.test.js +0 -201
  113. package/clients/file-kinds.test.js +0 -169
  114. package/clients/go-client.test.js +0 -127
  115. package/clients/jscpd-client.test.js +0 -127
  116. package/clients/knip-client.test.js +0 -112
  117. package/clients/lsp/__tests__/client.test.js +0 -325
  118. package/clients/lsp/__tests__/config.test.js +0 -166
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/integration.test.js +0 -127
  121. package/clients/lsp/__tests__/launch.test.js +0 -260
  122. package/clients/lsp/__tests__/server.test.js +0 -259
  123. package/clients/lsp/__tests__/service.test.js +0 -417
  124. package/clients/metrics-client.test.js +0 -141
  125. package/clients/ruff-client.test.js +0 -132
  126. package/clients/rust-client.test.js +0 -108
  127. package/clients/sanitize.test.js +0 -177
  128. package/clients/secrets-scanner.test.js +0 -100
  129. package/clients/services/__tests__/effect-integration.test.js +0 -86
  130. package/clients/test-runner-client.test.js +0 -192
  131. package/clients/todo-scanner.test.js +0 -301
  132. package/clients/type-coverage-client.test.js +0 -105
  133. package/clients/typescript-client.codefix.test.js +0 -157
  134. package/clients/typescript-client.test.js +0 -105
  135. package/commands/clients/ast-grep-client.js +0 -250
  136. package/commands/clients/ast-grep-parser.js +0 -86
  137. package/commands/clients/ast-grep-rule-manager.js +0 -91
  138. package/commands/clients/ast-grep-types.js +0 -9
  139. package/commands/clients/biome-client.js +0 -380
  140. package/commands/clients/complexity-client.js +0 -667
  141. package/commands/clients/file-kinds.js +0 -177
  142. package/commands/clients/file-utils.js +0 -40
  143. package/commands/clients/jscpd-client.js +0 -169
  144. package/commands/clients/knip-client.js +0 -211
  145. package/commands/clients/ruff-client.js +0 -297
  146. package/commands/clients/safe-spawn.js +0 -88
  147. package/commands/clients/scan-utils.js +0 -83
  148. package/commands/clients/sg-runner.js +0 -190
  149. package/commands/clients/types.js +0 -11
  150. package/commands/clients/typescript-client.js +0 -505
  151. package/commands/rate.test.js +0 -119
  152. package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
  153. package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
  154. package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
@@ -1,144 +0,0 @@
1
- import * as fs from "node:fs";
2
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
- import { BiomeClient } from "./biome-client.js";
4
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
5
- describe("BiomeClient", () => {
6
- let client;
7
- let tmpDir;
8
- let cleanup;
9
- beforeEach(() => {
10
- client = new BiomeClient();
11
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-biome-test-"));
12
- });
13
- afterEach(() => {
14
- cleanup();
15
- });
16
- afterEach(() => {
17
- cleanup();
18
- });
19
- describe("isSupportedFile", () => {
20
- it("should support JS/TS files", () => {
21
- expect(client.isSupportedFile("test.js")).toBe(true);
22
- expect(client.isSupportedFile("test.jsx")).toBe(true);
23
- expect(client.isSupportedFile("test.ts")).toBe(true);
24
- expect(client.isSupportedFile("test.tsx")).toBe(true);
25
- expect(client.isSupportedFile("test.mjs")).toBe(true);
26
- expect(client.isSupportedFile("test.cjs")).toBe(true);
27
- });
28
- it("should support CSS and JSON", () => {
29
- expect(client.isSupportedFile("style.css")).toBe(true);
30
- expect(client.isSupportedFile("config.json")).toBe(true);
31
- });
32
- it("should not support unsupported files", () => {
33
- expect(client.isSupportedFile("test.py")).toBe(false);
34
- expect(client.isSupportedFile("test.md")).toBe(false);
35
- expect(client.isSupportedFile("test.txt")).toBe(false);
36
- });
37
- });
38
- describe("isAvailable", () => {
39
- it("should check biome availability", () => {
40
- const available = client.isAvailable();
41
- // Just verify it doesn't throw - actual availability depends on environment
42
- expect(typeof available).toBe("boolean");
43
- });
44
- });
45
- describe("checkFile", () => {
46
- it("should return empty array for non-existent files", () => {
47
- if (!client.isAvailable())
48
- return;
49
- const result = client.checkFile("/nonexistent/file.ts");
50
- expect(result).toEqual([]);
51
- });
52
- it("should return array of diagnostics for TS files", {
53
- timeout: 15000,
54
- }, () => {
55
- if (!client.isAvailable())
56
- return;
57
- const content = `
58
- const x: number = "string";
59
- `;
60
- const filePath = createTempFile(tmpDir, "test.ts", content);
61
- const result = client.checkFile(filePath);
62
- // Should return an array (may or may not have issues)
63
- expect(Array.isArray(result)).toBe(true);
64
- });
65
- });
66
- describe("formatDiagnostics", () => {
67
- it("should format diagnostics for display", () => {
68
- const diags = [
69
- {
70
- line: 1,
71
- column: 0,
72
- endLine: 1,
73
- endColumn: 10,
74
- severity: "error",
75
- message: "Unexpected var",
76
- rule: "noVar",
77
- category: "lint",
78
- fixable: true,
79
- },
80
- ];
81
- const formatted = client.formatDiagnostics(diags, "test.ts");
82
- expect(formatted).toContain("Biome");
83
- expect(formatted).toContain("1 issue");
84
- expect(formatted).toContain("noVar");
85
- });
86
- it("should show fixable count", () => {
87
- const diags = [
88
- {
89
- line: 1,
90
- column: 0,
91
- endLine: 1,
92
- endColumn: 10,
93
- severity: "error",
94
- message: "Error 1",
95
- rule: "rule1",
96
- category: "lint",
97
- fixable: true,
98
- },
99
- {
100
- line: 2,
101
- column: 0,
102
- endLine: 2,
103
- endColumn: 10,
104
- severity: "warning",
105
- message: "Warning 1",
106
- rule: "rule2",
107
- category: "lint",
108
- fixable: false,
109
- },
110
- ];
111
- const formatted = client.formatDiagnostics(diags, "test.ts");
112
- expect(formatted).toContain("1 fixable");
113
- });
114
- it("should truncate long diagnostic lists", () => {
115
- const diags = Array.from({ length: 20 }, (_, i) => ({
116
- line: i + 1,
117
- column: 0,
118
- endLine: i + 1,
119
- endColumn: 10,
120
- severity: "warning",
121
- message: `Warning ${i}`,
122
- rule: `rule${i}`,
123
- category: "lint",
124
- fixable: false,
125
- }));
126
- const formatted = client.formatDiagnostics(diags, "test.ts");
127
- expect(formatted).toContain("...");
128
- expect(formatted).toContain("5 more");
129
- });
130
- });
131
- describe("formatFile", () => {
132
- it("should format a file", () => {
133
- if (!client.isAvailable())
134
- return;
135
- const content = `const x={a:1,b:2}`;
136
- const filePath = createTempFile(tmpDir, "test.ts", content);
137
- const result = client.formatFile(filePath);
138
- expect(result.success).toBe(true);
139
- // Check if file was formatted (should have spaces)
140
- const formatted = fs.readFileSync(filePath, "utf-8");
141
- expect(formatted).toContain(": "); // Should have spaces after colons
142
- });
143
- });
144
- });
@@ -1,197 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
- import { CacheManager, } from "./cache-manager.js";
6
- describe("CacheManager", () => {
7
- let manager;
8
- let testDir;
9
- beforeEach(() => {
10
- manager = new CacheManager();
11
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lens-cache-test-"));
12
- });
13
- afterEach(() => {
14
- fs.rmSync(testDir, { recursive: true, force: true });
15
- });
16
- describe("scanner cache", () => {
17
- it("should return null for missing cache", () => {
18
- const result = manager.readCache("knip", testDir);
19
- expect(result).toBeNull();
20
- });
21
- it("should write and read cache", () => {
22
- const data = { files: ["a.ts", "b.ts"], unused: ["x"] };
23
- manager.writeCache("knip", data, testDir, { scanDurationMs: 1500 });
24
- const result = manager.readCache("knip", testDir);
25
- expect(result).not.toBeNull();
26
- expect(result?.data).toEqual(data);
27
- expect(result?.meta.scanDurationMs).toBe(1500);
28
- expect(result?.meta.timestamp).toBeDefined();
29
- });
30
- it("should return null for stale cache", () => {
31
- const data = { files: [] };
32
- manager.writeCache("jscpd", data, testDir);
33
- // Manually set old timestamp
34
- const metaPath = path.join(testDir, ".pi-lens", "cache", "jscpd.meta.json");
35
- const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
36
- meta.timestamp = new Date(Date.now() - 60 * 60 * 1000).toISOString(); // 1 hour ago
37
- fs.writeFileSync(metaPath, JSON.stringify(meta));
38
- const result = manager.readCache("jscpd", testDir, 30 * 60 * 1000);
39
- expect(result).toBeNull();
40
- });
41
- it("should respect custom maxAge", () => {
42
- const data = { files: [] };
43
- manager.writeCache("madge", data, testDir);
44
- // Cache is 45 min old
45
- const metaPath = path.join(testDir, ".pi-lens", "cache", "madge.meta.json");
46
- const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
47
- meta.timestamp = new Date(Date.now() - 45 * 60 * 1000).toISOString();
48
- fs.writeFileSync(metaPath, JSON.stringify(meta));
49
- // Default 30 min → stale
50
- expect(manager.readCache("madge", testDir)).toBeNull();
51
- // Custom 60 min → fresh
52
- const result = manager.readCache("madge", testDir, 60 * 60 * 1000);
53
- expect(result).not.toBeNull();
54
- });
55
- it("should check cache freshness", () => {
56
- expect(manager.isCacheFresh("knip", testDir)).toBe(false);
57
- manager.writeCache("knip", {}, testDir);
58
- expect(manager.isCacheFresh("knip", testDir)).toBe(true);
59
- });
60
- it("should clear cache", () => {
61
- manager.writeCache("jscpd", { clones: [] }, testDir);
62
- expect(manager.isCacheFresh("jscpd", testDir)).toBe(true);
63
- manager.clearCache("jscpd", testDir);
64
- expect(manager.isCacheFresh("jscpd", testDir)).toBe(false);
65
- });
66
- });
67
- // Helper to get test file path (absolute)
68
- const testFile = (name) => path.join(testDir, name);
69
- describe("turn state", () => {
70
- it("should return default state when no file exists", () => {
71
- const state = manager.readTurnState(testDir);
72
- expect(state.files).toEqual({});
73
- expect(state.turnCycles).toBe(0);
74
- expect(state.maxCycles).toBe(3);
75
- });
76
- it("should write and read turn state", () => {
77
- const state = {
78
- files: {
79
- "src/a.ts": {
80
- modifiedRanges: [{ start: 1, end: 10 }],
81
- importsChanged: true,
82
- lastEdit: new Date().toISOString(),
83
- },
84
- },
85
- turnCycles: 1,
86
- maxCycles: 3,
87
- lastUpdated: "",
88
- };
89
- manager.writeTurnState(state, testDir);
90
- const read = manager.readTurnState(testDir);
91
- expect(read.turnCycles).toBe(1);
92
- expect(read.files["src/a.ts"].modifiedRanges).toHaveLength(1);
93
- });
94
- it("should add modified ranges and merge overlapping", () => {
95
- manager.addModifiedRange(testFile("src/a.ts"), { start: 1, end: 10 }, false, testDir);
96
- manager.addModifiedRange(testFile("src/a.ts"), { start: 8, end: 20 }, true, testDir);
97
- const state = manager.readTurnState(testDir);
98
- const key = "src/a.ts";
99
- const ranges = state.files[key]?.modifiedRanges;
100
- expect(ranges).toHaveLength(1); // Merged into one
101
- expect(ranges?.[0]).toEqual({ start: 1, end: 20 });
102
- expect(state.files[key].importsChanged).toBe(true);
103
- });
104
- it("should track imports_changed flag", () => {
105
- // First edit without import change
106
- manager.addModifiedRange(testFile("src/a.ts"), { start: 1, end: 5 }, false, testDir);
107
- // Second edit with import change
108
- manager.addModifiedRange(testFile("src/a.ts"), { start: 10, end: 15 }, true, testDir);
109
- const state = manager.readTurnState(testDir);
110
- expect(state.files["src/a.ts"].importsChanged).toBe(true);
111
- });
112
- it("should increment turn cycle", () => {
113
- manager.incrementTurnCycle(testDir);
114
- manager.incrementTurnCycle(testDir);
115
- const state = manager.readTurnState(testDir);
116
- expect(state.turnCycles).toBe(2);
117
- });
118
- it("should detect max cycles exceeded", () => {
119
- expect(manager.isMaxCyclesExceeded(testDir)).toBe(false);
120
- manager.incrementTurnCycle(testDir);
121
- manager.incrementTurnCycle(testDir);
122
- manager.incrementTurnCycle(testDir);
123
- expect(manager.isMaxCyclesExceeded(testDir)).toBe(true);
124
- });
125
- it("should clear turn state", () => {
126
- manager.addModifiedRange(testFile("src/a.ts"), { start: 1, end: 10 }, true, testDir);
127
- manager.incrementTurnCycle(testDir);
128
- manager.clearTurnState(testDir);
129
- const state = manager.readTurnState(testDir);
130
- expect(Object.keys(state.files)).toHaveLength(0);
131
- expect(state.turnCycles).toBe(0);
132
- });
133
- });
134
- describe("file queries", () => {
135
- beforeEach(() => {
136
- // Clear any previous state from other tests
137
- manager.clearTurnState(testDir);
138
- // Now add our test files
139
- manager.addModifiedRange(testFile("a.ts"), { start: 1, end: 10 }, false, testDir);
140
- manager.addModifiedRange(testFile("b.ts"), { start: 5, end: 20 }, true, testDir);
141
- manager.addModifiedRange(testFile("c.ts"), { start: 1, end: 5 }, true, testDir);
142
- });
143
- it("should get all files for jscpd", () => {
144
- const files = manager.getFilesForJscpd(testDir);
145
- expect(files).toHaveLength(3);
146
- });
147
- it("should get only files with import changes for madge", () => {
148
- // Verify state was recorded correctly
149
- const state = manager.readTurnState(testDir);
150
- const fileKeys = Object.keys(state.files);
151
- // Only b.ts and c.ts have importsChanged: true
152
- const madgeFiles = manager.getFilesForMadge(testDir);
153
- const filesWithImportsTrue = fileKeys.filter((k) => state.files[k].importsChanged);
154
- expect(madgeFiles).toHaveLength(filesWithImportsTrue.length);
155
- });
156
- });
157
- describe("range utilities", () => {
158
- it("should merge non-overlapping ranges", () => {
159
- const ranges = [
160
- { start: 1, end: 5 },
161
- { start: 10, end: 15 },
162
- { start: 20, end: 25 },
163
- ];
164
- expect(manager.mergeRanges(ranges)).toHaveLength(3);
165
- });
166
- it("should merge overlapping ranges", () => {
167
- const ranges = [
168
- { start: 1, end: 10 },
169
- { start: 5, end: 15 },
170
- ];
171
- const merged = manager.mergeRanges(ranges);
172
- expect(merged).toHaveLength(1);
173
- expect(merged[0]).toEqual({ start: 1, end: 15 });
174
- });
175
- it("should merge adjacent ranges", () => {
176
- const ranges = [
177
- { start: 1, end: 10 },
178
- { start: 11, end: 20 },
179
- ];
180
- const merged = manager.mergeRanges(ranges);
181
- expect(merged).toHaveLength(1);
182
- expect(merged[0]).toEqual({ start: 1, end: 20 });
183
- });
184
- it("should detect line in modified range", () => {
185
- const ranges = [
186
- { start: 10, end: 20 },
187
- { start: 30, end: 40 },
188
- ];
189
- expect(manager.isLineInModifiedRange(5, ranges)).toBe(false);
190
- expect(manager.isLineInModifiedRange(10, ranges)).toBe(true);
191
- expect(manager.isLineInModifiedRange(15, ranges)).toBe(true);
192
- expect(manager.isLineInModifiedRange(20, ranges)).toBe(true);
193
- expect(manager.isLineInModifiedRange(25, ranges)).toBe(false);
194
- expect(manager.isLineInModifiedRange(35, ranges)).toBe(true);
195
- });
196
- });
197
- });
@@ -1,234 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { ComplexityClient } from "./complexity-client.js";
3
- import { createTempFile, setupTestEnvironment } from "./test-utils.js";
4
- describe("ComplexityClient", () => {
5
- let client;
6
- let tmpDir;
7
- let cleanup;
8
- beforeEach(() => {
9
- client = new ComplexityClient();
10
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-complexity-test-"));
11
- });
12
- afterEach(() => {
13
- cleanup();
14
- });
15
- describe("isSupportedFile", () => {
16
- it("should support TypeScript files", () => {
17
- expect(client.isSupportedFile("test.ts")).toBe(true);
18
- expect(client.isSupportedFile("test.tsx")).toBe(true);
19
- });
20
- it("should support JavaScript files", () => {
21
- expect(client.isSupportedFile("test.js")).toBe(true);
22
- expect(client.isSupportedFile("test.jsx")).toBe(true);
23
- expect(client.isSupportedFile("test.mjs")).toBe(true);
24
- expect(client.isSupportedFile("test.cjs")).toBe(true);
25
- });
26
- it("should not support non-TS/JS files", () => {
27
- expect(client.isSupportedFile("test.py")).toBe(false);
28
- expect(client.isSupportedFile("test.json")).toBe(false);
29
- expect(client.isSupportedFile("test.md")).toBe(false);
30
- });
31
- });
32
- describe("analyzeFile", () => {
33
- it("should return null for non-existent files", () => {
34
- const result = client.analyzeFile("/nonexistent/file.ts");
35
- expect(result).toBeNull();
36
- });
37
- it("should analyze a simple function", () => {
38
- try {
39
- const content = `
40
- function greet(name: string): string {
41
- return "Hello, " + name;
42
- }
43
- `;
44
- const filePath = createTempFile(tmpDir, "simple.ts", content);
45
- const result = client.analyzeFile(filePath);
46
- expect(result).not.toBeNull();
47
- expect(result?.functionCount).toBe(1);
48
- expect(result?.cyclomaticComplexity).toBe(1);
49
- expect(result?.cognitiveComplexity).toBe(0);
50
- expect(result?.maxNestingDepth).toBeGreaterThanOrEqual(1);
51
- }
52
- finally {
53
- }
54
- });
55
- it("should detect if statements in cyclomatic complexity", () => {
56
- try {
57
- const content = `
58
- function check(x: number): string {
59
- if (x > 0) {
60
- return "positive";
61
- } else if (x < 0) {
62
- return "negative";
63
- } else {
64
- return "zero";
65
- }
66
- }
67
- `;
68
- const filePath = createTempFile(tmpDir, "if-test.ts", content);
69
- const result = client.analyzeFile(filePath);
70
- expect(result).not.toBeNull();
71
- // 1 base + 1 if + 1 else-if = 3
72
- expect(result?.cyclomaticComplexity).toBeGreaterThanOrEqual(3);
73
- }
74
- finally {
75
- }
76
- });
77
- it("should calculate maintainability index", () => {
78
- try {
79
- const content = `
80
- function simple(): number {
81
- return 42;
82
- }
83
- `;
84
- const filePath = createTempFile(tmpDir, "mi-test.ts", content);
85
- const result = client.analyzeFile(filePath);
86
- expect(result).not.toBeNull();
87
- expect(result?.maintainabilityIndex).toBeGreaterThan(0);
88
- expect(result?.maintainabilityIndex).toBeLessThanOrEqual(100);
89
- }
90
- finally {
91
- }
92
- });
93
- it("should detect deep nesting", () => {
94
- try {
95
- const content = `
96
- function deepNest(arr: number[][][][]): number {
97
- for (let i = 0; i < arr.length; i++) {
98
- for (let j = 0; j < arr[i].length; j++) {
99
- for (let k = 0; k < arr[i][j].length; k++) {
100
- for (let l = 0; l < arr[i][j][k].length; l++) {
101
- if (arr[i][j][k][l] > 0) {
102
- return arr[i][j][k][l];
103
- }
104
- }
105
- }
106
- }
107
- }
108
- return 0;
109
- }
110
- `;
111
- const filePath = createTempFile(tmpDir, "nesting-test.ts", content);
112
- const result = client.analyzeFile(filePath);
113
- expect(result).not.toBeNull();
114
- expect(result?.maxNestingDepth).toBeGreaterThanOrEqual(5);
115
- }
116
- finally {
117
- }
118
- });
119
- it("should count cognitive complexity with nesting penalty", () => {
120
- try {
121
- const content = `
122
- function nested(x: number, y: number): number {
123
- if (x > 0) {
124
- if (y > 0) {
125
- if (x > y) {
126
- return 1;
127
- }
128
- }
129
- }
130
- return 0;
131
- }
132
- `;
133
- const filePath = createTempFile(tmpDir, "cognitive-test.ts", content);
134
- const result = client.analyzeFile(filePath);
135
- expect(result).not.toBeNull();
136
- // Cognitive: 1 (if) + 2 (nested if) + 3 (deeply nested if) = 6
137
- expect(result?.cognitiveComplexity).toBeGreaterThanOrEqual(6);
138
- }
139
- finally {
140
- }
141
- });
142
- it("should calculate halstead volume", () => {
143
- try {
144
- const content = `
145
- function add(a: number, b: number): number {
146
- return a + b;
147
- }
148
- `;
149
- const filePath = createTempFile(tmpDir, "halstead-test.ts", content);
150
- const result = client.analyzeFile(filePath);
151
- expect(result).not.toBeNull();
152
- expect(result?.halsteadVolume).toBeGreaterThan(0);
153
- }
154
- finally {
155
- }
156
- });
157
- it("should measure function length", () => {
158
- try {
159
- const shortContent = `function short() { return 1; }`;
160
- const longContent = `
161
- function long(): number {
162
- const a = 1;
163
- const b = 2;
164
- const c = 3;
165
- const d = 4;
166
- const e = 5;
167
- const f = 6;
168
- const g = 7;
169
- const h = 8;
170
- const i = 9;
171
- const j = 10;
172
- return a + b + c + d + e + f + g + h + i + j;
173
- }
174
- `;
175
- const shortPath = createTempFile(tmpDir, "short.ts", shortContent);
176
- const longPath = createTempFile(tmpDir, "long.ts", longContent);
177
- const shortResult = client.analyzeFile(shortPath);
178
- const longResult = client.analyzeFile(longPath);
179
- expect(shortResult?.maxFunctionLength ?? 0).toBeLessThan(longResult?.maxFunctionLength ?? 0);
180
- }
181
- finally {
182
- }
183
- });
184
- });
185
- describe("formatMetrics", () => {
186
- it("should format metrics for display", () => {
187
- const metrics = {
188
- filePath: "test.ts",
189
- maxNestingDepth: 4,
190
- avgFunctionLength: 15,
191
- maxFunctionLength: 30,
192
- functionCount: 3,
193
- cyclomaticComplexity: 4,
194
- maxCyclomaticComplexity: 8,
195
- cognitiveComplexity: 12,
196
- halsteadVolume: 200,
197
- maintainabilityIndex: 75,
198
- linesOfCode: 100,
199
- commentLines: 10,
200
- codeEntropy: 0.5,
201
- maxParamsInFunction: 3,
202
- aiCommentPatterns: 1,
203
- singleUseFunctions: 0,
204
- tryCatchCount: 1,
205
- };
206
- const formatted = client.formatMetrics(metrics);
207
- expect(formatted).toContain("test.ts");
208
- expect(formatted).toContain("75/100");
209
- });
210
- it("should warn about low maintainability", () => {
211
- const metrics = {
212
- filePath: "bad.ts",
213
- maxNestingDepth: 8,
214
- avgFunctionLength: 60,
215
- maxFunctionLength: 100,
216
- functionCount: 5,
217
- cyclomaticComplexity: 15,
218
- maxCyclomaticComplexity: 25,
219
- cognitiveComplexity: 50,
220
- halsteadVolume: 800,
221
- maintainabilityIndex: 25,
222
- linesOfCode: 500,
223
- commentLines: 10,
224
- codeEntropy: 0.5,
225
- maxParamsInFunction: 4,
226
- aiCommentPatterns: 2,
227
- singleUseFunctions: 1,
228
- tryCatchCount: 2,
229
- };
230
- const formatted = client.formatMetrics(metrics);
231
- expect(formatted).toContain("✗"); // Low MI indicator
232
- });
233
- });
234
- });
@@ -1,60 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { DependencyChecker } from "./dependency-checker.js";
3
- import { setupTestEnvironment } from "./test-utils.js";
4
- describe("DependencyChecker", () => {
5
- let client;
6
- let tmpDir;
7
- let cleanup;
8
- beforeEach(() => {
9
- client = new DependencyChecker();
10
- ({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-dep-test-"));
11
- });
12
- afterEach(() => {
13
- cleanup();
14
- });
15
- describe("isAvailable", () => {
16
- it("should check madge availability", () => {
17
- const available = client.isAvailable();
18
- expect(typeof available).toBe("boolean");
19
- });
20
- });
21
- describe("checkFile", () => {
22
- it("should return no circular deps for non-existent files", () => {
23
- const result = client.checkFile("/nonexistent/file.ts");
24
- expect(result.hasCircular).toBe(false);
25
- expect(result.circular).toEqual([]);
26
- });
27
- it("should return correct structure when not available", () => {
28
- const mockChecker = new DependencyChecker();
29
- if (mockChecker.isAvailable())
30
- return; // Skip if available
31
- const result = mockChecker.checkFile("/some/file.ts");
32
- expect(result).toHaveProperty("hasCircular");
33
- expect(result).toHaveProperty("circular");
34
- expect(result).toHaveProperty("checked");
35
- });
36
- });
37
- describe("scanProject", () => {
38
- it("should return correct structure", () => {
39
- const mockChecker = new DependencyChecker();
40
- // When not available, should still return expected structure
41
- const result = mockChecker.scanProject(tmpDir);
42
- expect(result).toHaveProperty("circular");
43
- expect(result).toHaveProperty("count");
44
- expect(Array.isArray(result.circular)).toBe(true);
45
- });
46
- });
47
- describe("formatWarning", () => {
48
- it("should format circular dependency warning", () => {
49
- const circularDeps = ["b.ts", "c.ts", "a.ts"];
50
- const formatted = client.formatWarning("a.ts", circularDeps);
51
- expect(formatted).toContain("cycle");
52
- expect(formatted).toContain("a.ts");
53
- });
54
- it("should show the circular path", () => {
55
- const circularDeps = ["b.ts", "a.ts"];
56
- const formatted = client.formatWarning("a.ts", circularDeps);
57
- expect(formatted).toContain("b.ts");
58
- });
59
- });
60
- });