pi-lens 2.0.28 → 2.0.29
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 +12 -0
- package/README.md +2 -2
- package/clients/ts-service.js +129 -0
- package/clients/ts-service.ts +160 -0
- package/clients/type-safety-client.js +20 -28
- package/clients/type-safety-client.ts +21 -32
- package/index.ts +0 -4
- package/package.json +1 -1
- package/rules/ast-grep-rules/rules/no-useless-concat.yml +0 -17
- package/rules/ast-grep-rules/rules/no-var.yml +0 -10
- package/rules/ast-grep-rules/rules/prefer-template.yml +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.0.29] - 2026-03-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`clients/ts-service.ts`**: Shared TypeScript service that creates one `ts.Program` per session. Both `complexity-client` and `type-safety-client` now share the same program instead of creating a new one per file. Significant performance improvement on large codebases.
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- **3 redundant ast-grep rules** that overlap with Biome: `no-var`, `prefer-template`, `no-useless-concat`. Biome handles these natively with auto-fix. ast-grep no longer duplicates this coverage.
|
|
12
|
+
- **`prefer-const` from RULE_ACTIONS** — no longer needed (Biome handles directly).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Consolidated rule overlap**: Biome is now the single source of truth for style/format rules. ast-grep focuses on structural patterns Biome doesn't cover (security, design smells, AI slop).
|
|
16
|
+
|
|
5
17
|
## [2.0.27] - 2026-03-26
|
|
6
18
|
|
|
7
19
|
### Added
|
package/README.md
CHANGED
|
@@ -195,8 +195,8 @@ Each rule includes a `message` and `note` that are shown in diagnostics, so the
|
|
|
195
195
|
**TypeScript**
|
|
196
196
|
`no-any-type`, `no-as-any`, `no-non-null-assertion`
|
|
197
197
|
|
|
198
|
-
**Style**
|
|
199
|
-
`
|
|
198
|
+
**Style** (Biome handles `no-var`, `prefer-const`, `prefer-template`, `no-useless-concat` natively)
|
|
199
|
+
`prefer-nullish-coalescing`, `prefer-optional-chain`, `nested-ternary`, `no-lonely-if`
|
|
200
200
|
|
|
201
201
|
**Correctness**
|
|
202
202
|
`no-debugger`, `no-throw-string`, `no-return-await`, `no-await-in-loop`, `no-await-in-promise-all`, `require-await`, `empty-catch`, `strict-equality`, `strict-inequality`
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
export class TypeScriptService {
|
|
13
|
+
private program: ts.Program | null = null;
|
|
14
|
+
private checker: ts.TypeChecker | null = null;
|
|
15
|
+
private files = new Map<string, string>();
|
|
16
|
+
private log: (msg: string) => void;
|
|
17
|
+
|
|
18
|
+
constructor(verbose = false) {
|
|
19
|
+
this.log = verbose
|
|
20
|
+
? (msg: string) => console.error(`[ts-service] ${msg}`)
|
|
21
|
+
: () => {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Update a file's content in the service
|
|
26
|
+
*/
|
|
27
|
+
updateFile(filePath: string, content: string): void {
|
|
28
|
+
this.files.set(filePath, content);
|
|
29
|
+
this.invalidate();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Invalidate the program (rebuild on next access)
|
|
34
|
+
*/
|
|
35
|
+
invalidate(): void {
|
|
36
|
+
this.program = null;
|
|
37
|
+
this.checker = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the shared type checker (rebuilds program if needed)
|
|
42
|
+
*/
|
|
43
|
+
getChecker(): ts.TypeChecker | null {
|
|
44
|
+
if (this.checker) return this.checker;
|
|
45
|
+
|
|
46
|
+
if (this.files.size === 0) return null;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
50
|
+
target: ts.ScriptTarget.Latest,
|
|
51
|
+
module: ts.ModuleKind.ESNext,
|
|
52
|
+
strict: true,
|
|
53
|
+
noEmit: true,
|
|
54
|
+
skipLibCheck: true,
|
|
55
|
+
lib: ["es2020"],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
59
|
+
|
|
60
|
+
// Override getSourceFile to return our cached files
|
|
61
|
+
const originalGetSourceFile = host.getSourceFile;
|
|
62
|
+
host.getSourceFile = (fileName, languageVersion) => {
|
|
63
|
+
// Check if we have this file cached
|
|
64
|
+
const cachedContent = this.files.get(fileName);
|
|
65
|
+
if (cachedContent !== undefined) {
|
|
66
|
+
return ts.createSourceFile(
|
|
67
|
+
fileName,
|
|
68
|
+
cachedContent,
|
|
69
|
+
languageVersion,
|
|
70
|
+
true,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// Fall back to default (for lib files, etc.)
|
|
74
|
+
return originalGetSourceFile(fileName, languageVersion);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Override fileExists and readFile
|
|
78
|
+
const originalFileExists = host.fileExists;
|
|
79
|
+
host.fileExists = (fileName) => {
|
|
80
|
+
if (this.files.has(fileName)) return true;
|
|
81
|
+
return originalFileExists(fileName);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const originalReadFile = host.readFile;
|
|
85
|
+
host.readFile = (fileName) => {
|
|
86
|
+
const cached = this.files.get(fileName);
|
|
87
|
+
if (cached !== undefined) return cached;
|
|
88
|
+
return originalReadFile(fileName);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const fileNames = [...this.files.keys()];
|
|
92
|
+
this.program = ts.createProgram(fileNames, compilerOptions, host);
|
|
93
|
+
this.checker = this.program.getTypeChecker();
|
|
94
|
+
|
|
95
|
+
this.log(`Program created with ${fileNames.length} files`);
|
|
96
|
+
return this.checker;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.log(`Error creating program: ${error}`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get source file for a path
|
|
105
|
+
*/
|
|
106
|
+
getSourceFile(filePath: string): ts.SourceFile | null {
|
|
107
|
+
const content = this.files.get(filePath);
|
|
108
|
+
if (!content) {
|
|
109
|
+
// Try to read from disk
|
|
110
|
+
try {
|
|
111
|
+
const diskContent = fs.readFileSync(filePath, "utf-8");
|
|
112
|
+
this.files.set(filePath, diskContent);
|
|
113
|
+
return ts.createSourceFile(
|
|
114
|
+
filePath,
|
|
115
|
+
diskContent,
|
|
116
|
+
ts.ScriptTarget.Latest,
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return ts.createSourceFile(
|
|
124
|
+
filePath,
|
|
125
|
+
content,
|
|
126
|
+
ts.ScriptTarget.Latest,
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get source file from program (for type checking)
|
|
133
|
+
*/
|
|
134
|
+
getSourceFileFromProgram(filePath: string): ts.SourceFile | null {
|
|
135
|
+
// Ensure program is built
|
|
136
|
+
const checker = this.getChecker();
|
|
137
|
+
if (!checker || !this.program) return null;
|
|
138
|
+
|
|
139
|
+
return this.program.getSourceFile(filePath) ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Clear all cached files
|
|
144
|
+
*/
|
|
145
|
+
clear(): void {
|
|
146
|
+
this.files.clear();
|
|
147
|
+
this.invalidate();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Singleton ---
|
|
152
|
+
|
|
153
|
+
let instance: TypeScriptService | null = null;
|
|
154
|
+
|
|
155
|
+
export function getTypeScriptService(verbose = false): TypeScriptService {
|
|
156
|
+
if (!instance) {
|
|
157
|
+
instance = new TypeScriptService(verbose);
|
|
158
|
+
}
|
|
159
|
+
return instance;
|
|
160
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Type Safety Client for pi-lens
|
|
3
3
|
*
|
|
4
4
|
* Detects type safety violations that can cause runtime bugs.
|
|
5
|
-
* Uses the
|
|
5
|
+
* Uses the shared TypeScriptService for efficient type checking.
|
|
6
6
|
*
|
|
7
7
|
* Checks:
|
|
8
8
|
* - Switch Exhaustiveness: Missing cases in union type switches
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import * as ts from "typescript";
|
|
15
|
+
import { getTypeScriptService } from "./ts-service.js";
|
|
15
16
|
// --- Client ---
|
|
16
17
|
export class TypeSafetyClient {
|
|
17
18
|
constructor(verbose = false) {
|
|
@@ -53,6 +54,11 @@ export class TypeSafetyClient {
|
|
|
53
54
|
const checker = this.getTypeChecker(sourceFile);
|
|
54
55
|
if (!checker)
|
|
55
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;
|
|
56
62
|
const visit = (node) => {
|
|
57
63
|
if (ts.isSwitchStatement(node)) {
|
|
58
64
|
const exprType = checker.getTypeAtLocation(node.expression);
|
|
@@ -94,11 +100,11 @@ export class TypeSafetyClient {
|
|
|
94
100
|
// Find missing cases
|
|
95
101
|
const missingCases = literalValues.filter((v) => !coveredCases.has(v));
|
|
96
102
|
if (missingCases.length > 0 && !hasDefault) {
|
|
97
|
-
const line =
|
|
98
|
-
const exprText = node.expression.getText(
|
|
103
|
+
const line = programSourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
104
|
+
const exprText = node.expression.getText(programSourceFile);
|
|
99
105
|
const typeStr = missingCases.map((c) => `'${c}'`).join(", ");
|
|
100
106
|
issues.push({
|
|
101
|
-
filePath:
|
|
107
|
+
filePath: programSourceFile.fileName,
|
|
102
108
|
rule: "switch-exhaustiveness",
|
|
103
109
|
line,
|
|
104
110
|
message: `Switch on '${exprText}' is not exhaustive. Missing cases: ${typeStr}`,
|
|
@@ -110,35 +116,21 @@ export class TypeSafetyClient {
|
|
|
110
116
|
}
|
|
111
117
|
ts.forEachChild(node, visit);
|
|
112
118
|
};
|
|
113
|
-
ts.forEachChild(
|
|
119
|
+
ts.forEachChild(programSourceFile, visit);
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
116
|
-
* Get type checker
|
|
122
|
+
* Get type checker from shared service
|
|
117
123
|
*/
|
|
118
124
|
getTypeChecker(sourceFile) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
// Create a host that uses our pre-parsed source file
|
|
128
|
-
const host = ts.createCompilerHost(compilerOptions);
|
|
129
|
-
const originalGetSourceFile = host.getSourceFile;
|
|
130
|
-
host.getSourceFile = (fileName, languageVersion) => {
|
|
131
|
-
if (fileName === sourceFile.fileName)
|
|
132
|
-
return sourceFile;
|
|
133
|
-
return originalGetSourceFile(fileName, languageVersion);
|
|
134
|
-
};
|
|
135
|
-
const program = ts.createProgram([sourceFile.fileName], compilerOptions, host);
|
|
136
|
-
return program.getTypeChecker();
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
this.log("Could not create type checker, skipping exhaustiveness check");
|
|
140
|
-
return null;
|
|
125
|
+
const tsService = getTypeScriptService();
|
|
126
|
+
// Update the file in the shared service
|
|
127
|
+
const content = fs.readFileSync(sourceFile.fileName, "utf-8");
|
|
128
|
+
tsService.updateFile(sourceFile.fileName, content);
|
|
129
|
+
const checker = tsService.getChecker();
|
|
130
|
+
if (!checker) {
|
|
131
|
+
this.log("Could not get type checker, skipping exhaustiveness check");
|
|
141
132
|
}
|
|
133
|
+
return checker;
|
|
142
134
|
}
|
|
143
135
|
}
|
|
144
136
|
// --- Singleton ---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Type Safety Client for pi-lens
|
|
3
3
|
*
|
|
4
4
|
* Detects type safety violations that can cause runtime bugs.
|
|
5
|
-
* Uses the
|
|
5
|
+
* Uses the shared TypeScriptService for efficient type checking.
|
|
6
6
|
*
|
|
7
7
|
* Checks:
|
|
8
8
|
* - Switch Exhaustiveness: Missing cases in union type switches
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import * as ts from "typescript";
|
|
16
|
+
import { getTypeScriptService } from "./ts-service.js";
|
|
16
17
|
|
|
17
18
|
// --- Types ---
|
|
18
19
|
|
|
@@ -87,6 +88,11 @@ export class TypeSafetyClient {
|
|
|
87
88
|
const checker = this.getTypeChecker(sourceFile);
|
|
88
89
|
if (!checker) return;
|
|
89
90
|
|
|
91
|
+
// Use the source file from the program (has proper type information)
|
|
92
|
+
const tsService = getTypeScriptService();
|
|
93
|
+
const programSourceFile = tsService.getSourceFileFromProgram(sourceFile.fileName);
|
|
94
|
+
if (!programSourceFile) return;
|
|
95
|
+
|
|
90
96
|
const visit = (node: ts.Node) => {
|
|
91
97
|
if (ts.isSwitchStatement(node)) {
|
|
92
98
|
const exprType = checker.getTypeAtLocation(node.expression);
|
|
@@ -137,15 +143,15 @@ export class TypeSafetyClient {
|
|
|
137
143
|
);
|
|
138
144
|
|
|
139
145
|
if (missingCases.length > 0 && !hasDefault) {
|
|
140
|
-
const line =
|
|
146
|
+
const line = programSourceFile.getLineAndCharacterOfPosition(
|
|
141
147
|
node.getStart(),
|
|
142
148
|
).line + 1;
|
|
143
149
|
|
|
144
|
-
const exprText = node.expression.getText(
|
|
150
|
+
const exprText = node.expression.getText(programSourceFile);
|
|
145
151
|
const typeStr = missingCases.map((c) => `'${c}'`).join(", ");
|
|
146
152
|
|
|
147
153
|
issues.push({
|
|
148
|
-
filePath:
|
|
154
|
+
filePath: programSourceFile.fileName,
|
|
149
155
|
rule: "switch-exhaustiveness",
|
|
150
156
|
line,
|
|
151
157
|
message: `Switch on '${exprText}' is not exhaustive. Missing cases: ${typeStr}`,
|
|
@@ -159,41 +165,24 @@ export class TypeSafetyClient {
|
|
|
159
165
|
ts.forEachChild(node, visit);
|
|
160
166
|
};
|
|
161
167
|
|
|
162
|
-
ts.forEachChild(
|
|
168
|
+
ts.forEachChild(programSourceFile, visit);
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
/**
|
|
166
|
-
* Get type checker
|
|
172
|
+
* Get type checker from shared service
|
|
167
173
|
*/
|
|
168
174
|
private getTypeChecker(sourceFile: ts.SourceFile): ts.TypeChecker | null {
|
|
169
|
-
|
|
170
|
-
const compilerOptions: ts.CompilerOptions = {
|
|
171
|
-
target: ts.ScriptTarget.Latest,
|
|
172
|
-
module: ts.ModuleKind.ESNext,
|
|
173
|
-
strict: true,
|
|
174
|
-
noEmit: true,
|
|
175
|
-
skipLibCheck: true,
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Create a host that uses our pre-parsed source file
|
|
179
|
-
const host = ts.createCompilerHost(compilerOptions);
|
|
180
|
-
const originalGetSourceFile = host.getSourceFile;
|
|
181
|
-
host.getSourceFile = (fileName, languageVersion) => {
|
|
182
|
-
if (fileName === sourceFile.fileName) return sourceFile;
|
|
183
|
-
return originalGetSourceFile(fileName, languageVersion);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const program = ts.createProgram(
|
|
187
|
-
[sourceFile.fileName],
|
|
188
|
-
compilerOptions,
|
|
189
|
-
host,
|
|
190
|
-
);
|
|
175
|
+
const tsService = getTypeScriptService();
|
|
191
176
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
177
|
+
// Update the file in the shared service
|
|
178
|
+
const content = fs.readFileSync(sourceFile.fileName, "utf-8");
|
|
179
|
+
tsService.updateFile(sourceFile.fileName, content);
|
|
180
|
+
|
|
181
|
+
const checker = tsService.getChecker();
|
|
182
|
+
if (!checker) {
|
|
183
|
+
this.log("Could not get type checker, skipping exhaustiveness check");
|
|
196
184
|
}
|
|
185
|
+
return checker;
|
|
197
186
|
}
|
|
198
187
|
|
|
199
188
|
}
|
package/index.ts
CHANGED
|
@@ -613,11 +613,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
613
613
|
string,
|
|
614
614
|
{ type: "biome" | "agent" | "skip"; note: string }
|
|
615
615
|
> = {
|
|
616
|
-
"no-var": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
617
|
-
"prefer-template": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
618
|
-
"no-useless-concat": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
619
616
|
"no-lonely-if": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
620
|
-
"prefer-const": { type: "biome", note: "auto-fixed by Biome --write" },
|
|
621
617
|
"empty-catch": {
|
|
622
618
|
type: "agent",
|
|
623
619
|
note: "Add this.log('Error: ' + err.message) to the catch block",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.29",
|
|
4
4
|
"description": "Real-time code quality feedback for pi — TypeScript LSP, Biome, ast-grep, Ruff, complexity metrics, duplicate detection. Includes automated fix loop (/lens-booboo-fix) and interactive architectural refactoring (/lens-booboo-refactor) with browser-based interviews.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
id: no-useless-concat
|
|
2
|
-
language: TypeScript
|
|
3
|
-
message: "Unnecessary string concatenation with adjacent string literals"
|
|
4
|
-
severity: warning
|
|
5
|
-
note: |
|
|
6
|
-
Adjacent string literals should be combined into a single string literal
|
|
7
|
-
rather than concatenated with the + operator.
|
|
8
|
-
rule:
|
|
9
|
-
all:
|
|
10
|
-
- pattern: $A + $B
|
|
11
|
-
- any:
|
|
12
|
-
- has:
|
|
13
|
-
kind: string
|
|
14
|
-
pattern: "$A"
|
|
15
|
-
- has:
|
|
16
|
-
kind: string
|
|
17
|
-
pattern: "$B"
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
id: no-var
|
|
2
|
-
language: TypeScript
|
|
3
|
-
message: "Use 'const' or 'let' instead of 'var'"
|
|
4
|
-
severity: warning
|
|
5
|
-
note: |
|
|
6
|
-
var has function scope and can lead to unexpected hoisting behavior.
|
|
7
|
-
Use const for values that don't change, let for values that do.
|
|
8
|
-
rule:
|
|
9
|
-
pattern: var $X = $Y
|
|
10
|
-
fix: let $X = $Y
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
id: prefer-template
|
|
2
|
-
language: TypeScript
|
|
3
|
-
message: "Use template literals instead of string concatenation"
|
|
4
|
-
severity: warning
|
|
5
|
-
note: |
|
|
6
|
-
Template literals are more readable and less error-prone than string
|
|
7
|
-
concatenation with the + operator.
|
|
8
|
-
rule:
|
|
9
|
-
all:
|
|
10
|
-
- pattern: $A + $B
|
|
11
|
-
- any:
|
|
12
|
-
- inside:
|
|
13
|
-
kind: binary_expression
|
|
14
|
-
has:
|
|
15
|
-
pattern: $A + $B
|
|
16
|
-
any:
|
|
17
|
-
- has:
|
|
18
|
-
kind: string
|
|
19
|
-
- has:
|
|
20
|
-
kind: template_string
|