pi-lens 3.6.2 → 3.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tree-sitter Structural Analysis Runner
|
|
3
|
-
*
|
|
4
|
-
* Executes all loaded tree-sitter query files from rules/tree-sitter-queries/
|
|
5
|
-
* for fast AST-based pattern matching.
|
|
6
|
-
* Updated: ast-grep-napi test
|
|
7
|
-
*/
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import { RuleCache } from "../../cache/rule-cache.js";
|
|
11
|
-
import { normalizeMapKey } from "../../path-utils.js";
|
|
12
|
-
import { TreeSitterClient } from "../../tree-sitter-client.js";
|
|
13
|
-
import { queryLoader, } from "../../tree-sitter-query-loader.js";
|
|
14
|
-
// Module-level singleton: web-tree-sitter WASM must only be initialized once per process.
|
|
15
|
-
// Creating a new TreeSitterClient() on every write resets TRANSFER_BUFFER (a module-level
|
|
16
|
-
// WASM pointer) — concurrent writes race on _ts_init() and corrupt shared WASM state → crash.
|
|
17
|
-
let _sharedClient = null;
|
|
18
|
-
function getSharedClient() {
|
|
19
|
-
if (!_sharedClient) {
|
|
20
|
-
_sharedClient = new TreeSitterClient();
|
|
21
|
-
}
|
|
22
|
-
return _sharedClient;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Check if a code block is effectively empty (ignoring comments and whitespace)
|
|
26
|
-
*/
|
|
27
|
-
function isEmptyBlock(blockContent) {
|
|
28
|
-
// Remove comments, whitespace, and check if anything remains
|
|
29
|
-
const cleaned = blockContent
|
|
30
|
-
.replace(/\/\/.*$/gm, "") // Remove single-line comments
|
|
31
|
-
.replace(/\/\*[\s\S]*?\*\//g, "") // Remove multi-line comments
|
|
32
|
-
.replace(/\s+/g, "") // Remove all whitespace
|
|
33
|
-
.trim();
|
|
34
|
-
return cleaned.length === 0 || cleaned === "{}";
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Extract parameter count from match text
|
|
38
|
-
*/
|
|
39
|
-
function countParameters(matchText) {
|
|
40
|
-
// Count commas in parameter list, or check for non-empty params
|
|
41
|
-
// Simple heuristic: count commas + 1, or 0 if empty
|
|
42
|
-
const paramsMatch = matchText.match(/\((.*)\)/);
|
|
43
|
-
if (!paramsMatch)
|
|
44
|
-
return 0;
|
|
45
|
-
const params = paramsMatch[1].trim();
|
|
46
|
-
if (!params)
|
|
47
|
-
return 0;
|
|
48
|
-
return params.split(",").length;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Apply post-filter to determine if a match should be reported
|
|
52
|
-
*/
|
|
53
|
-
function applyPostFilter(query, captures) {
|
|
54
|
-
if (!query.post_filter)
|
|
55
|
-
return true; // No filter = always include
|
|
56
|
-
switch (query.post_filter) {
|
|
57
|
-
case "empty_body": {
|
|
58
|
-
// Check if the BODY capture is effectively empty
|
|
59
|
-
const body = captures.BODY || captures.body || "";
|
|
60
|
-
return isEmptyBlock(body);
|
|
61
|
-
}
|
|
62
|
-
case "count_params": {
|
|
63
|
-
// Check if parameter count meets minimum
|
|
64
|
-
const minParams = query.post_filter_params?.min_params || 6;
|
|
65
|
-
// Get PARAMS capture which contains the parameter list like "(a, b, c)"
|
|
66
|
-
const params = captures.PARAMS || captures.params || captures.PARAM || "";
|
|
67
|
-
const paramCount = countParameters(params);
|
|
68
|
-
return paramCount >= minParams;
|
|
69
|
-
}
|
|
70
|
-
case "not_dbg_method":
|
|
71
|
-
// Exclude debug methods (for console-statement)
|
|
72
|
-
return !/\b(dbg|debug|logDebug)\b/i.test(captures.METHOD || "");
|
|
73
|
-
default:
|
|
74
|
-
// Unknown filter - include by default (safer than excluding)
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Check if variable name matches secret patterns
|
|
80
|
-
* This handles the #match? predicate from tree-sitter queries
|
|
81
|
-
*/
|
|
82
|
-
function matchesSecretPattern(varName) {
|
|
83
|
-
const secretPatterns = [
|
|
84
|
-
/api[_-]?key/i,
|
|
85
|
-
/api[_-]?secret/i,
|
|
86
|
-
/password/i,
|
|
87
|
-
/secret/i,
|
|
88
|
-
/token/i,
|
|
89
|
-
/auth/i,
|
|
90
|
-
/private[_-]?key/i,
|
|
91
|
-
/access[_-]?token/i,
|
|
92
|
-
/credentials/i,
|
|
93
|
-
/aws[_-]?secret/i,
|
|
94
|
-
/github[_-]?token/i,
|
|
95
|
-
];
|
|
96
|
-
return secretPatterns.some((pattern) => pattern.test(varName));
|
|
97
|
-
}
|
|
98
|
-
const treeSitterRunner = {
|
|
99
|
-
id: "tree-sitter",
|
|
100
|
-
appliesTo: ["jsts", "python"],
|
|
101
|
-
priority: 14, // Between oxlint (12) and ast-grep-napi (15)
|
|
102
|
-
enabledByDefault: true,
|
|
103
|
-
skipTestFiles: false, // Run on test files too (structural issues matter there)
|
|
104
|
-
async run(ctx) {
|
|
105
|
-
// Use singleton client — WASM must never be re-initialized after first call
|
|
106
|
-
const client = getSharedClient();
|
|
107
|
-
if (!client.isAvailable()) {
|
|
108
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
109
|
-
}
|
|
110
|
-
const initialized = await client.init();
|
|
111
|
-
if (!initialized) {
|
|
112
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
113
|
-
}
|
|
114
|
-
// Determine language from file extension
|
|
115
|
-
const filePath = ctx.filePath;
|
|
116
|
-
const isPython = filePath.endsWith(".py");
|
|
117
|
-
const isTypeScript = filePath.endsWith(".ts");
|
|
118
|
-
const isTSX = filePath.endsWith(".tsx");
|
|
119
|
-
const isJavaScript = filePath.endsWith(".js") || filePath.endsWith(".jsx");
|
|
120
|
-
let languageId;
|
|
121
|
-
if (isPython) {
|
|
122
|
-
languageId = "python";
|
|
123
|
-
}
|
|
124
|
-
else if (isTSX) {
|
|
125
|
-
languageId = "tsx";
|
|
126
|
-
}
|
|
127
|
-
else if (isTypeScript) {
|
|
128
|
-
languageId = "typescript";
|
|
129
|
-
}
|
|
130
|
-
else if (isJavaScript) {
|
|
131
|
-
languageId = "javascript";
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
135
|
-
}
|
|
136
|
-
// Try cache first, fall back to loading from disk
|
|
137
|
-
let languageQueries = [];
|
|
138
|
-
const cache = new RuleCache(languageId);
|
|
139
|
-
// Get all rule files for this language (use ctx.cwd for project root)
|
|
140
|
-
const rulesDir = path.join(ctx.cwd, "rules", "tree-sitter-queries", languageId);
|
|
141
|
-
const ruleFiles = [];
|
|
142
|
-
if (fs.existsSync(rulesDir)) {
|
|
143
|
-
ruleFiles.push(...fs
|
|
144
|
-
.readdirSync(rulesDir)
|
|
145
|
-
.filter((f) => f.endsWith(".yml"))
|
|
146
|
-
.map((f) => path.join(rulesDir, f)));
|
|
147
|
-
}
|
|
148
|
-
// Try cache
|
|
149
|
-
const cached = cache.get(ruleFiles);
|
|
150
|
-
if (cached) {
|
|
151
|
-
// Use cached queries
|
|
152
|
-
languageQueries = cached.queries.map((q) => ({
|
|
153
|
-
...q,
|
|
154
|
-
has_fix: false,
|
|
155
|
-
filePath: "",
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// Load from disk
|
|
160
|
-
if (!queryLoader.getAllQueries().length) {
|
|
161
|
-
await queryLoader.loadQueries();
|
|
162
|
-
}
|
|
163
|
-
const allQueries = queryLoader.getAllQueries();
|
|
164
|
-
languageQueries = allQueries.filter((q) => q.language === languageId ||
|
|
165
|
-
(isJavaScript && q.language === "typescript"));
|
|
166
|
-
// Save to cache
|
|
167
|
-
cache.set(ruleFiles, languageQueries.map((q) => ({
|
|
168
|
-
id: q.id,
|
|
169
|
-
name: q.name,
|
|
170
|
-
severity: q.severity,
|
|
171
|
-
language: q.language,
|
|
172
|
-
message: q.message,
|
|
173
|
-
query: q.query,
|
|
174
|
-
metavars: q.metavars,
|
|
175
|
-
post_filter: q.post_filter,
|
|
176
|
-
post_filter_params: q.post_filter_params,
|
|
177
|
-
})));
|
|
178
|
-
}
|
|
179
|
-
if (languageQueries.length === 0) {
|
|
180
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
181
|
-
}
|
|
182
|
-
const diagnostics = [];
|
|
183
|
-
// Run each query against the file
|
|
184
|
-
for (const query of languageQueries) {
|
|
185
|
-
try {
|
|
186
|
-
// Extract directory from file path (use path.dirname for cross-platform)
|
|
187
|
-
const rootDir = path.dirname(filePath);
|
|
188
|
-
const matches = await client.structuralSearch(query.id, // Use query ID as pattern (findMatchingQuery will resolve it)
|
|
189
|
-
languageId, rootDir, {
|
|
190
|
-
maxResults: 10,
|
|
191
|
-
fileFilter: (f) => normalizeMapKey(f) === normalizeMapKey(filePath),
|
|
192
|
-
});
|
|
193
|
-
for (const match of matches) {
|
|
194
|
-
// Apply post-filter if defined (pass captures for proper filtering)
|
|
195
|
-
if (!applyPostFilter(query, match.captures)) {
|
|
196
|
-
continue; // Skip this match - filter didn't pass
|
|
197
|
-
}
|
|
198
|
-
// For hardcoded-secrets, also check variable name pattern
|
|
199
|
-
if (query.id === "hardcoded-secrets") {
|
|
200
|
-
// Extract variable name from captures
|
|
201
|
-
const varName = match.captures?.VARNAME || "";
|
|
202
|
-
if (!varName || !matchesSecretPattern(varName)) {
|
|
203
|
-
continue; // Skip - no variable name or doesn't match secret patterns
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
// Get line/column from match (already 0-indexed from tree-sitter)
|
|
207
|
-
const line = match.line;
|
|
208
|
-
const column = match.column;
|
|
209
|
-
// Map severity to semantic
|
|
210
|
-
const semantic = query.severity === "error"
|
|
211
|
-
? "blocking"
|
|
212
|
-
: query.severity === "warning"
|
|
213
|
-
? "warning"
|
|
214
|
-
: "none";
|
|
215
|
-
diagnostics.push({
|
|
216
|
-
id: `tree-sitter:${query.id}:${line}`,
|
|
217
|
-
message: query.message,
|
|
218
|
-
filePath,
|
|
219
|
-
line: line + 1, // 1-indexed
|
|
220
|
-
column: column + 1, // 1-indexed
|
|
221
|
-
severity: query.severity,
|
|
222
|
-
semantic,
|
|
223
|
-
tool: "tree-sitter",
|
|
224
|
-
rule: query.id,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
// Individual query failure shouldn't stop other queries
|
|
230
|
-
console.error(`[tree-sitter] Query ${query.id} failed:`, err);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (diagnostics.length === 0) {
|
|
234
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
235
|
-
}
|
|
236
|
-
// Check if any blocking issues
|
|
237
|
-
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
238
|
-
return {
|
|
239
|
-
status: hasBlocking ? "failed" : "succeeded",
|
|
240
|
-
diagnostics,
|
|
241
|
-
semantic: hasBlocking ? "blocking" : "warning",
|
|
242
|
-
};
|
|
243
|
-
},
|
|
244
|
-
};
|
|
245
|
-
export default treeSitterRunner;
|
|
246
|
-
// test ast-grep-napi re-enable
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript LSP runner for dispatch system
|
|
3
|
-
*
|
|
4
|
-
* Uses the new LSP client architecture (Phase 3) when --lens-lsp is enabled.
|
|
5
|
-
* Falls back to built-in TypeScriptClient for backward compatibility.
|
|
6
|
-
*
|
|
7
|
-
* @deprecated The built-in TypeScriptClient is deprecated. Use --lens-lsp for full LSP support.
|
|
8
|
-
*/
|
|
9
|
-
import { getLSPService } from "../../lsp/index.js";
|
|
10
|
-
import { TypeScriptClient } from "../../typescript-client.js";
|
|
11
|
-
import { readFileContent } from "./utils.js";
|
|
12
|
-
const tsLspRunner = {
|
|
13
|
-
id: "ts-lsp",
|
|
14
|
-
appliesTo: ["jsts"],
|
|
15
|
-
priority: 5,
|
|
16
|
-
enabledByDefault: true,
|
|
17
|
-
async run(ctx) {
|
|
18
|
-
// Only check TypeScript files
|
|
19
|
-
if (!ctx.filePath.match(/\.tsx?$/)) {
|
|
20
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
21
|
-
}
|
|
22
|
-
// When --lens-lsp is active, the `lsp` runner (priority 4) already
|
|
23
|
-
// handles TypeScript via the LSP service. Skip to avoid duplicate work.
|
|
24
|
-
if (ctx.pi.getFlag("lens-lsp")) {
|
|
25
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
26
|
-
}
|
|
27
|
-
// DEPRECATED: Fall back to built-in TypeScriptClient
|
|
28
|
-
// This path is deprecated and will be removed in a future release
|
|
29
|
-
return runWithBuiltinClient(ctx);
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* Run with new LSP client (Phase 3)
|
|
34
|
-
*/
|
|
35
|
-
async function runWithLSPClient(ctx) {
|
|
36
|
-
const lspService = getLSPService();
|
|
37
|
-
// Check if we have LSP available for this file
|
|
38
|
-
const hasLSP = await lspService.hasLSP(ctx.filePath);
|
|
39
|
-
if (!hasLSP) {
|
|
40
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
41
|
-
}
|
|
42
|
-
// Read file content
|
|
43
|
-
const content = readFileContent(ctx.filePath);
|
|
44
|
-
if (!content) {
|
|
45
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
46
|
-
}
|
|
47
|
-
// Open file in LSP and get diagnostics
|
|
48
|
-
await lspService.openFile(ctx.filePath, content);
|
|
49
|
-
// getDiagnostics() internally calls waitForDiagnostics() with bus
|
|
50
|
-
// subscription + 150ms debounce + 3s timeout
|
|
51
|
-
const lspDiags = await lspService.getDiagnostics(ctx.filePath);
|
|
52
|
-
// Convert LSP diagnostics to our format
|
|
53
|
-
// Defensive: filter out malformed diagnostics that may lack range
|
|
54
|
-
const diagnostics = lspDiags
|
|
55
|
-
.filter((d) => d.range?.start?.line !== undefined)
|
|
56
|
-
.map((d) => ({
|
|
57
|
-
id: `ts-lsp:${d.code ?? "unknown"}:${d.range.start.line}`,
|
|
58
|
-
message: d.message,
|
|
59
|
-
filePath: ctx.filePath,
|
|
60
|
-
line: d.range.start.line + 1,
|
|
61
|
-
column: d.range.start.character + 1,
|
|
62
|
-
severity: d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info",
|
|
63
|
-
semantic: d.severity === 1 ? "blocking" : "warning",
|
|
64
|
-
tool: "ts-lsp",
|
|
65
|
-
code: String(d.code ?? ""),
|
|
66
|
-
}));
|
|
67
|
-
return {
|
|
68
|
-
status: "failed",
|
|
69
|
-
diagnostics,
|
|
70
|
-
semantic: "blocking",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Run with deprecated built-in TypeScriptClient
|
|
75
|
-
* @deprecated Use runWithLSPClient instead
|
|
76
|
-
*/
|
|
77
|
-
async function runWithBuiltinClient(ctx) {
|
|
78
|
-
const tsClient = new TypeScriptClient();
|
|
79
|
-
const content = readFileContent(ctx.filePath);
|
|
80
|
-
if (!content) {
|
|
81
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
82
|
-
}
|
|
83
|
-
tsClient.updateFile(ctx.filePath, content);
|
|
84
|
-
const diags = tsClient.getDiagnostics(ctx.filePath);
|
|
85
|
-
if (diags.length === 0) {
|
|
86
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
87
|
-
}
|
|
88
|
-
// Get code fixes for all errors
|
|
89
|
-
const allFixes = tsClient.getAllCodeFixes(ctx.filePath);
|
|
90
|
-
// Convert to diagnostics
|
|
91
|
-
const diagnostics = [];
|
|
92
|
-
// The built-in client returns Diagnostic with { range: { start: { line, character } } }
|
|
93
|
-
for (const d of diags) {
|
|
94
|
-
// Safely access nested properties
|
|
95
|
-
if (!d.range?.start)
|
|
96
|
-
continue;
|
|
97
|
-
const line = d.range.start.line;
|
|
98
|
-
const character = d.range.start.character ?? 0;
|
|
99
|
-
const severity = d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info";
|
|
100
|
-
const semantic = d.severity === 1 ? "blocking" : "warning";
|
|
101
|
-
// Find fixes for this line
|
|
102
|
-
const lineFixes = allFixes.get(line);
|
|
103
|
-
const fixDescription = lineFixes?.[0]?.description;
|
|
104
|
-
diagnostics.push({
|
|
105
|
-
id: `ts:${d.code}:${line}`,
|
|
106
|
-
message: fixDescription
|
|
107
|
-
? `${d.message} [💡 ${fixDescription}]`
|
|
108
|
-
: d.message,
|
|
109
|
-
filePath: ctx.filePath,
|
|
110
|
-
line: line + 1,
|
|
111
|
-
column: character + 1,
|
|
112
|
-
severity,
|
|
113
|
-
semantic,
|
|
114
|
-
tool: "ts-lsp",
|
|
115
|
-
fixable: !!lineFixes && lineFixes.length > 0,
|
|
116
|
-
fixSuggestion: fixDescription,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
status: "failed",
|
|
121
|
-
diagnostics,
|
|
122
|
-
semantic: "blocking",
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
export default tsLspRunner;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript Slop runner for dispatch system
|
|
3
|
-
*
|
|
4
|
-
* Detects "slop" patterns in TypeScript/JavaScript code:
|
|
5
|
-
* - Verbose patterns (ceremony that adds no value)
|
|
6
|
-
* - Defensive over-checking (excessive guards)
|
|
7
|
-
* - Manual reimplementation of builtins
|
|
8
|
-
* - Unnecessary object allocations
|
|
9
|
-
*
|
|
10
|
-
* Based on slop-code-bench patterns adapted for TypeScript
|
|
11
|
-
*/
|
|
12
|
-
import { safeSpawn } from "../../safe-spawn.js";
|
|
13
|
-
import { createConfigFinder, isSgAvailable, } from "./utils/runner-helpers.js";
|
|
14
|
-
const findSlopConfig = createConfigFinder("ts-slop-rules");
|
|
15
|
-
const tsSlopRunner = {
|
|
16
|
-
id: "ts-slop",
|
|
17
|
-
// NOTE: TypeScript/JavaScript slop detection is now handled by ast-grep-napi
|
|
18
|
-
// This CLI runner is kept as fallback for edge cases but disabled by default
|
|
19
|
-
appliesTo: [], // Disabled - use ast-grep-napi instead
|
|
20
|
-
priority: 20,
|
|
21
|
-
enabledByDefault: false,
|
|
22
|
-
skipTestFiles: true,
|
|
23
|
-
async run(ctx) {
|
|
24
|
-
// Check if ast-grep is available
|
|
25
|
-
if (!isSgAvailable()) {
|
|
26
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
27
|
-
}
|
|
28
|
-
// Find slop config
|
|
29
|
-
const configPath = findSlopConfig(ctx.cwd);
|
|
30
|
-
if (!configPath) {
|
|
31
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
32
|
-
}
|
|
33
|
-
// Run ast-grep scan
|
|
34
|
-
const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
|
|
35
|
-
const result = safeSpawn("npx", args, {
|
|
36
|
-
timeout: 30000,
|
|
37
|
-
});
|
|
38
|
-
const raw = result.stdout + result.stderr;
|
|
39
|
-
if (result.status === 0 && !raw.trim()) {
|
|
40
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
41
|
-
}
|
|
42
|
-
// Parse results
|
|
43
|
-
const diagnostics = parseSlopOutput(raw, ctx.filePath);
|
|
44
|
-
if (diagnostics.length === 0) {
|
|
45
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
status: "failed",
|
|
49
|
-
diagnostics,
|
|
50
|
-
semantic: "warning",
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
function parseSlopOutput(raw, filePath) {
|
|
55
|
-
const diagnostics = [];
|
|
56
|
-
try {
|
|
57
|
-
const parsed = JSON.parse(raw);
|
|
58
|
-
if (Array.isArray(parsed)) {
|
|
59
|
-
for (const item of parsed) {
|
|
60
|
-
const line = item.range?.start?.line || 1;
|
|
61
|
-
const ruleId = item.rule || "unknown";
|
|
62
|
-
const message = item.message || "";
|
|
63
|
-
// Categorize by severity based on weight from metadata
|
|
64
|
-
const weight = item.metadata?.weight || 3;
|
|
65
|
-
const severity = weight >= 4 ? "error" : "warning";
|
|
66
|
-
const category = item.metadata?.category || "slop";
|
|
67
|
-
// Add slop category indicator to message
|
|
68
|
-
let enhancedMessage = `[${category}] ${message}`;
|
|
69
|
-
if (item.replacement) {
|
|
70
|
-
const preview = item.replacement.length > 40
|
|
71
|
-
? `${item.replacement.substring(0, 40)}...`
|
|
72
|
-
: item.replacement;
|
|
73
|
-
enhancedMessage += `\n💡 Suggested fix: → "${preview}"`;
|
|
74
|
-
}
|
|
75
|
-
diagnostics.push({
|
|
76
|
-
id: `ts-slop-${line}-${ruleId}`,
|
|
77
|
-
message: enhancedMessage,
|
|
78
|
-
filePath,
|
|
79
|
-
line,
|
|
80
|
-
column: item.range?.start?.column || 0,
|
|
81
|
-
severity,
|
|
82
|
-
semantic: severity === "error" ? "blocking" : "warning",
|
|
83
|
-
tool: "ts-slop",
|
|
84
|
-
rule: ruleId,
|
|
85
|
-
fixable: !!item.replacement,
|
|
86
|
-
fixSuggestion: item.replacement,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// JSON parse failed, try line-by-line
|
|
93
|
-
const lines = raw.split("\n");
|
|
94
|
-
for (const line of lines) {
|
|
95
|
-
if (line.includes(":") && line.includes("L")) {
|
|
96
|
-
const match = line.match(/L(\d+):?\s*(.+)/);
|
|
97
|
-
if (match) {
|
|
98
|
-
diagnostics.push({
|
|
99
|
-
id: `ts-slop-${match[1]}-line`,
|
|
100
|
-
message: `[slop] ${match[2].trim()}`,
|
|
101
|
-
filePath,
|
|
102
|
-
line: parseInt(match[1], 10),
|
|
103
|
-
severity: "warning",
|
|
104
|
-
semantic: "warning",
|
|
105
|
-
tool: "ts-slop",
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return diagnostics;
|
|
112
|
-
}
|
|
113
|
-
export default tsSlopRunner;
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type safety runner for dispatch system
|
|
3
|
-
*
|
|
4
|
-
* Checks for:
|
|
5
|
-
* - Switch exhaustiveness
|
|
6
|
-
* - Missing return statements
|
|
7
|
-
* - Type safety issues
|
|
8
|
-
*/
|
|
9
|
-
import { readFileContent } from "./utils.js";
|
|
10
|
-
const typeSafetyRunner = {
|
|
11
|
-
id: "type-safety",
|
|
12
|
-
appliesTo: ["jsts"],
|
|
13
|
-
priority: 20,
|
|
14
|
-
enabledByDefault: true,
|
|
15
|
-
async run(ctx) {
|
|
16
|
-
// Only check TypeScript files
|
|
17
|
-
if (!ctx.filePath.match(/\.tsx?$/)) {
|
|
18
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
19
|
-
}
|
|
20
|
-
const content = readFileContent(ctx.filePath);
|
|
21
|
-
if (!content) {
|
|
22
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
23
|
-
}
|
|
24
|
-
const diagnostics = [];
|
|
25
|
-
// Check for switch exhaustiveness patterns
|
|
26
|
-
diagnostics.push(...checkSwitchExhaustiveness(content, ctx.filePath));
|
|
27
|
-
// Check for missing return patterns
|
|
28
|
-
diagnostics.push(...checkMissingReturns(content, ctx.filePath));
|
|
29
|
-
// Check for any type usage
|
|
30
|
-
diagnostics.push(...checkAnyTypeUsage(content, ctx.filePath));
|
|
31
|
-
if (diagnostics.length === 0) {
|
|
32
|
-
return { status: "succeeded", diagnostics: [], semantic: "none" };
|
|
33
|
-
}
|
|
34
|
-
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
35
|
-
return {
|
|
36
|
-
status: hasErrors ? "failed" : "succeeded",
|
|
37
|
-
diagnostics,
|
|
38
|
-
semantic: hasErrors ? "blocking" : "warning",
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
function checkSwitchExhaustiveness(content, filePath) {
|
|
43
|
-
const diagnostics = [];
|
|
44
|
-
const switchRegex = /switch\s*\(\s*(\w+)\s*\)\s*\{/g;
|
|
45
|
-
let match;
|
|
46
|
-
while ((match = switchRegex.exec(content)) !== null) {
|
|
47
|
-
const switchStart = match.index;
|
|
48
|
-
const switchVar = match[1];
|
|
49
|
-
// Find the switch block
|
|
50
|
-
let braceCount = 0;
|
|
51
|
-
const blockStart = content.indexOf("{", switchStart);
|
|
52
|
-
let blockEnd = blockStart;
|
|
53
|
-
while (blockEnd < content.length && braceCount >= 0) {
|
|
54
|
-
if (content[blockEnd] === "{")
|
|
55
|
-
braceCount++;
|
|
56
|
-
if (content[blockEnd] === "}")
|
|
57
|
-
braceCount--;
|
|
58
|
-
blockEnd++;
|
|
59
|
-
}
|
|
60
|
-
const switchBlock = content.slice(blockStart, blockEnd);
|
|
61
|
-
// Check if it has a default case
|
|
62
|
-
if (!/\bdefault\s*:/.test(switchBlock)) {
|
|
63
|
-
const caseCount = (switchBlock.match(/\bcase\s+/g) || []).length;
|
|
64
|
-
if (caseCount > 2) {
|
|
65
|
-
const lineNum = content.slice(0, switchStart).split("\n").length;
|
|
66
|
-
diagnostics.push({
|
|
67
|
-
id: `switch-${lineNum}-${switchVar}`,
|
|
68
|
-
message: `Switch on '${switchVar}' has ${caseCount} cases but no default`,
|
|
69
|
-
filePath,
|
|
70
|
-
line: lineNum,
|
|
71
|
-
severity: "warning",
|
|
72
|
-
semantic: "warning",
|
|
73
|
-
tool: "type-safety",
|
|
74
|
-
rule: "switch-exhaustiveness",
|
|
75
|
-
fixSuggestion: "Add 'default: break;' or use exhaustive checking",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return diagnostics;
|
|
81
|
-
}
|
|
82
|
-
function checkMissingReturns(content, filePath) {
|
|
83
|
-
const diagnostics = [];
|
|
84
|
-
const funcRegex = /function\s+(\w+)\s*\([^)]*\)\s*:\s*([^\s{]+)\s*\{/g;
|
|
85
|
-
let match;
|
|
86
|
-
while ((match = funcRegex.exec(content)) !== null) {
|
|
87
|
-
const returnType = match[2].trim();
|
|
88
|
-
if (returnType === "void" ||
|
|
89
|
-
returnType === "never" ||
|
|
90
|
-
returnType.includes("Promise<void>")) {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const funcStart = match.index;
|
|
94
|
-
const funcName = match[1];
|
|
95
|
-
// Find function block
|
|
96
|
-
let braceCount = 0;
|
|
97
|
-
const blockStart = content.indexOf("{", funcStart);
|
|
98
|
-
let blockEnd = blockStart;
|
|
99
|
-
while (blockEnd < content.length && braceCount >= 0) {
|
|
100
|
-
if (content[blockEnd] === "{")
|
|
101
|
-
braceCount++;
|
|
102
|
-
if (content[blockEnd] === "}")
|
|
103
|
-
braceCount--;
|
|
104
|
-
blockEnd++;
|
|
105
|
-
}
|
|
106
|
-
const funcBlock = content.slice(blockStart, blockEnd);
|
|
107
|
-
if (!/\breturn\b/.test(funcBlock)) {
|
|
108
|
-
const lineNum = content.slice(0, funcStart).split("\n").length;
|
|
109
|
-
diagnostics.push({
|
|
110
|
-
id: `missing-return-${lineNum}-${funcName}`,
|
|
111
|
-
message: `Function '${funcName}' returns '${returnType}' but has no return statement`,
|
|
112
|
-
filePath,
|
|
113
|
-
line: lineNum,
|
|
114
|
-
severity: "error",
|
|
115
|
-
semantic: "blocking",
|
|
116
|
-
tool: "type-safety",
|
|
117
|
-
rule: "missing-return",
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return diagnostics;
|
|
122
|
-
}
|
|
123
|
-
function checkAnyTypeUsage(content, filePath) {
|
|
124
|
-
const diagnostics = [];
|
|
125
|
-
const anyRegex = /:\s*any\b|as\s+any\b/g;
|
|
126
|
-
let match;
|
|
127
|
-
while ((match = anyRegex.exec(content)) !== null) {
|
|
128
|
-
const lineNum = content.slice(0, match.index).split("\n").length;
|
|
129
|
-
diagnostics.push({
|
|
130
|
-
id: `any-type-${lineNum}`,
|
|
131
|
-
message: "Avoid 'any' type — use 'unknown' or define a proper interface",
|
|
132
|
-
filePath,
|
|
133
|
-
line: lineNum,
|
|
134
|
-
severity: "warning",
|
|
135
|
-
semantic: "warning",
|
|
136
|
-
tool: "type-safety",
|
|
137
|
-
rule: "no-any-type",
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
return diagnostics;
|
|
141
|
-
}
|
|
142
|
-
export default typeSafetyRunner;
|