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
package/clients/ts-service.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared TypeScript Service for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Creates a single ts.Program per session that is shared across all clients
|
|
5
|
-
* (complexity-client, type-safety-client). Avoids creating a new program per file.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
import * as ts from "typescript";
|
|
9
|
-
export class TypeScriptService {
|
|
10
|
-
constructor(verbose = false) {
|
|
11
|
-
this.program = null;
|
|
12
|
-
this.checker = null;
|
|
13
|
-
this.files = new Map();
|
|
14
|
-
this.log = verbose
|
|
15
|
-
? (msg) => console.error(`[ts-service] ${msg}`)
|
|
16
|
-
: () => { };
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Update a file's content in the service
|
|
20
|
-
*/
|
|
21
|
-
updateFile(filePath, content) {
|
|
22
|
-
this.files.set(filePath, content);
|
|
23
|
-
this.invalidate();
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Invalidate the program (rebuild on next access)
|
|
27
|
-
*/
|
|
28
|
-
invalidate() {
|
|
29
|
-
this.program = null;
|
|
30
|
-
this.checker = null;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Get the shared type checker (rebuilds program if needed)
|
|
34
|
-
*/
|
|
35
|
-
getChecker() {
|
|
36
|
-
if (this.checker)
|
|
37
|
-
return this.checker;
|
|
38
|
-
if (this.files.size === 0)
|
|
39
|
-
return null;
|
|
40
|
-
try {
|
|
41
|
-
const compilerOptions = {
|
|
42
|
-
target: ts.ScriptTarget.Latest,
|
|
43
|
-
module: ts.ModuleKind.ESNext,
|
|
44
|
-
strict: true,
|
|
45
|
-
noEmit: true,
|
|
46
|
-
skipLibCheck: true,
|
|
47
|
-
lib: ["es2020"],
|
|
48
|
-
};
|
|
49
|
-
const host = ts.createCompilerHost(compilerOptions);
|
|
50
|
-
// Override getSourceFile to return our cached files
|
|
51
|
-
const originalGetSourceFile = host.getSourceFile;
|
|
52
|
-
host.getSourceFile = (fileName, languageVersion) => {
|
|
53
|
-
// Check if we have this file cached
|
|
54
|
-
const cachedContent = this.files.get(fileName);
|
|
55
|
-
if (cachedContent !== undefined) {
|
|
56
|
-
return ts.createSourceFile(fileName, cachedContent, languageVersion, true);
|
|
57
|
-
}
|
|
58
|
-
// Fall back to default (for lib files, etc.)
|
|
59
|
-
return originalGetSourceFile(fileName, languageVersion);
|
|
60
|
-
};
|
|
61
|
-
// Override fileExists and readFile
|
|
62
|
-
const originalFileExists = host.fileExists;
|
|
63
|
-
host.fileExists = (fileName) => {
|
|
64
|
-
if (this.files.has(fileName))
|
|
65
|
-
return true;
|
|
66
|
-
return originalFileExists(fileName);
|
|
67
|
-
};
|
|
68
|
-
const originalReadFile = host.readFile;
|
|
69
|
-
host.readFile = (fileName) => {
|
|
70
|
-
const cached = this.files.get(fileName);
|
|
71
|
-
if (cached !== undefined)
|
|
72
|
-
return cached;
|
|
73
|
-
return originalReadFile(fileName);
|
|
74
|
-
};
|
|
75
|
-
const fileNames = [...this.files.keys()];
|
|
76
|
-
this.program = ts.createProgram(fileNames, compilerOptions, host);
|
|
77
|
-
this.checker = this.program.getTypeChecker();
|
|
78
|
-
this.log(`Program created with ${fileNames.length} files`);
|
|
79
|
-
return this.checker;
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
this.log(`Error creating program: ${error}`);
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Get source file for a path
|
|
88
|
-
*/
|
|
89
|
-
getSourceFile(filePath) {
|
|
90
|
-
const content = this.files.get(filePath);
|
|
91
|
-
if (!content) {
|
|
92
|
-
// Try to read from disk
|
|
93
|
-
try {
|
|
94
|
-
const diskContent = fs.readFileSync(filePath, "utf-8");
|
|
95
|
-
this.files.set(filePath, diskContent);
|
|
96
|
-
return ts.createSourceFile(filePath, diskContent, ts.ScriptTarget.Latest, true);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Get source file from program (for type checking)
|
|
106
|
-
*/
|
|
107
|
-
getSourceFileFromProgram(filePath) {
|
|
108
|
-
// Ensure program is built
|
|
109
|
-
const checker = this.getChecker();
|
|
110
|
-
if (!checker || !this.program)
|
|
111
|
-
return null;
|
|
112
|
-
return this.program.getSourceFile(filePath) ?? null;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Clear all cached files
|
|
116
|
-
*/
|
|
117
|
-
clear() {
|
|
118
|
-
this.files.clear();
|
|
119
|
-
this.invalidate();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// --- Singleton ---
|
|
123
|
-
let instance = null;
|
|
124
|
-
export function getTypeScriptService(verbose = false) {
|
|
125
|
-
if (!instance) {
|
|
126
|
-
instance = new TypeScriptService(verbose);
|
|
127
|
-
}
|
|
128
|
-
return instance;
|
|
129
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeCoverage Client for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Measures what percentage of TypeScript identifiers are properly typed
|
|
5
|
-
* (i.e. not implicitly or explicitly `any`). Complements the LSP — the LSP
|
|
6
|
-
* catches hard errors, this catches type weakness.
|
|
7
|
-
*
|
|
8
|
-
* Requires: npm install -D type-coverage
|
|
9
|
-
* Docs: https://github.com/plantain-00/type-coverage
|
|
10
|
-
*/
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
13
|
-
// --- Client ---
|
|
14
|
-
export class TypeCoverageClient {
|
|
15
|
-
constructor(verbose = false) {
|
|
16
|
-
this.available = null;
|
|
17
|
-
this.log = verbose
|
|
18
|
-
? (msg) => console.error(`[type-coverage] ${msg}`)
|
|
19
|
-
: () => { };
|
|
20
|
-
}
|
|
21
|
-
isAvailable() {
|
|
22
|
-
if (this.available !== null)
|
|
23
|
-
return this.available;
|
|
24
|
-
const result = safeSpawn("npx", ["type-coverage", "--version"], {
|
|
25
|
-
timeout: 10000,
|
|
26
|
-
});
|
|
27
|
-
this.available = !result.error && result.status === 0;
|
|
28
|
-
return this.available;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Run type-coverage on the project at cwd.
|
|
32
|
-
* Uses --detail to get per-identifier locations for untyped names.
|
|
33
|
-
* Uses --strict to count `any` casts as untyped.
|
|
34
|
-
*/
|
|
35
|
-
scan(cwd) {
|
|
36
|
-
if (!this.isAvailable()) {
|
|
37
|
-
return {
|
|
38
|
-
success: false,
|
|
39
|
-
percentage: 0,
|
|
40
|
-
typed: 0,
|
|
41
|
-
total: 0,
|
|
42
|
-
untypedLocations: [],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const result = safeSpawn("npx", [
|
|
47
|
-
"type-coverage",
|
|
48
|
-
"--detail",
|
|
49
|
-
"--strict",
|
|
50
|
-
"--ignore-files",
|
|
51
|
-
"**/*.d.ts",
|
|
52
|
-
], {
|
|
53
|
-
timeout: 30000,
|
|
54
|
-
cwd,
|
|
55
|
-
});
|
|
56
|
-
const output = (result.stdout ?? "") + (result.stderr ?? "");
|
|
57
|
-
return this.parseOutput(output, cwd);
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
this.log(`Scan error: ${err.message}`);
|
|
61
|
-
return {
|
|
62
|
-
success: false,
|
|
63
|
-
percentage: 0,
|
|
64
|
-
typed: 0,
|
|
65
|
-
total: 0,
|
|
66
|
-
untypedLocations: [],
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
formatResult(result, maxLocations = 10) {
|
|
71
|
-
if (!result.success)
|
|
72
|
-
return "";
|
|
73
|
-
const pct = result.percentage.toFixed(1);
|
|
74
|
-
let icon = "✗";
|
|
75
|
-
if (result.percentage >= 95)
|
|
76
|
-
icon = "✓";
|
|
77
|
-
else if (result.percentage >= 80)
|
|
78
|
-
icon = "⚠";
|
|
79
|
-
let output = `[type-coverage] ${icon} ${pct}% typed (${result.typed}/${result.total} identifiers)`;
|
|
80
|
-
if (result.untypedLocations.length === 0) {
|
|
81
|
-
output += " — fully typed\n";
|
|
82
|
-
return output;
|
|
83
|
-
}
|
|
84
|
-
output += `:\n`;
|
|
85
|
-
for (const loc of result.untypedLocations.slice(0, maxLocations)) {
|
|
86
|
-
output += ` ${path.basename(loc.file)}:${loc.line}:${loc.column} — ${loc.name}\n`;
|
|
87
|
-
}
|
|
88
|
-
if (result.untypedLocations.length > maxLocations) {
|
|
89
|
-
output += ` ... and ${result.untypedLocations.length - maxLocations} more\n`;
|
|
90
|
-
}
|
|
91
|
-
return output;
|
|
92
|
-
}
|
|
93
|
-
// --- Internal ---
|
|
94
|
-
parseOutput(output, cwd) {
|
|
95
|
-
const untypedLocations = [];
|
|
96
|
-
// Parse detail lines: "path/to/file.ts:line:col: name"
|
|
97
|
-
const detailPattern = /^(.+):(\d+):(\d+):\s+(.+)$/gm;
|
|
98
|
-
let match;
|
|
99
|
-
while ((match = detailPattern.exec(output)) !== null) {
|
|
100
|
-
const [, file, line, col, name] = match;
|
|
101
|
-
// Skip the summary line which also matches the pattern
|
|
102
|
-
if (name.includes("%") || name.includes("/"))
|
|
103
|
-
continue;
|
|
104
|
-
untypedLocations.push({
|
|
105
|
-
file: path.resolve(cwd, file),
|
|
106
|
-
line: parseInt(line, 10),
|
|
107
|
-
column: parseInt(col, 10),
|
|
108
|
-
name: name.trim(),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
// Parse summary: "(3979 / 4100) 97.04%"
|
|
112
|
-
const summaryMatch = output.match(/\((\d+)\s*\/\s*(\d+)\)\s*([\d.]+)%/);
|
|
113
|
-
if (!summaryMatch) {
|
|
114
|
-
return {
|
|
115
|
-
success: false,
|
|
116
|
-
percentage: 0,
|
|
117
|
-
typed: 0,
|
|
118
|
-
total: 0,
|
|
119
|
-
untypedLocations: [],
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
const typed = parseInt(summaryMatch[1], 10);
|
|
123
|
-
const total = parseInt(summaryMatch[2], 10);
|
|
124
|
-
const percentage = parseFloat(summaryMatch[3]);
|
|
125
|
-
return { success: true, percentage, typed, total, untypedLocations };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { setupTestEnvironment } from "./test-utils.js";
|
|
3
|
-
import { TypeCoverageClient } from "./type-coverage-client.js";
|
|
4
|
-
describe("TypeCoverageClient", () => {
|
|
5
|
-
let client;
|
|
6
|
-
let _tmpDir;
|
|
7
|
-
let cleanup;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
client = new TypeCoverageClient();
|
|
10
|
-
({ tmpDir: _tmpDir, cleanup } = setupTestEnvironment("pi-lens-typecoverage-test-"));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
cleanup();
|
|
14
|
-
});
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
describe("isAvailable", () => {
|
|
19
|
-
it("should check type-coverage availability", () => {
|
|
20
|
-
const available = client.isAvailable();
|
|
21
|
-
expect(typeof available).toBe("boolean");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
describe("formatResult", () => {
|
|
25
|
-
it("should return empty string when not successful", () => {
|
|
26
|
-
const result = {
|
|
27
|
-
success: false,
|
|
28
|
-
percentage: 0,
|
|
29
|
-
typed: 0,
|
|
30
|
-
total: 0,
|
|
31
|
-
untypedLocations: [],
|
|
32
|
-
};
|
|
33
|
-
expect(client.formatResult(result)).toBe("");
|
|
34
|
-
});
|
|
35
|
-
it("should show coverage percentage", () => {
|
|
36
|
-
const result = {
|
|
37
|
-
success: true,
|
|
38
|
-
percentage: 95,
|
|
39
|
-
typed: 95,
|
|
40
|
-
total: 100,
|
|
41
|
-
untypedLocations: [],
|
|
42
|
-
};
|
|
43
|
-
const formatted = client.formatResult(result);
|
|
44
|
-
expect(formatted).toContain("95.0%");
|
|
45
|
-
expect(formatted).toContain("95/100");
|
|
46
|
-
});
|
|
47
|
-
it("should show warning for low coverage", () => {
|
|
48
|
-
const result = {
|
|
49
|
-
success: true,
|
|
50
|
-
percentage: 80,
|
|
51
|
-
typed: 80,
|
|
52
|
-
total: 100,
|
|
53
|
-
untypedLocations: [],
|
|
54
|
-
};
|
|
55
|
-
const formatted = client.formatResult(result);
|
|
56
|
-
expect(formatted).toContain("⚠");
|
|
57
|
-
});
|
|
58
|
-
it("should show checkmark for high coverage", () => {
|
|
59
|
-
const result = {
|
|
60
|
-
success: true,
|
|
61
|
-
percentage: 100,
|
|
62
|
-
typed: 100,
|
|
63
|
-
total: 100,
|
|
64
|
-
untypedLocations: [],
|
|
65
|
-
};
|
|
66
|
-
const formatted = client.formatResult(result);
|
|
67
|
-
expect(formatted).toContain("✓");
|
|
68
|
-
});
|
|
69
|
-
it("should show untyped locations", () => {
|
|
70
|
-
const result = {
|
|
71
|
-
success: true,
|
|
72
|
-
percentage: 90,
|
|
73
|
-
typed: 90,
|
|
74
|
-
total: 100,
|
|
75
|
-
untypedLocations: [
|
|
76
|
-
{ file: "test.ts", line: 10, column: 5, name: "x" },
|
|
77
|
-
{ file: "test.ts", line: 20, column: 8, name: "y" },
|
|
78
|
-
],
|
|
79
|
-
};
|
|
80
|
-
const formatted = client.formatResult(result);
|
|
81
|
-
expect(formatted).toContain("test.ts:10");
|
|
82
|
-
expect(formatted).toContain("test.ts:20");
|
|
83
|
-
expect(formatted).toContain("x");
|
|
84
|
-
expect(formatted).toContain("y");
|
|
85
|
-
});
|
|
86
|
-
it("should truncate long untyped location lists", () => {
|
|
87
|
-
const locations = Array.from({ length: 20 }, (_, i) => ({
|
|
88
|
-
file: `file${i}.ts`,
|
|
89
|
-
line: i + 1,
|
|
90
|
-
column: 0,
|
|
91
|
-
name: `var${i}`,
|
|
92
|
-
}));
|
|
93
|
-
const result = {
|
|
94
|
-
success: true,
|
|
95
|
-
percentage: 80,
|
|
96
|
-
typed: 80,
|
|
97
|
-
total: 100,
|
|
98
|
-
untypedLocations: locations,
|
|
99
|
-
};
|
|
100
|
-
const formatted = client.formatResult(result, 10);
|
|
101
|
-
expect(formatted).toContain("...");
|
|
102
|
-
expect(formatted).toContain("10 more");
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { setupTestEnvironment } from "./test-utils.js";
|
|
3
|
-
import { TypeCoverageClient } from "./type-coverage-client.js";
|
|
4
|
-
|
|
5
|
-
describe("TypeCoverageClient", () => {
|
|
6
|
-
let client: TypeCoverageClient;
|
|
7
|
-
let _tmpDir: string;
|
|
8
|
-
let cleanup: () => void;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
client = new TypeCoverageClient();
|
|
12
|
-
({ tmpDir: _tmpDir, cleanup } = setupTestEnvironment(
|
|
13
|
-
"pi-lens-typecoverage-test-",
|
|
14
|
-
));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
cleanup();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
cleanup();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe("isAvailable", () => {
|
|
26
|
-
it("should check type-coverage availability", () => {
|
|
27
|
-
const available = client.isAvailable();
|
|
28
|
-
expect(typeof available).toBe("boolean");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("formatResult", () => {
|
|
33
|
-
it("should return empty string when not successful", () => {
|
|
34
|
-
const result = {
|
|
35
|
-
success: false,
|
|
36
|
-
percentage: 0,
|
|
37
|
-
typed: 0,
|
|
38
|
-
total: 0,
|
|
39
|
-
untypedLocations: [],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
expect(client.formatResult(result)).toBe("");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should show coverage percentage", () => {
|
|
46
|
-
const result = {
|
|
47
|
-
success: true,
|
|
48
|
-
percentage: 95,
|
|
49
|
-
typed: 95,
|
|
50
|
-
total: 100,
|
|
51
|
-
untypedLocations: [],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const formatted = client.formatResult(result);
|
|
55
|
-
expect(formatted).toContain("95.0%");
|
|
56
|
-
expect(formatted).toContain("95/100");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should show warning for low coverage", () => {
|
|
60
|
-
const result = {
|
|
61
|
-
success: true,
|
|
62
|
-
percentage: 80,
|
|
63
|
-
typed: 80,
|
|
64
|
-
total: 100,
|
|
65
|
-
untypedLocations: [],
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const formatted = client.formatResult(result);
|
|
69
|
-
expect(formatted).toContain("⚠");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("should show checkmark for high coverage", () => {
|
|
73
|
-
const result = {
|
|
74
|
-
success: true,
|
|
75
|
-
percentage: 100,
|
|
76
|
-
typed: 100,
|
|
77
|
-
total: 100,
|
|
78
|
-
untypedLocations: [],
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const formatted = client.formatResult(result);
|
|
82
|
-
expect(formatted).toContain("✓");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("should show untyped locations", () => {
|
|
86
|
-
const result = {
|
|
87
|
-
success: true,
|
|
88
|
-
percentage: 90,
|
|
89
|
-
typed: 90,
|
|
90
|
-
total: 100,
|
|
91
|
-
untypedLocations: [
|
|
92
|
-
{ file: "test.ts", line: 10, column: 5, name: "x" },
|
|
93
|
-
{ file: "test.ts", line: 20, column: 8, name: "y" },
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const formatted = client.formatResult(result);
|
|
98
|
-
expect(formatted).toContain("test.ts:10");
|
|
99
|
-
expect(formatted).toContain("test.ts:20");
|
|
100
|
-
expect(formatted).toContain("x");
|
|
101
|
-
expect(formatted).toContain("y");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("should truncate long untyped location lists", () => {
|
|
105
|
-
const locations = Array.from({ length: 20 }, (_, i) => ({
|
|
106
|
-
file: `file${i}.ts`,
|
|
107
|
-
line: i + 1,
|
|
108
|
-
column: 0,
|
|
109
|
-
name: `var${i}`,
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
const result = {
|
|
113
|
-
success: true,
|
|
114
|
-
percentage: 80,
|
|
115
|
-
typed: 80,
|
|
116
|
-
total: 100,
|
|
117
|
-
untypedLocations: locations,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const formatted = client.formatResult(result, 10);
|
|
121
|
-
expect(formatted).toContain("...");
|
|
122
|
-
expect(formatted).toContain("10 more");
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type Safety Client for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Detects type safety violations that can cause runtime bugs.
|
|
5
|
-
* Uses the shared TypeScriptService for efficient type checking.
|
|
6
|
-
*
|
|
7
|
-
* Checks:
|
|
8
|
-
* - Switch Exhaustiveness: Missing cases in union type switches
|
|
9
|
-
* - Null Safety: Potential null/undefined dereferences (future)
|
|
10
|
-
* - Exhaustive Type Guards: Incomplete instanceof checks (future)
|
|
11
|
-
*/
|
|
12
|
-
import * as fs from "node:fs";
|
|
13
|
-
import * as path from "node:path";
|
|
14
|
-
import * as ts from "typescript";
|
|
15
|
-
import { getTypeScriptService } from "./ts-service.js";
|
|
16
|
-
// --- Client ---
|
|
17
|
-
export class TypeSafetyClient {
|
|
18
|
-
constructor(verbose = false) {
|
|
19
|
-
this.log = verbose
|
|
20
|
-
? (msg) => console.error(`[type-safety] ${msg}`)
|
|
21
|
-
: () => { };
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Check if file is supported (TS/JS)
|
|
25
|
-
*/
|
|
26
|
-
isSupportedFile(filePath) {
|
|
27
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
28
|
-
return [".ts", ".tsx", ".js", ".jsx"].includes(ext);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Analyze type safety issues for a file
|
|
32
|
-
*/
|
|
33
|
-
analyzeFile(filePath) {
|
|
34
|
-
const absolutePath = path.resolve(filePath);
|
|
35
|
-
if (!fs.existsSync(absolutePath))
|
|
36
|
-
return null;
|
|
37
|
-
try {
|
|
38
|
-
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
39
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
40
|
-
const issues = [];
|
|
41
|
-
// Check switch exhaustiveness
|
|
42
|
-
this.checkSwitchExhaustiveness(sourceFile, issues);
|
|
43
|
-
return { filePath: absolutePath, issues };
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
this.log(`Error analyzing ${filePath}: ${error}`);
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Check for switch statements that don't exhaust all union cases
|
|
52
|
-
*/
|
|
53
|
-
checkSwitchExhaustiveness(sourceFile, issues) {
|
|
54
|
-
const checker = this.getTypeChecker(sourceFile);
|
|
55
|
-
if (!checker)
|
|
56
|
-
return;
|
|
57
|
-
// Use the source file from the program (has proper type information)
|
|
58
|
-
const tsService = getTypeScriptService();
|
|
59
|
-
const programSourceFile = tsService.getSourceFileFromProgram(sourceFile.fileName);
|
|
60
|
-
if (!programSourceFile)
|
|
61
|
-
return;
|
|
62
|
-
const visit = (node) => {
|
|
63
|
-
if (ts.isSwitchStatement(node)) {
|
|
64
|
-
const exprType = checker.getTypeAtLocation(node.expression);
|
|
65
|
-
// Only check union types (literal unions and object unions)
|
|
66
|
-
if (exprType.isUnion()) {
|
|
67
|
-
const unionTypes = exprType.types;
|
|
68
|
-
// Get all literal values from the union
|
|
69
|
-
const literalValues = unionTypes
|
|
70
|
-
.filter((t) => t.isLiteral() || t.flags & ts.TypeFlags.BooleanLiteral)
|
|
71
|
-
.map((t) => {
|
|
72
|
-
if (t.isLiteral()) {
|
|
73
|
-
return String(t.value);
|
|
74
|
-
}
|
|
75
|
-
// Boolean literals
|
|
76
|
-
if (t.flags & ts.TypeFlags.BooleanLiteral) {
|
|
77
|
-
return checker.typeToString(t);
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
})
|
|
81
|
-
.filter((v) => v !== null);
|
|
82
|
-
// Skip if no literal union (e.g., string | number)
|
|
83
|
-
if (literalValues.length === 0)
|
|
84
|
-
return;
|
|
85
|
-
// Get all case clauses
|
|
86
|
-
const coveredCases = new Set();
|
|
87
|
-
for (const clause of node.caseBlock.clauses) {
|
|
88
|
-
if (ts.isCaseClause(clause)) {
|
|
89
|
-
const caseType = checker.getTypeAtLocation(clause.expression);
|
|
90
|
-
if (caseType.isLiteral()) {
|
|
91
|
-
coveredCases.add(String(caseType.value));
|
|
92
|
-
}
|
|
93
|
-
else if (caseType.flags & ts.TypeFlags.BooleanLiteral) {
|
|
94
|
-
coveredCases.add(checker.typeToString(caseType));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// Check for hasDefault
|
|
99
|
-
const hasDefault = node.caseBlock.clauses.some((c) => ts.isDefaultClause(c));
|
|
100
|
-
// Find missing cases
|
|
101
|
-
const missingCases = literalValues.filter((v) => !coveredCases.has(v));
|
|
102
|
-
if (missingCases.length > 0 && !hasDefault) {
|
|
103
|
-
const line = programSourceFile.getLineAndCharacterOfPosition(node.getStart())
|
|
104
|
-
.line + 1;
|
|
105
|
-
const exprText = node.expression.getText(programSourceFile);
|
|
106
|
-
const typeStr = missingCases.map((c) => `'${c}'`).join(", ");
|
|
107
|
-
issues.push({
|
|
108
|
-
filePath: programSourceFile.fileName,
|
|
109
|
-
rule: "switch-exhaustiveness",
|
|
110
|
-
line,
|
|
111
|
-
message: `Switch on '${exprText}' is not exhaustive. Missing cases: ${typeStr}`,
|
|
112
|
-
severity: "error",
|
|
113
|
-
context: `Type has ${literalValues.length} cases, ${coveredCases.size} covered, ${missingCases.length} missing`,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
ts.forEachChild(node, visit);
|
|
119
|
-
};
|
|
120
|
-
ts.forEachChild(programSourceFile, visit);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Get type checker from shared service
|
|
124
|
-
*/
|
|
125
|
-
getTypeChecker(sourceFile) {
|
|
126
|
-
const tsService = getTypeScriptService();
|
|
127
|
-
// Update the file in the shared service
|
|
128
|
-
const content = fs.readFileSync(sourceFile.fileName, "utf-8");
|
|
129
|
-
tsService.updateFile(sourceFile.fileName, content);
|
|
130
|
-
const checker = tsService.getChecker();
|
|
131
|
-
if (!checker) {
|
|
132
|
-
this.log("Could not get type checker, skipping exhaustiveness check");
|
|
133
|
-
}
|
|
134
|
-
return checker;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// --- Singleton ---
|
|
138
|
-
const _instance = null;
|
package/clients/types.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP Types - Core types for the pi-local LSP client
|
|
3
|
-
* Simplified from the full LSP spec to what we actually need
|
|
4
|
-
*/
|
|
5
|
-
export var DiagnosticSeverity;
|
|
6
|
-
(function (DiagnosticSeverity) {
|
|
7
|
-
DiagnosticSeverity[DiagnosticSeverity["Error"] = 1] = "Error";
|
|
8
|
-
DiagnosticSeverity[DiagnosticSeverity["Warning"] = 2] = "Warning";
|
|
9
|
-
DiagnosticSeverity[DiagnosticSeverity["Information"] = 3] = "Information";
|
|
10
|
-
DiagnosticSeverity[DiagnosticSeverity["Hint"] = 4] = "Hint";
|
|
11
|
-
})(DiagnosticSeverity || (DiagnosticSeverity = {}));
|