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,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ast-grep NAPI runner for dispatch system
|
|
3
|
-
*
|
|
4
|
-
* Uses @ast-grep/napi for programmatic parsing instead of CLI.
|
|
5
|
-
* Handles TypeScript/JavaScript/CSS/HTML files with YAML rule support.
|
|
6
|
-
*
|
|
7
|
-
* Replaces CLI-based runners for faster performance (100x speedup).
|
|
8
|
-
*/
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { calculateRuleComplexity, hasUnsupportedConditions, isOverlyBroadPattern, isStructuredRule, loadYamlRules, MAX_BLOCKING_RULE_COMPLEXITY, } from "./yaml-rule-parser.js";
|
|
12
|
-
// Lazy load the napi package
|
|
13
|
-
let sg;
|
|
14
|
-
let sgLoadAttempted = false;
|
|
15
|
-
async function loadSg() {
|
|
16
|
-
if (sg)
|
|
17
|
-
return sg;
|
|
18
|
-
if (sgLoadAttempted)
|
|
19
|
-
return undefined; // Don't retry if already failed
|
|
20
|
-
sgLoadAttempted = true;
|
|
21
|
-
try {
|
|
22
|
-
sg = await import("@ast-grep/napi");
|
|
23
|
-
return sg;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Supported extensions for NAPI
|
|
30
|
-
const SUPPORTED_EXTS = [".ts", ".tsx", ".js", ".jsx", ".css", ".html", ".htm"];
|
|
31
|
-
/** Maximum matches per rule to prevent excessive false positives */
|
|
32
|
-
const MAX_MATCHES_PER_RULE = 10;
|
|
33
|
-
/** Maximum total diagnostics per file to prevent output spam */
|
|
34
|
-
const MAX_TOTAL_DIAGNOSTICS = 50;
|
|
35
|
-
/** Rules already covered by tree-sitter runner (priority 14, runs first) */
|
|
36
|
-
const TREE_SITTER_OVERLAP = new Set([
|
|
37
|
-
"constructor-super",
|
|
38
|
-
"empty-catch",
|
|
39
|
-
"long-parameter-list",
|
|
40
|
-
"nested-ternary",
|
|
41
|
-
"no-dupe-class-members",
|
|
42
|
-
]);
|
|
43
|
-
/** Maximum AST depth to traverse to prevent stack overflow on deeply nested files */
|
|
44
|
-
const MAX_AST_DEPTH = 50;
|
|
45
|
-
/** Maximum recursion depth for structured rule execution */
|
|
46
|
-
const MAX_RULE_DEPTH = 5;
|
|
47
|
-
function canHandle(filePath) {
|
|
48
|
-
return SUPPORTED_EXTS.includes(path.extname(filePath).toLowerCase());
|
|
49
|
-
}
|
|
50
|
-
function getLang(filePath, sgModule) {
|
|
51
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
52
|
-
switch (ext) {
|
|
53
|
-
case ".ts":
|
|
54
|
-
return sgModule.ts;
|
|
55
|
-
case ".tsx":
|
|
56
|
-
return sgModule.tsx;
|
|
57
|
-
case ".js":
|
|
58
|
-
case ".jsx":
|
|
59
|
-
return sgModule.js;
|
|
60
|
-
case ".css":
|
|
61
|
-
return sgModule.css;
|
|
62
|
-
case ".html":
|
|
63
|
-
case ".htm":
|
|
64
|
-
return sgModule.html;
|
|
65
|
-
default:
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Check if a single node matches a condition (without searching descendants).
|
|
71
|
-
* In ast-grep semantics:
|
|
72
|
-
* - pattern/kind/regex: check the node itself
|
|
73
|
-
* - all: node must match ALL sub-conditions
|
|
74
|
-
* - any: node must match at least ONE sub-condition
|
|
75
|
-
* - not: node must NOT match the sub-condition
|
|
76
|
-
* - has: node must have a DESCENDANT matching the sub-condition
|
|
77
|
-
*/
|
|
78
|
-
function nodeMatchesCondition(node, condition, depth = 0) {
|
|
79
|
-
if (depth > MAX_RULE_DEPTH)
|
|
80
|
-
return false;
|
|
81
|
-
// Check kind constraint
|
|
82
|
-
if (condition.kind && node.kind() !== condition.kind)
|
|
83
|
-
return false;
|
|
84
|
-
// Check pattern constraint (node itself must match)
|
|
85
|
-
if (condition.pattern) {
|
|
86
|
-
try {
|
|
87
|
-
const matches = node.findAll(condition.pattern);
|
|
88
|
-
// Check if the node itself is among the matches (same start position)
|
|
89
|
-
const nodeRange = node.range();
|
|
90
|
-
let selfMatch = false;
|
|
91
|
-
for (const m of matches) {
|
|
92
|
-
const mr = m.range();
|
|
93
|
-
if (mr.start.line === nodeRange.start.line &&
|
|
94
|
-
mr.start.column === nodeRange.start.column &&
|
|
95
|
-
mr.end.line === nodeRange.end.line &&
|
|
96
|
-
mr.end.column === nodeRange.end.column) {
|
|
97
|
-
selfMatch = true;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (!selfMatch)
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Check regex constraint
|
|
109
|
-
if (condition.regex) {
|
|
110
|
-
try {
|
|
111
|
-
const text = node.text();
|
|
112
|
-
if (!new RegExp(condition.regex).test(text))
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Check has (descendant must match)
|
|
120
|
-
if (condition.has) {
|
|
121
|
-
const descendants = findMatchingNodes(node, condition.has, depth + 1);
|
|
122
|
-
if (descendants.length === 0)
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
// Check not (node must NOT match this condition)
|
|
126
|
-
if (condition.not) {
|
|
127
|
-
if (nodeMatchesCondition(node, condition.not, depth + 1))
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
// Check all (node must match ALL sub-conditions)
|
|
131
|
-
if (condition.all) {
|
|
132
|
-
for (const sub of condition.all) {
|
|
133
|
-
if (!nodeMatchesCondition(node, sub, depth + 1))
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// Check any (node must match at least one sub-condition)
|
|
138
|
-
if (condition.any) {
|
|
139
|
-
let anyMatch = false;
|
|
140
|
-
for (const sub of condition.any) {
|
|
141
|
-
if (nodeMatchesCondition(node, sub, depth + 1)) {
|
|
142
|
-
anyMatch = true;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (!anyMatch)
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Find all nodes in the tree that match a condition.
|
|
153
|
-
* This is the "search" function - traverses the tree and checks each node.
|
|
154
|
-
*/
|
|
155
|
-
function findMatchingNodes(rootNode, condition, depth = 0) {
|
|
156
|
-
if (depth > MAX_RULE_DEPTH)
|
|
157
|
-
return [];
|
|
158
|
-
const matches = [];
|
|
159
|
-
// Optimization: if the condition has a kind, only check nodes of that kind
|
|
160
|
-
// If it has a pattern, use findAll for initial candidates
|
|
161
|
-
let candidates;
|
|
162
|
-
if (condition.pattern && !condition.all && !condition.any) {
|
|
163
|
-
// Use findAll for pattern-only conditions (fast path)
|
|
164
|
-
try {
|
|
165
|
-
candidates = rootNode.findAll(condition.pattern);
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else if (condition.kind && !condition.all && !condition.any) {
|
|
172
|
-
// Use findByKind for kind-only conditions (fast path)
|
|
173
|
-
candidates = findByKind(rootNode, condition.kind, 0);
|
|
174
|
-
}
|
|
175
|
-
else if (condition.all) {
|
|
176
|
-
// For `all`, find the narrowest sub-condition to generate candidates
|
|
177
|
-
candidates = getCandidatesForAll(rootNode, condition.all);
|
|
178
|
-
}
|
|
179
|
-
else if (condition.any) {
|
|
180
|
-
// For `any`, union candidates from all sub-conditions
|
|
181
|
-
const seen = new Set();
|
|
182
|
-
candidates = [];
|
|
183
|
-
for (const sub of condition.any) {
|
|
184
|
-
const subMatches = findMatchingNodes(rootNode, sub, depth + 1);
|
|
185
|
-
for (const m of subMatches) {
|
|
186
|
-
const r = m.range();
|
|
187
|
-
const key = `${r.start.line}:${r.start.column}`;
|
|
188
|
-
if (!seen.has(key)) {
|
|
189
|
-
seen.add(key);
|
|
190
|
-
candidates.push(m);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Fallback: traverse all nodes
|
|
197
|
-
candidates = getAllNodes(rootNode, 0);
|
|
198
|
-
}
|
|
199
|
-
for (const candidate of candidates) {
|
|
200
|
-
if (nodeMatchesCondition(candidate, condition, depth)) {
|
|
201
|
-
matches.push(candidate);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return matches;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* For an `all` condition, find the narrowest sub-condition to generate
|
|
208
|
-
* initial candidates. This avoids scanning all nodes when one sub-condition
|
|
209
|
-
* has a specific kind or pattern.
|
|
210
|
-
*/
|
|
211
|
-
function getCandidatesForAll(rootNode, subs) {
|
|
212
|
-
// Prefer kind-based narrowing first, then pattern-based
|
|
213
|
-
for (const sub of subs) {
|
|
214
|
-
if (sub.kind) {
|
|
215
|
-
return findByKind(rootNode, sub.kind, 0);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
for (const sub of subs) {
|
|
219
|
-
if (sub.pattern) {
|
|
220
|
-
try {
|
|
221
|
-
return rootNode.findAll(sub.pattern);
|
|
222
|
-
}
|
|
223
|
-
catch { }
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// No narrowing possible, scan all
|
|
227
|
-
return getAllNodes(rootNode, 0);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Legacy wrapper - execute a structured rule using the new two-phase approach.
|
|
231
|
-
*/
|
|
232
|
-
function executeStructuredRule(rootNode, condition, matches = [], depth = 0) {
|
|
233
|
-
return findMatchingNodes(rootNode, condition, depth);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Find all nodes of a specific kind with depth limit
|
|
237
|
-
*/
|
|
238
|
-
function findByKind(node, kind, currentDepth) {
|
|
239
|
-
if (currentDepth > MAX_AST_DEPTH)
|
|
240
|
-
return [];
|
|
241
|
-
const results = [];
|
|
242
|
-
if (node.kind() === kind)
|
|
243
|
-
results.push(node);
|
|
244
|
-
for (const child of node.children()) {
|
|
245
|
-
results.push(...findByKind(child, kind, currentDepth + 1));
|
|
246
|
-
}
|
|
247
|
-
return results;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Get all nodes with depth limit to prevent stack overflow
|
|
251
|
-
*/
|
|
252
|
-
function getAllNodes(node, currentDepth) {
|
|
253
|
-
if (currentDepth > MAX_AST_DEPTH)
|
|
254
|
-
return [];
|
|
255
|
-
const results = [node];
|
|
256
|
-
for (const child of node.children()) {
|
|
257
|
-
results.push(...getAllNodes(child, currentDepth + 1));
|
|
258
|
-
}
|
|
259
|
-
return results;
|
|
260
|
-
}
|
|
261
|
-
// --- Runner Definition ---
|
|
262
|
-
const astGrepNapiRunner = {
|
|
263
|
-
id: "ast-grep-napi",
|
|
264
|
-
appliesTo: ["jsts"],
|
|
265
|
-
priority: 15,
|
|
266
|
-
enabledByDefault: true,
|
|
267
|
-
skipTestFiles: true,
|
|
268
|
-
async run(ctx) {
|
|
269
|
-
if (!canHandle(ctx.filePath)) {
|
|
270
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
271
|
-
}
|
|
272
|
-
const sgModule = await loadSg();
|
|
273
|
-
if (!sgModule) {
|
|
274
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
275
|
-
}
|
|
276
|
-
if (!fs.existsSync(ctx.filePath)) {
|
|
277
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
278
|
-
}
|
|
279
|
-
const lang = getLang(ctx.filePath, sgModule);
|
|
280
|
-
if (!lang) {
|
|
281
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
282
|
-
}
|
|
283
|
-
const stats = fs.statSync(ctx.filePath);
|
|
284
|
-
if (stats.size > 1024 * 1024) {
|
|
285
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
286
|
-
}
|
|
287
|
-
let content;
|
|
288
|
-
try {
|
|
289
|
-
content = fs.readFileSync(ctx.filePath, "utf-8");
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
293
|
-
}
|
|
294
|
-
let root;
|
|
295
|
-
try {
|
|
296
|
-
root = lang.parse(content);
|
|
297
|
-
}
|
|
298
|
-
catch {
|
|
299
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
300
|
-
}
|
|
301
|
-
let rootNode;
|
|
302
|
-
try {
|
|
303
|
-
rootNode = root.root();
|
|
304
|
-
}
|
|
305
|
-
catch {
|
|
306
|
-
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
307
|
-
}
|
|
308
|
-
const diagnostics = [];
|
|
309
|
-
const ruleDirs = [
|
|
310
|
-
path.join(process.cwd(), "rules/ast-grep-rules/rules"),
|
|
311
|
-
path.join(process.cwd(), "rules/ast-grep-rules"),
|
|
312
|
-
];
|
|
313
|
-
for (const ruleDir of ruleDirs) {
|
|
314
|
-
let rules;
|
|
315
|
-
try {
|
|
316
|
-
rules = loadYamlRules(ruleDir, ctx.blockingOnly ? "error" : undefined);
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
for (const rule of rules) {
|
|
322
|
-
// Skip rules already handled by tree-sitter runner (priority 14)
|
|
323
|
-
if (TREE_SITTER_OVERLAP.has(rule.id))
|
|
324
|
-
continue;
|
|
325
|
-
// Skip rules using conditions we can't execute (inside, follows,
|
|
326
|
-
// precedes, stopBy, field, nthChild, constraints). Running these
|
|
327
|
-
// with only partial condition evaluation causes false positives.
|
|
328
|
-
if (hasUnsupportedConditions(rule))
|
|
329
|
-
continue;
|
|
330
|
-
// Skip rules whose top-level pattern is overly broad ($NAME, $X, etc.)
|
|
331
|
-
// without additional structural constraints to narrow matches.
|
|
332
|
-
if (rule.rule &&
|
|
333
|
-
isOverlyBroadPattern(rule.rule.pattern) &&
|
|
334
|
-
!isStructuredRule(rule)) {
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
const lang = rule.language?.toLowerCase();
|
|
338
|
-
if (lang && lang !== "typescript" && lang !== "javascript") {
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
if (ctx.blockingOnly && rule.rule) {
|
|
342
|
-
const complexity = calculateRuleComplexity(rule.rule);
|
|
343
|
-
if (complexity > MAX_BLOCKING_RULE_COMPLEXITY) {
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
try {
|
|
348
|
-
let matches = [];
|
|
349
|
-
if (isStructuredRule(rule) && rule.rule) {
|
|
350
|
-
matches = executeStructuredRule(rootNode, rule.rule, []);
|
|
351
|
-
}
|
|
352
|
-
else if (rule.rule?.pattern || rule.rule?.kind) {
|
|
353
|
-
const pattern = rule.rule.pattern || rule.rule.kind;
|
|
354
|
-
if (pattern) {
|
|
355
|
-
try {
|
|
356
|
-
matches = rootNode.findAll(pattern);
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
if (rule.rule.kind) {
|
|
360
|
-
matches = findByKind(rootNode, rule.rule.kind, 0);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const limitedMatches = matches.slice(0, MAX_MATCHES_PER_RULE);
|
|
366
|
-
for (const match of limitedMatches) {
|
|
367
|
-
if (diagnostics.length >= MAX_TOTAL_DIAGNOSTICS)
|
|
368
|
-
break;
|
|
369
|
-
const node = match;
|
|
370
|
-
const range = node.range();
|
|
371
|
-
const severity = rule.severity === "error" ? "error" : "warning";
|
|
372
|
-
diagnostics.push({
|
|
373
|
-
id: `ast-grep-napi-${range.start.line}-${rule.id}`,
|
|
374
|
-
message: `[${rule.metadata?.category || "slop"}] ${rule.message || rule.id}`,
|
|
375
|
-
filePath: ctx.filePath,
|
|
376
|
-
line: range.start.line + 1,
|
|
377
|
-
column: range.start.column + 1,
|
|
378
|
-
severity,
|
|
379
|
-
semantic: severity === "error" ? "blocking" : "warning",
|
|
380
|
-
tool: "ast-grep-napi",
|
|
381
|
-
rule: rule.id,
|
|
382
|
-
fixable: false,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
if (diagnostics.length >= MAX_TOTAL_DIAGNOSTICS)
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
catch {
|
|
389
|
-
// Rule failed, skip
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const hasBlocking = diagnostics.some((d) => d.semantic === "blocking");
|
|
394
|
-
return {
|
|
395
|
-
status: "succeeded",
|
|
396
|
-
diagnostics,
|
|
397
|
-
semantic: hasBlocking
|
|
398
|
-
? "blocking"
|
|
399
|
-
: diagnostics.length > 0
|
|
400
|
-
? "warning"
|
|
401
|
-
: "none",
|
|
402
|
-
};
|
|
403
|
-
},
|
|
404
|
-
};
|
|
405
|
-
export default astGrepNapiRunner;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
function createMockContext(filePath, kind = "jsts") {
|
|
5
|
-
return {
|
|
6
|
-
filePath,
|
|
7
|
-
cwd: process.cwd(),
|
|
8
|
-
kind,
|
|
9
|
-
autofix: false,
|
|
10
|
-
deltaMode: false,
|
|
11
|
-
baselines: { get: () => [], add: () => { }, save: () => { } },
|
|
12
|
-
pi: {},
|
|
13
|
-
hasTool: async () => false,
|
|
14
|
-
log: () => { },
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
describe("ast-grep-napi vs CLI comparison", () => {
|
|
18
|
-
it("should load the napi module", async () => {
|
|
19
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
20
|
-
expect(napiModule.default.id).toBe("ast-grep-napi");
|
|
21
|
-
expect(napiModule.default.appliesTo).toEqual(["jsts"]);
|
|
22
|
-
});
|
|
23
|
-
it("should scan TypeScript file and return succeeded status", async () => {
|
|
24
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `napi_test_${Date.now()}.ts`);
|
|
25
|
-
fs.writeFileSync(tmpFile, `// Test file with various patterns
|
|
26
|
-
function test(items: string[]) {
|
|
27
|
-
for (let i = 0; i < items.length; i++) {
|
|
28
|
-
console.log(items[i]);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
riskyOperation();
|
|
33
|
-
} catch (e) {
|
|
34
|
-
// empty catch
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return await fetchData();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function fetchData() {
|
|
41
|
-
return await Promise.resolve(42);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function riskyOperation() {
|
|
45
|
-
debugger;
|
|
46
|
-
}
|
|
47
|
-
`);
|
|
48
|
-
try {
|
|
49
|
-
// Test NAPI version
|
|
50
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
51
|
-
const napiRunner = napiModule.default;
|
|
52
|
-
console.time("napi");
|
|
53
|
-
let napiResult;
|
|
54
|
-
try {
|
|
55
|
-
napiResult = await napiRunner.run(createMockContext(tmpFile));
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
console.error("NAPI runner threw error:", error);
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
console.timeEnd("napi");
|
|
62
|
-
console.log("NAPI result status:", napiResult.status);
|
|
63
|
-
console.log("NAPI result semantic:", napiResult.semantic);
|
|
64
|
-
console.log("NAPI result diagnostics count:", napiResult.diagnostics?.length);
|
|
65
|
-
// Should complete successfully (not skipped, not failed)
|
|
66
|
-
expect(napiResult.status).toBe("succeeded");
|
|
67
|
-
// Semantic reflects the highest severity: "blocking" if any error-severity rules matched
|
|
68
|
-
expect(["warning", "blocking"]).toContain(napiResult.semantic);
|
|
69
|
-
// Log findings
|
|
70
|
-
console.log("NAPI found:", napiResult.diagnostics.length, "issues");
|
|
71
|
-
console.log("\n=== NAPI FINDINGS ===");
|
|
72
|
-
napiResult.diagnostics.forEach((d, i) => {
|
|
73
|
-
console.log(`${i + 1}. Line ${d.line}: ${d.rule}`);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
try {
|
|
78
|
-
if (fs.existsSync(tmpFile)) {
|
|
79
|
-
fs.unlinkSync(tmpFile);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// Ignore cleanup errors
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
it("should skip non-TS/JS files", async () => {
|
|
88
|
-
const tmpFile = path.join(process.env.TEMP || "/tmp", `napi_test_py_${Date.now()}.py`);
|
|
89
|
-
fs.writeFileSync(tmpFile, "# Python file\nprint('hello')");
|
|
90
|
-
try {
|
|
91
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
92
|
-
const napiRunner = napiModule.default;
|
|
93
|
-
const result = await napiRunner.run(createMockContext(tmpFile, "python"));
|
|
94
|
-
expect(result.status).toBe("skipped");
|
|
95
|
-
}
|
|
96
|
-
finally {
|
|
97
|
-
try {
|
|
98
|
-
if (fs.existsSync(tmpFile)) {
|
|
99
|
-
fs.unlinkSync(tmpFile);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// Ignore cleanup errors
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
import type { DispatchContext } from "../types.js";
|
|
5
|
-
|
|
6
|
-
function createMockContext(
|
|
7
|
-
filePath: string,
|
|
8
|
-
kind: any = "jsts",
|
|
9
|
-
): DispatchContext {
|
|
10
|
-
return {
|
|
11
|
-
filePath,
|
|
12
|
-
cwd: process.cwd(),
|
|
13
|
-
kind,
|
|
14
|
-
autofix: false,
|
|
15
|
-
deltaMode: false,
|
|
16
|
-
baselines: { get: () => [], add: () => {}, save: () => {} } as any,
|
|
17
|
-
pi: {} as any,
|
|
18
|
-
hasTool: async () => false,
|
|
19
|
-
log: () => {},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("ast-grep-napi vs CLI comparison", () => {
|
|
24
|
-
it("should load the napi module", async () => {
|
|
25
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
26
|
-
expect(napiModule.default.id).toBe("ast-grep-napi");
|
|
27
|
-
expect(napiModule.default.appliesTo).toEqual(["jsts"]);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("should scan TypeScript file and return succeeded status", async () => {
|
|
31
|
-
const tmpFile = path.join(
|
|
32
|
-
process.env.TEMP || "/tmp",
|
|
33
|
-
`napi_test_${Date.now()}.ts`,
|
|
34
|
-
);
|
|
35
|
-
fs.writeFileSync(
|
|
36
|
-
tmpFile,
|
|
37
|
-
`// Test file with various patterns
|
|
38
|
-
function test(items: string[]) {
|
|
39
|
-
for (let i = 0; i < items.length; i++) {
|
|
40
|
-
console.log(items[i]);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
riskyOperation();
|
|
45
|
-
} catch (e) {
|
|
46
|
-
// empty catch
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return await fetchData();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function fetchData() {
|
|
53
|
-
return await Promise.resolve(42);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function riskyOperation() {
|
|
57
|
-
debugger;
|
|
58
|
-
}
|
|
59
|
-
`,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// Test NAPI version
|
|
64
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
65
|
-
const napiRunner = napiModule.default;
|
|
66
|
-
|
|
67
|
-
console.time("napi");
|
|
68
|
-
let napiResult;
|
|
69
|
-
try {
|
|
70
|
-
napiResult = await napiRunner.run(createMockContext(tmpFile));
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error("NAPI runner threw error:", error);
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
console.timeEnd("napi");
|
|
76
|
-
|
|
77
|
-
console.log("NAPI result status:", napiResult.status);
|
|
78
|
-
console.log("NAPI result semantic:", napiResult.semantic);
|
|
79
|
-
console.log(
|
|
80
|
-
"NAPI result diagnostics count:",
|
|
81
|
-
napiResult.diagnostics?.length,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Should complete successfully (not skipped, not failed)
|
|
85
|
-
expect(napiResult.status).toBe("succeeded");
|
|
86
|
-
// Semantic reflects the highest severity: "blocking" if any error-severity rules matched
|
|
87
|
-
expect(["warning", "blocking"]).toContain(napiResult.semantic);
|
|
88
|
-
|
|
89
|
-
// Log findings
|
|
90
|
-
console.log("NAPI found:", napiResult.diagnostics.length, "issues");
|
|
91
|
-
console.log("\n=== NAPI FINDINGS ===");
|
|
92
|
-
napiResult.diagnostics.forEach((d, i) => {
|
|
93
|
-
console.log(`${i + 1}. Line ${d.line}: ${d.rule}`);
|
|
94
|
-
});
|
|
95
|
-
} finally {
|
|
96
|
-
try {
|
|
97
|
-
if (fs.existsSync(tmpFile)) {
|
|
98
|
-
fs.unlinkSync(tmpFile);
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
// Ignore cleanup errors
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should skip non-TS/JS files", async () => {
|
|
107
|
-
const tmpFile = path.join(
|
|
108
|
-
process.env.TEMP || "/tmp",
|
|
109
|
-
`napi_test_py_${Date.now()}.py`,
|
|
110
|
-
);
|
|
111
|
-
fs.writeFileSync(tmpFile, "# Python file\nprint('hello')");
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const napiModule = await import("./ast-grep-napi.js");
|
|
115
|
-
const napiRunner = napiModule.default;
|
|
116
|
-
|
|
117
|
-
const result = await napiRunner.run(createMockContext(tmpFile, "python"));
|
|
118
|
-
expect(result.status).toBe("skipped");
|
|
119
|
-
} finally {
|
|
120
|
-
try {
|
|
121
|
-
if (fs.existsSync(tmpFile)) {
|
|
122
|
-
fs.unlinkSync(tmpFile);
|
|
123
|
-
}
|
|
124
|
-
} catch {
|
|
125
|
-
// Ignore cleanup errors
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|