@reverse-craft/smart-fs 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -13
- package/dist/analyzer.d.ts +91 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/beautifier.d.ts +120 -0
- package/dist/beautifier.d.ts.map +1 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1267 -0
- package/dist/index.js.map +7 -0
- package/dist/languageDetector.d.ts +74 -0
- package/dist/languageDetector.d.ts.map +1 -0
- package/dist/llmConfig.d.ts +34 -0
- package/dist/llmConfig.d.ts.map +1 -0
- package/dist/searcher.d.ts +113 -0
- package/dist/searcher.d.ts.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +571 -78
- package/dist/server.js.map +4 -4
- package/dist/tools/ToolDefinition.d.ts +24 -0
- package/dist/tools/ToolDefinition.d.ts.map +1 -0
- package/dist/tools/aiFindJsvmpDispatcher.d.ts +79 -0
- package/dist/tools/aiFindJsvmpDispatcher.d.ts.map +1 -0
- package/dist/tools/applyCustomTransform.d.ts +14 -0
- package/dist/tools/applyCustomTransform.d.ts.map +1 -0
- package/dist/tools/findUsageSmart.d.ts +16 -0
- package/dist/tools/findUsageSmart.d.ts.map +1 -0
- package/dist/tools/index.d.ts +43 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/readCodeSmart.d.ts +13 -0
- package/dist/tools/readCodeSmart.d.ts.map +1 -0
- package/dist/tools/searchCodeSmart.d.ts +18 -0
- package/dist/tools/searchCodeSmart.d.ts.map +1 -0
- package/dist/transformer.d.ts +119 -0
- package/dist/transformer.d.ts.map +1 -0
- package/dist/truncator.d.ts +69 -0
- package/dist/truncator.d.ts.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +16 -16
package/dist/index.js
ADDED
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import * as fs3 from "fs/promises";
|
|
3
|
+
import * as path4 from "path";
|
|
4
|
+
|
|
5
|
+
// src/languageDetector.ts
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
var LANGUAGE_CONFIG = {
|
|
8
|
+
javascript: {
|
|
9
|
+
language: "javascript",
|
|
10
|
+
supportsAST: true,
|
|
11
|
+
supportsBeautify: true,
|
|
12
|
+
supportsSourceMap: true
|
|
13
|
+
},
|
|
14
|
+
typescript: {
|
|
15
|
+
language: "typescript",
|
|
16
|
+
supportsAST: true,
|
|
17
|
+
supportsBeautify: true,
|
|
18
|
+
supportsSourceMap: true
|
|
19
|
+
},
|
|
20
|
+
json: {
|
|
21
|
+
language: "json",
|
|
22
|
+
supportsAST: false,
|
|
23
|
+
supportsBeautify: true,
|
|
24
|
+
supportsSourceMap: false
|
|
25
|
+
},
|
|
26
|
+
html: {
|
|
27
|
+
language: "html",
|
|
28
|
+
supportsAST: false,
|
|
29
|
+
supportsBeautify: true,
|
|
30
|
+
supportsSourceMap: false
|
|
31
|
+
},
|
|
32
|
+
xml: {
|
|
33
|
+
language: "xml",
|
|
34
|
+
supportsAST: false,
|
|
35
|
+
supportsBeautify: true,
|
|
36
|
+
supportsSourceMap: false
|
|
37
|
+
},
|
|
38
|
+
css: {
|
|
39
|
+
language: "css",
|
|
40
|
+
supportsAST: false,
|
|
41
|
+
supportsBeautify: true,
|
|
42
|
+
supportsSourceMap: false
|
|
43
|
+
},
|
|
44
|
+
unknown: {
|
|
45
|
+
language: "unknown",
|
|
46
|
+
supportsAST: false,
|
|
47
|
+
supportsBeautify: false,
|
|
48
|
+
supportsSourceMap: false
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var EXTENSION_MAP = {
|
|
52
|
+
".js": "javascript",
|
|
53
|
+
".mjs": "javascript",
|
|
54
|
+
".cjs": "javascript",
|
|
55
|
+
".jsx": "javascript",
|
|
56
|
+
".ts": "typescript",
|
|
57
|
+
".tsx": "typescript",
|
|
58
|
+
".mts": "typescript",
|
|
59
|
+
".cts": "typescript",
|
|
60
|
+
".json": "json",
|
|
61
|
+
".html": "html",
|
|
62
|
+
".htm": "html",
|
|
63
|
+
".xml": "xml",
|
|
64
|
+
".svg": "xml",
|
|
65
|
+
".css": "css"
|
|
66
|
+
};
|
|
67
|
+
function detectLanguage(filePath) {
|
|
68
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
69
|
+
const language = EXTENSION_MAP[ext] ?? "unknown";
|
|
70
|
+
return LANGUAGE_CONFIG[language];
|
|
71
|
+
}
|
|
72
|
+
function getLanguageInfo(language) {
|
|
73
|
+
return LANGUAGE_CONFIG[language];
|
|
74
|
+
}
|
|
75
|
+
function isFullySupportedLanguage(language) {
|
|
76
|
+
const info = LANGUAGE_CONFIG[language];
|
|
77
|
+
return info.supportsAST && info.supportsBeautify && info.supportsSourceMap;
|
|
78
|
+
}
|
|
79
|
+
function getSupportedExtensions() {
|
|
80
|
+
return Object.keys(EXTENSION_MAP);
|
|
81
|
+
}
|
|
82
|
+
function isExtensionSupported(ext) {
|
|
83
|
+
const normalizedExt = ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`;
|
|
84
|
+
return normalizedExt in EXTENSION_MAP;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/beautifier.ts
|
|
88
|
+
import * as esbuild from "esbuild";
|
|
89
|
+
import * as crypto from "crypto";
|
|
90
|
+
import * as fs from "fs/promises";
|
|
91
|
+
import * as path2 from "path";
|
|
92
|
+
import * as os from "os";
|
|
93
|
+
var TEMP_DIR = path2.join(os.tmpdir(), "smart-fs-mcp-cache");
|
|
94
|
+
async function ensureCacheDir() {
|
|
95
|
+
await fs.mkdir(TEMP_DIR, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
function calculateCacheKey(originalPath, mtimeMs) {
|
|
98
|
+
const fileKey = `${originalPath}-${mtimeMs}`;
|
|
99
|
+
return crypto.createHash("md5").update(fileKey).digest("hex");
|
|
100
|
+
}
|
|
101
|
+
function getCachePaths(originalPath, hash) {
|
|
102
|
+
const fileName = path2.basename(originalPath, ".js");
|
|
103
|
+
const beautifiedPath = path2.join(TEMP_DIR, `${fileName}.${hash}.beautified.js`);
|
|
104
|
+
const mapPath = `${beautifiedPath}.map`;
|
|
105
|
+
return { beautifiedPath, mapPath };
|
|
106
|
+
}
|
|
107
|
+
function getLocalPaths(originalPath) {
|
|
108
|
+
const absolutePath = path2.resolve(originalPath);
|
|
109
|
+
const dir = path2.dirname(absolutePath);
|
|
110
|
+
const ext = path2.extname(absolutePath);
|
|
111
|
+
const baseName = path2.basename(absolutePath, ext);
|
|
112
|
+
const beautifiedPath = path2.join(dir, `${baseName}.beautified.js`);
|
|
113
|
+
const mapPath = `${beautifiedPath}.map`;
|
|
114
|
+
return { beautifiedPath, mapPath };
|
|
115
|
+
}
|
|
116
|
+
async function isCacheValid(beautifiedPath, mapPath) {
|
|
117
|
+
try {
|
|
118
|
+
await Promise.all([
|
|
119
|
+
fs.access(beautifiedPath),
|
|
120
|
+
fs.access(mapPath)
|
|
121
|
+
]);
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function isLocalCacheValid(originalPath) {
|
|
128
|
+
const absolutePath = path2.resolve(originalPath);
|
|
129
|
+
const { beautifiedPath } = getLocalPaths(absolutePath);
|
|
130
|
+
let originalStats;
|
|
131
|
+
try {
|
|
132
|
+
originalStats = await fs.stat(absolutePath);
|
|
133
|
+
} catch {
|
|
134
|
+
return {
|
|
135
|
+
originalMtime: 0,
|
|
136
|
+
beautifiedExists: false,
|
|
137
|
+
beautifiedMtime: 0,
|
|
138
|
+
isValid: false
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const originalMtime = originalStats.mtimeMs;
|
|
142
|
+
let beautifiedStats;
|
|
143
|
+
try {
|
|
144
|
+
beautifiedStats = await fs.stat(beautifiedPath);
|
|
145
|
+
} catch {
|
|
146
|
+
return {
|
|
147
|
+
originalMtime,
|
|
148
|
+
beautifiedExists: false,
|
|
149
|
+
beautifiedMtime: 0,
|
|
150
|
+
isValid: false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const beautifiedMtime = beautifiedStats.mtimeMs;
|
|
154
|
+
const isValid = beautifiedMtime >= originalMtime;
|
|
155
|
+
return {
|
|
156
|
+
originalMtime,
|
|
157
|
+
beautifiedExists: true,
|
|
158
|
+
beautifiedMtime,
|
|
159
|
+
isValid
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function beautifyJson(content) {
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(content);
|
|
165
|
+
return { code: JSON.stringify(parsed, null, 2), parseFailed: false };
|
|
166
|
+
} catch {
|
|
167
|
+
return { code: content, parseFailed: true };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function beautifyHtml(content) {
|
|
171
|
+
try {
|
|
172
|
+
let formatted = "";
|
|
173
|
+
let indent = 0;
|
|
174
|
+
const indentStr = " ";
|
|
175
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
176
|
+
const tokens = normalized.split(/(<[^>]+>)/g).filter((token) => token.trim() !== "");
|
|
177
|
+
for (const token of tokens) {
|
|
178
|
+
const trimmedToken = token.trim();
|
|
179
|
+
if (!trimmedToken) continue;
|
|
180
|
+
if (trimmedToken.startsWith("<")) {
|
|
181
|
+
if (trimmedToken.startsWith("<!") || trimmedToken.startsWith("<?") || trimmedToken.endsWith("/>")) {
|
|
182
|
+
formatted += indentStr.repeat(indent) + trimmedToken + "\n";
|
|
183
|
+
} else if (trimmedToken.startsWith("</")) {
|
|
184
|
+
indent = Math.max(0, indent - 1);
|
|
185
|
+
formatted += indentStr.repeat(indent) + trimmedToken + "\n";
|
|
186
|
+
} else {
|
|
187
|
+
formatted += indentStr.repeat(indent) + trimmedToken + "\n";
|
|
188
|
+
indent++;
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
const textLines = trimmedToken.split("\n");
|
|
192
|
+
for (const line of textLines) {
|
|
193
|
+
const trimmedLine = line.trim();
|
|
194
|
+
if (trimmedLine) {
|
|
195
|
+
formatted += indentStr.repeat(indent) + trimmedLine + "\n";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return formatted.trimEnd();
|
|
201
|
+
} catch {
|
|
202
|
+
return content;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function beautifyCss(content) {
|
|
206
|
+
try {
|
|
207
|
+
let formatted = content;
|
|
208
|
+
formatted = formatted.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
209
|
+
formatted = formatted.replace(/\{/g, " {\n");
|
|
210
|
+
formatted = formatted.replace(/\}/g, "\n}\n");
|
|
211
|
+
formatted = formatted.replace(/;/g, ";\n");
|
|
212
|
+
formatted = formatted.replace(/\n\s*\n/g, "\n");
|
|
213
|
+
const lines = formatted.split("\n");
|
|
214
|
+
let indent = 0;
|
|
215
|
+
const indentStr = " ";
|
|
216
|
+
const result = [];
|
|
217
|
+
for (const line of lines) {
|
|
218
|
+
const trimmed = line.trim();
|
|
219
|
+
if (!trimmed) continue;
|
|
220
|
+
if (trimmed.startsWith("}")) {
|
|
221
|
+
indent = Math.max(0, indent - 1);
|
|
222
|
+
}
|
|
223
|
+
result.push(indentStr.repeat(indent) + trimmed);
|
|
224
|
+
if (trimmed.endsWith("{")) {
|
|
225
|
+
indent++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result.join("\n");
|
|
229
|
+
} catch {
|
|
230
|
+
return content;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function beautifyCode(code, language) {
|
|
234
|
+
const langInfo = getLanguageInfo(language);
|
|
235
|
+
if (!langInfo.supportsBeautify) {
|
|
236
|
+
return { code, usedFallback: true };
|
|
237
|
+
}
|
|
238
|
+
switch (language) {
|
|
239
|
+
case "json": {
|
|
240
|
+
const result = beautifyJson(code);
|
|
241
|
+
return { code: result.code, usedFallback: result.parseFailed };
|
|
242
|
+
}
|
|
243
|
+
case "html":
|
|
244
|
+
case "xml":
|
|
245
|
+
return { code: beautifyHtml(code), usedFallback: false };
|
|
246
|
+
case "css":
|
|
247
|
+
return { code: beautifyCss(code), usedFallback: false };
|
|
248
|
+
case "javascript":
|
|
249
|
+
case "typescript":
|
|
250
|
+
return { code, usedFallback: true };
|
|
251
|
+
default:
|
|
252
|
+
return { code, usedFallback: true };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function ensureBeautified(originalPath, options) {
|
|
256
|
+
const absolutePath = path2.resolve(originalPath);
|
|
257
|
+
let stats;
|
|
258
|
+
try {
|
|
259
|
+
stats = await fs.stat(absolutePath);
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error(`File not found: ${originalPath}`);
|
|
262
|
+
}
|
|
263
|
+
const langInfo = options?.language ? getLanguageInfo(options.language) : detectLanguage(absolutePath);
|
|
264
|
+
const language = langInfo.language;
|
|
265
|
+
const localPaths = getLocalPaths(absolutePath);
|
|
266
|
+
if (language !== "javascript" && language !== "typescript") {
|
|
267
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
268
|
+
const beautified = beautifyCode(content, language);
|
|
269
|
+
return {
|
|
270
|
+
code: beautified.code,
|
|
271
|
+
rawMap: null,
|
|
272
|
+
// No source map for non-JS/TS
|
|
273
|
+
localPath: localPaths.beautifiedPath,
|
|
274
|
+
localMapPath: localPaths.mapPath,
|
|
275
|
+
usedFallback: beautified.usedFallback
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const localCacheCheck = await isLocalCacheValid(absolutePath);
|
|
279
|
+
if (localCacheCheck.isValid) {
|
|
280
|
+
try {
|
|
281
|
+
const [code2, mapContent] = await Promise.all([
|
|
282
|
+
fs.readFile(localPaths.beautifiedPath, "utf-8"),
|
|
283
|
+
fs.readFile(localPaths.mapPath, "utf-8")
|
|
284
|
+
]);
|
|
285
|
+
return {
|
|
286
|
+
code: code2,
|
|
287
|
+
rawMap: JSON.parse(mapContent),
|
|
288
|
+
localPath: localPaths.beautifiedPath,
|
|
289
|
+
localMapPath: localPaths.mapPath,
|
|
290
|
+
usedFallback: false
|
|
291
|
+
};
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
await ensureCacheDir();
|
|
296
|
+
const hash = calculateCacheKey(absolutePath, stats.mtimeMs);
|
|
297
|
+
const { beautifiedPath, mapPath } = getCachePaths(absolutePath, hash);
|
|
298
|
+
if (await isCacheValid(beautifiedPath, mapPath)) {
|
|
299
|
+
const [code2, mapContent] = await Promise.all([
|
|
300
|
+
fs.readFile(beautifiedPath, "utf-8"),
|
|
301
|
+
fs.readFile(mapPath, "utf-8")
|
|
302
|
+
]);
|
|
303
|
+
const result2 = {
|
|
304
|
+
code: code2,
|
|
305
|
+
rawMap: JSON.parse(mapContent),
|
|
306
|
+
localPath: localPaths.beautifiedPath,
|
|
307
|
+
localMapPath: localPaths.mapPath,
|
|
308
|
+
usedFallback: false
|
|
309
|
+
};
|
|
310
|
+
await saveToLocal(result2, localPaths, mapContent);
|
|
311
|
+
return result2;
|
|
312
|
+
}
|
|
313
|
+
let esbuildResult;
|
|
314
|
+
try {
|
|
315
|
+
esbuildResult = await esbuild.build({
|
|
316
|
+
entryPoints: [absolutePath],
|
|
317
|
+
bundle: false,
|
|
318
|
+
write: false,
|
|
319
|
+
format: "esm",
|
|
320
|
+
sourcemap: "external",
|
|
321
|
+
sourcesContent: false,
|
|
322
|
+
outfile: "out.js",
|
|
323
|
+
// Beautify settings
|
|
324
|
+
minify: false,
|
|
325
|
+
keepNames: true,
|
|
326
|
+
treeShaking: false
|
|
327
|
+
});
|
|
328
|
+
} catch (err) {
|
|
329
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
330
|
+
return {
|
|
331
|
+
code: content,
|
|
332
|
+
rawMap: null,
|
|
333
|
+
localPath: localPaths.beautifiedPath,
|
|
334
|
+
localMapPath: localPaths.mapPath,
|
|
335
|
+
usedFallback: true
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const codeFile = esbuildResult.outputFiles?.find((f) => f.path.endsWith(".js"));
|
|
339
|
+
const mapFile = esbuildResult.outputFiles?.find((f) => f.path.endsWith(".map"));
|
|
340
|
+
if (!codeFile || !mapFile) {
|
|
341
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
342
|
+
return {
|
|
343
|
+
code: content,
|
|
344
|
+
rawMap: null,
|
|
345
|
+
localPath: localPaths.beautifiedPath,
|
|
346
|
+
localMapPath: localPaths.mapPath,
|
|
347
|
+
usedFallback: true
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const code = codeFile.text;
|
|
351
|
+
const rawMap = JSON.parse(mapFile.text);
|
|
352
|
+
const mapText = mapFile.text;
|
|
353
|
+
await Promise.all([
|
|
354
|
+
fs.writeFile(beautifiedPath, code, "utf-8"),
|
|
355
|
+
fs.writeFile(mapPath, mapText, "utf-8")
|
|
356
|
+
]);
|
|
357
|
+
const result = {
|
|
358
|
+
code,
|
|
359
|
+
rawMap,
|
|
360
|
+
localPath: localPaths.beautifiedPath,
|
|
361
|
+
localMapPath: localPaths.mapPath,
|
|
362
|
+
usedFallback: false
|
|
363
|
+
};
|
|
364
|
+
await saveToLocal(result, localPaths, mapText);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
async function saveToLocal(result, localPaths, mapText) {
|
|
368
|
+
try {
|
|
369
|
+
await Promise.all([
|
|
370
|
+
fs.writeFile(localPaths.beautifiedPath, result.code, "utf-8"),
|
|
371
|
+
fs.writeFile(localPaths.mapPath, mapText, "utf-8")
|
|
372
|
+
]);
|
|
373
|
+
result.localPath = localPaths.beautifiedPath;
|
|
374
|
+
result.localMapPath = localPaths.mapPath;
|
|
375
|
+
} catch (err) {
|
|
376
|
+
const error = err;
|
|
377
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
378
|
+
result.localSaveError = `Permission denied: Cannot write to ${path2.dirname(localPaths.beautifiedPath)}`;
|
|
379
|
+
} else if (error.code === "ENOSPC") {
|
|
380
|
+
result.localSaveError = `Insufficient disk space: Cannot write to ${path2.dirname(localPaths.beautifiedPath)}`;
|
|
381
|
+
} else {
|
|
382
|
+
result.localSaveError = `Failed to save locally: ${error.message || String(err)}`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/truncator.ts
|
|
388
|
+
import { parse } from "meriyah";
|
|
389
|
+
import { walk } from "estree-walker";
|
|
390
|
+
import MagicString from "magic-string";
|
|
391
|
+
function countNewlines(str) {
|
|
392
|
+
let count = 0;
|
|
393
|
+
for (const char of str) {
|
|
394
|
+
if (char === "\n") count++;
|
|
395
|
+
}
|
|
396
|
+
return count;
|
|
397
|
+
}
|
|
398
|
+
function createTruncatedString(original, previewLength) {
|
|
399
|
+
const newlineCount = countNewlines(original);
|
|
400
|
+
const start = original.slice(0, previewLength);
|
|
401
|
+
const end = original.slice(-previewLength);
|
|
402
|
+
const marker = `...[TRUNCATED ${original.length} CHARS]...`;
|
|
403
|
+
const startNewlines = countNewlines(start);
|
|
404
|
+
const endNewlines = countNewlines(end);
|
|
405
|
+
const preservedNewlines = Math.max(0, newlineCount - startNewlines - endNewlines);
|
|
406
|
+
const newlineStr = "\n".repeat(preservedNewlines);
|
|
407
|
+
return `${start}${marker}${newlineStr}${end}`;
|
|
408
|
+
}
|
|
409
|
+
function truncateCodeAST(sourceCode, limit = 200, previewLength = 50) {
|
|
410
|
+
let ast;
|
|
411
|
+
try {
|
|
412
|
+
ast = parse(sourceCode, {
|
|
413
|
+
module: true,
|
|
414
|
+
next: true,
|
|
415
|
+
ranges: true,
|
|
416
|
+
loc: true,
|
|
417
|
+
raw: true
|
|
418
|
+
});
|
|
419
|
+
} catch {
|
|
420
|
+
return { code: sourceCode, parseFailed: true };
|
|
421
|
+
}
|
|
422
|
+
const magicString = new MagicString(sourceCode);
|
|
423
|
+
walk(ast, {
|
|
424
|
+
enter(node) {
|
|
425
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
426
|
+
const literal = node;
|
|
427
|
+
const value = literal.value;
|
|
428
|
+
if (value.length > limit && literal.start !== void 0 && literal.end !== void 0) {
|
|
429
|
+
const truncated = createTruncatedString(value, previewLength);
|
|
430
|
+
const originalText = sourceCode.slice(literal.start, literal.end);
|
|
431
|
+
const quote = originalText[0];
|
|
432
|
+
magicString.overwrite(literal.start, literal.end, `${quote}${truncated}${quote}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (node.type === "TemplateLiteral") {
|
|
436
|
+
const template = node;
|
|
437
|
+
for (const quasi of template.quasis) {
|
|
438
|
+
const value = quasi.value.raw;
|
|
439
|
+
if (value.length > limit && quasi.start !== void 0 && quasi.end !== void 0) {
|
|
440
|
+
const truncated = createTruncatedString(value, previewLength);
|
|
441
|
+
magicString.overwrite(quasi.start, quasi.end, truncated);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
return { code: magicString.toString(), parseFailed: false };
|
|
448
|
+
}
|
|
449
|
+
function truncateCodeHighPerf(sourceCode, limit = 200, previewLength = 50) {
|
|
450
|
+
return truncateCodeAST(sourceCode, limit, previewLength).code;
|
|
451
|
+
}
|
|
452
|
+
function truncateFallback(code, maxLineChars = 500, previewLength = 50) {
|
|
453
|
+
if (!code) {
|
|
454
|
+
return code;
|
|
455
|
+
}
|
|
456
|
+
const lines = code.split("\n");
|
|
457
|
+
const processedLines = lines.map((line) => {
|
|
458
|
+
if (line.length <= maxLineChars) {
|
|
459
|
+
return line;
|
|
460
|
+
}
|
|
461
|
+
const start = line.slice(0, previewLength);
|
|
462
|
+
const end = line.slice(-previewLength);
|
|
463
|
+
const truncatedChars = line.length - previewLength * 2;
|
|
464
|
+
const marker = `...[LINE TRUNCATED ${truncatedChars} CHARS]...`;
|
|
465
|
+
return `${start}${marker}${end}`;
|
|
466
|
+
});
|
|
467
|
+
return processedLines.join("\n");
|
|
468
|
+
}
|
|
469
|
+
function truncateCode(code, options) {
|
|
470
|
+
const {
|
|
471
|
+
language,
|
|
472
|
+
charLimit = 200,
|
|
473
|
+
maxLineChars = 500,
|
|
474
|
+
previewLength = 50
|
|
475
|
+
} = options ?? {};
|
|
476
|
+
const langInfo = language ? getLanguageInfo(language) : null;
|
|
477
|
+
const supportsAST = langInfo?.supportsAST ?? false;
|
|
478
|
+
if (supportsAST && (language === "javascript" || language === "typescript")) {
|
|
479
|
+
const astResult = truncateCodeAST(code, charLimit, previewLength);
|
|
480
|
+
if (astResult.parseFailed) {
|
|
481
|
+
const truncatedCode3 = truncateFallback(code, maxLineChars, previewLength);
|
|
482
|
+
return {
|
|
483
|
+
code: truncatedCode3,
|
|
484
|
+
usedFallback: true
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const truncatedCode2 = truncateLongLines(astResult.code, maxLineChars, previewLength / maxLineChars);
|
|
488
|
+
return {
|
|
489
|
+
code: truncatedCode2,
|
|
490
|
+
usedFallback: false
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const truncatedCode = truncateFallback(code, maxLineChars, previewLength);
|
|
494
|
+
return {
|
|
495
|
+
code: truncatedCode,
|
|
496
|
+
usedFallback: true
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function truncateCodeFromFile(filePath, code, options) {
|
|
500
|
+
const language = options?.language ?? detectLanguage(filePath).language;
|
|
501
|
+
return truncateCode(code, {
|
|
502
|
+
...options,
|
|
503
|
+
language
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function truncateLongLines(code, maxLineChars = 500, previewRatio = 0.2) {
|
|
507
|
+
if (!code) {
|
|
508
|
+
return code;
|
|
509
|
+
}
|
|
510
|
+
const lines = code.split("\n");
|
|
511
|
+
const previewLength = Math.floor(maxLineChars * previewRatio);
|
|
512
|
+
const processedLines = lines.map((line) => {
|
|
513
|
+
if (line.length <= maxLineChars) {
|
|
514
|
+
return line;
|
|
515
|
+
}
|
|
516
|
+
const start = line.slice(0, previewLength);
|
|
517
|
+
const end = line.slice(-previewLength);
|
|
518
|
+
const truncatedChars = line.length - previewLength * 2;
|
|
519
|
+
const marker = `...[LINE TRUNCATED ${truncatedChars} CHARS]...`;
|
|
520
|
+
return `${start}${marker}${end}`;
|
|
521
|
+
});
|
|
522
|
+
return processedLines.join("\n");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/searcher.ts
|
|
526
|
+
import { SourceMapConsumer } from "source-map-js";
|
|
527
|
+
function unescapeBackslashes(str) {
|
|
528
|
+
return str.replace(/\\\\/g, "\\");
|
|
529
|
+
}
|
|
530
|
+
function escapeRegex(str) {
|
|
531
|
+
return str.replace(/[()[\]{}.*+?^$|\\]/g, "\\$&");
|
|
532
|
+
}
|
|
533
|
+
function createRegex(query, caseSensitive = false, isRegex = false) {
|
|
534
|
+
try {
|
|
535
|
+
const flags = caseSensitive ? "g" : "gi";
|
|
536
|
+
const pattern = isRegex ? query : escapeRegex(query);
|
|
537
|
+
return new RegExp(pattern, flags);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
540
|
+
throw new Error(`Invalid regex: ${message}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function getOriginalPosition(consumer, lineNumber) {
|
|
544
|
+
const pos = consumer.originalPositionFor({
|
|
545
|
+
line: lineNumber,
|
|
546
|
+
column: 0
|
|
547
|
+
});
|
|
548
|
+
return {
|
|
549
|
+
line: pos.line,
|
|
550
|
+
column: pos.column
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function buildLineOffsets(code) {
|
|
554
|
+
let capacity = Math.max(16, Math.ceil(code.length / 40));
|
|
555
|
+
let offsets = new Int32Array(capacity);
|
|
556
|
+
let lineCount = 0;
|
|
557
|
+
offsets[0] = 0;
|
|
558
|
+
for (let i = 0; i < code.length; i++) {
|
|
559
|
+
if (code[i] === "\n") {
|
|
560
|
+
lineCount++;
|
|
561
|
+
if (lineCount >= capacity) {
|
|
562
|
+
capacity *= 2;
|
|
563
|
+
const newArr = new Int32Array(capacity);
|
|
564
|
+
newArr.set(offsets);
|
|
565
|
+
offsets = newArr;
|
|
566
|
+
}
|
|
567
|
+
offsets[lineCount] = i + 1;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
offsets: offsets.subarray(0, lineCount + 1),
|
|
572
|
+
totalLines: lineCount + 1
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function getLineNumberFromIndex(offsets, totalLines, index) {
|
|
576
|
+
let low = 0;
|
|
577
|
+
let high = totalLines - 1;
|
|
578
|
+
while (low <= high) {
|
|
579
|
+
const mid = low + high >>> 1;
|
|
580
|
+
if (offsets[mid] <= index) {
|
|
581
|
+
low = mid + 1;
|
|
582
|
+
} else {
|
|
583
|
+
high = mid - 1;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return low;
|
|
587
|
+
}
|
|
588
|
+
function getLineContent(code, offsets, totalLines, lineNo) {
|
|
589
|
+
if (lineNo < 1 || lineNo > totalLines) return "";
|
|
590
|
+
const start = offsets[lineNo - 1];
|
|
591
|
+
let end;
|
|
592
|
+
if (lineNo < totalLines) {
|
|
593
|
+
end = offsets[lineNo] - 1;
|
|
594
|
+
} else {
|
|
595
|
+
end = code.length;
|
|
596
|
+
}
|
|
597
|
+
if (end > start && code[end - 1] === "\r") {
|
|
598
|
+
end--;
|
|
599
|
+
}
|
|
600
|
+
return code.slice(start, end);
|
|
601
|
+
}
|
|
602
|
+
function searchInCode(code, rawMap, options) {
|
|
603
|
+
const {
|
|
604
|
+
query,
|
|
605
|
+
contextLines = 2,
|
|
606
|
+
caseSensitive = false,
|
|
607
|
+
maxMatches = 10,
|
|
608
|
+
isRegex = false,
|
|
609
|
+
timeoutMs = 500
|
|
610
|
+
} = options;
|
|
611
|
+
const { offsets, totalLines } = buildLineOffsets(code);
|
|
612
|
+
const flags = (caseSensitive ? "g" : "gi") + "m";
|
|
613
|
+
const patternStr = isRegex ? unescapeBackslashes(query) : escapeRegex(query);
|
|
614
|
+
let regex;
|
|
615
|
+
try {
|
|
616
|
+
regex = new RegExp(patternStr, flags);
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
619
|
+
throw new Error(`Invalid regex: ${message}`);
|
|
620
|
+
}
|
|
621
|
+
const consumer = new SourceMapConsumer({
|
|
622
|
+
...rawMap,
|
|
623
|
+
version: String(rawMap.version)
|
|
624
|
+
});
|
|
625
|
+
const matches = [];
|
|
626
|
+
let lastMatchedLine = -1;
|
|
627
|
+
let totalMatchesFound = 0;
|
|
628
|
+
let match;
|
|
629
|
+
const startTime = Date.now();
|
|
630
|
+
while ((match = regex.exec(code)) !== null) {
|
|
631
|
+
if (match.index === regex.lastIndex) {
|
|
632
|
+
regex.lastIndex++;
|
|
633
|
+
}
|
|
634
|
+
const lineNumber = getLineNumberFromIndex(offsets, totalLines, match.index);
|
|
635
|
+
if (lineNumber === lastMatchedLine) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
lastMatchedLine = lineNumber;
|
|
639
|
+
totalMatchesFound++;
|
|
640
|
+
if (matches.length < maxMatches) {
|
|
641
|
+
const contextBefore = [];
|
|
642
|
+
for (let i = Math.max(1, lineNumber - contextLines); i < lineNumber; i++) {
|
|
643
|
+
contextBefore.push({
|
|
644
|
+
lineNumber: i,
|
|
645
|
+
content: getLineContent(code, offsets, totalLines, i),
|
|
646
|
+
originalPosition: getOriginalPosition(consumer, i)
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const contextAfter = [];
|
|
650
|
+
for (let i = lineNumber + 1; i <= Math.min(totalLines, lineNumber + contextLines); i++) {
|
|
651
|
+
contextAfter.push({
|
|
652
|
+
lineNumber: i,
|
|
653
|
+
content: getLineContent(code, offsets, totalLines, i),
|
|
654
|
+
originalPosition: getOriginalPosition(consumer, i)
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
matches.push({
|
|
658
|
+
lineNumber,
|
|
659
|
+
lineContent: getLineContent(code, offsets, totalLines, lineNumber),
|
|
660
|
+
originalPosition: getOriginalPosition(consumer, lineNumber),
|
|
661
|
+
contextBefore,
|
|
662
|
+
contextAfter
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
matches,
|
|
671
|
+
totalMatches: totalMatchesFound,
|
|
672
|
+
truncated: totalMatchesFound > maxMatches
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function formatSourcePosition(line, column) {
|
|
676
|
+
if (line !== null && column !== null) {
|
|
677
|
+
return `L${line}:${column}`;
|
|
678
|
+
}
|
|
679
|
+
return "";
|
|
680
|
+
}
|
|
681
|
+
function formatCodeLine(lineNumber, sourcePos, code, maxLineNumWidth, prefix = " ") {
|
|
682
|
+
const lineNumStr = String(lineNumber).padStart(maxLineNumWidth, " ");
|
|
683
|
+
const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
|
|
684
|
+
return `${prefix}${lineNumStr} ${srcPosPadded} ${code}`;
|
|
685
|
+
}
|
|
686
|
+
function formatSearchResult(filePath, query, caseSensitive, result, maxMatches = 50, isRegex = false) {
|
|
687
|
+
const { matches, totalMatches, truncated } = result;
|
|
688
|
+
const outputParts = [];
|
|
689
|
+
const caseInfo = caseSensitive ? "case-sensitive" : "case-insensitive";
|
|
690
|
+
const modeInfo = isRegex ? "regex" : "literal";
|
|
691
|
+
outputParts.push(`${filePath}`);
|
|
692
|
+
outputParts.push(`Query="${query}" (${modeInfo}, ${caseInfo})`);
|
|
693
|
+
outputParts.push(`Src=original position for breakpoints`);
|
|
694
|
+
if (totalMatches === 0) {
|
|
695
|
+
outputParts.push("Matches: None");
|
|
696
|
+
return outputParts.join("\n");
|
|
697
|
+
}
|
|
698
|
+
const matchInfo = truncated ? `Matches: ${totalMatches} (showing first ${maxMatches})` : `Matches: ${totalMatches}`;
|
|
699
|
+
outputParts.push(matchInfo);
|
|
700
|
+
for (const match of matches) {
|
|
701
|
+
outputParts.push(`--- Line ${match.lineNumber} ---`);
|
|
702
|
+
const allLineNumbers = [
|
|
703
|
+
...match.contextBefore.map((c) => c.lineNumber),
|
|
704
|
+
match.lineNumber,
|
|
705
|
+
...match.contextAfter.map((c) => c.lineNumber)
|
|
706
|
+
];
|
|
707
|
+
const maxLineNumWidth = Math.max(...allLineNumbers.map((n) => String(n).length));
|
|
708
|
+
for (const ctx of match.contextBefore) {
|
|
709
|
+
const srcPos = formatSourcePosition(ctx.originalPosition.line, ctx.originalPosition.column);
|
|
710
|
+
outputParts.push(formatCodeLine(ctx.lineNumber, srcPos, ctx.content, maxLineNumWidth, " "));
|
|
711
|
+
}
|
|
712
|
+
const matchSrcPos = formatSourcePosition(match.originalPosition.line, match.originalPosition.column);
|
|
713
|
+
outputParts.push(formatCodeLine(match.lineNumber, matchSrcPos, match.lineContent, maxLineNumWidth, ">>"));
|
|
714
|
+
for (const ctx of match.contextAfter) {
|
|
715
|
+
const srcPos = formatSourcePosition(ctx.originalPosition.line, ctx.originalPosition.column);
|
|
716
|
+
outputParts.push(formatCodeLine(ctx.lineNumber, srcPos, ctx.content, maxLineNumWidth, " "));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (truncated) {
|
|
720
|
+
outputParts.push(`
|
|
721
|
+
... (${totalMatches - maxMatches} more matches not shown)`);
|
|
722
|
+
}
|
|
723
|
+
return outputParts.join("\n");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/analyzer.ts
|
|
727
|
+
import { SourceMapConsumer as SourceMapConsumer2 } from "source-map-js";
|
|
728
|
+
import { parse as parse2 } from "@babel/parser";
|
|
729
|
+
var traverse = null;
|
|
730
|
+
async function getTraverse() {
|
|
731
|
+
if (!traverse) {
|
|
732
|
+
const mod = await import("@babel/traverse");
|
|
733
|
+
traverse = mod.default?.default ?? mod.default;
|
|
734
|
+
}
|
|
735
|
+
return traverse;
|
|
736
|
+
}
|
|
737
|
+
var DEFAULT_PARSER_OPTIONS = {
|
|
738
|
+
sourceType: "unambiguous",
|
|
739
|
+
plugins: [
|
|
740
|
+
"jsx",
|
|
741
|
+
"typescript",
|
|
742
|
+
"classProperties",
|
|
743
|
+
"classPrivateProperties",
|
|
744
|
+
"classPrivateMethods",
|
|
745
|
+
"dynamicImport",
|
|
746
|
+
"optionalChaining",
|
|
747
|
+
"nullishCoalescingOperator",
|
|
748
|
+
"objectRestSpread"
|
|
749
|
+
],
|
|
750
|
+
errorRecovery: true
|
|
751
|
+
};
|
|
752
|
+
function parseCode(code) {
|
|
753
|
+
try {
|
|
754
|
+
return parse2(code, DEFAULT_PARSER_OPTIONS);
|
|
755
|
+
} catch (err) {
|
|
756
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
757
|
+
throw new Error(`Parse error: ${message}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function getOriginalPosition2(consumer, line, column) {
|
|
761
|
+
const pos = consumer.originalPositionFor({ line, column });
|
|
762
|
+
return {
|
|
763
|
+
line: pos.line,
|
|
764
|
+
column: pos.column
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function getLineContent2(lines, lineNumber) {
|
|
768
|
+
if (lineNumber < 1 || lineNumber > lines.length) {
|
|
769
|
+
return "";
|
|
770
|
+
}
|
|
771
|
+
return lines[lineNumber - 1];
|
|
772
|
+
}
|
|
773
|
+
function createLocationInfo(line, column, lines, consumer) {
|
|
774
|
+
return {
|
|
775
|
+
line,
|
|
776
|
+
column,
|
|
777
|
+
originalPosition: getOriginalPosition2(consumer, line, column),
|
|
778
|
+
lineContent: getLineContent2(lines, line)
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
async function analyzeBindings(code, rawMap, identifier, options) {
|
|
782
|
+
const targetLine = options?.targetLine;
|
|
783
|
+
const isTargeted = targetLine !== void 0;
|
|
784
|
+
const maxReferences = options?.maxReferences ?? (isTargeted ? 15 : 10);
|
|
785
|
+
const ast = parseCode(code);
|
|
786
|
+
const lines = code.split("\n");
|
|
787
|
+
const consumer = new SourceMapConsumer2({
|
|
788
|
+
...rawMap,
|
|
789
|
+
version: String(rawMap.version)
|
|
790
|
+
});
|
|
791
|
+
const bindings = [];
|
|
792
|
+
const processedScopes = /* @__PURE__ */ new Set();
|
|
793
|
+
const traverse2 = await getTraverse();
|
|
794
|
+
try {
|
|
795
|
+
traverse2(ast, {
|
|
796
|
+
Identifier(path5) {
|
|
797
|
+
if (path5.node.name !== identifier) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (isTargeted) {
|
|
801
|
+
const nodeLoc = path5.node.loc;
|
|
802
|
+
if (!nodeLoc || nodeLoc.start.line !== targetLine) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const binding = path5.scope.getBinding(identifier);
|
|
807
|
+
if (!binding) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const scopeUid = binding.scope.uid;
|
|
811
|
+
if (processedScopes.has(scopeUid)) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
processedScopes.add(scopeUid);
|
|
815
|
+
const defNode = binding.identifier;
|
|
816
|
+
const defLoc = defNode.loc;
|
|
817
|
+
if (!defLoc) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const definition = createLocationInfo(
|
|
821
|
+
defLoc.start.line,
|
|
822
|
+
defLoc.start.column,
|
|
823
|
+
lines,
|
|
824
|
+
consumer
|
|
825
|
+
);
|
|
826
|
+
const allReferences = [];
|
|
827
|
+
for (const refPath of binding.referencePaths) {
|
|
828
|
+
const refLoc = refPath.node.loc;
|
|
829
|
+
if (!refLoc) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
allReferences.push(
|
|
833
|
+
createLocationInfo(
|
|
834
|
+
refLoc.start.line,
|
|
835
|
+
refLoc.start.column,
|
|
836
|
+
lines,
|
|
837
|
+
consumer
|
|
838
|
+
)
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
const totalReferences = allReferences.length;
|
|
842
|
+
const limitedReferences = allReferences.slice(0, maxReferences);
|
|
843
|
+
let hitLocation;
|
|
844
|
+
if (isTargeted) {
|
|
845
|
+
const nodeLoc = path5.node.loc;
|
|
846
|
+
hitLocation = createLocationInfo(
|
|
847
|
+
nodeLoc.start.line,
|
|
848
|
+
nodeLoc.start.column,
|
|
849
|
+
lines,
|
|
850
|
+
consumer
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
bindings.push({
|
|
854
|
+
scopeUid,
|
|
855
|
+
kind: binding.kind,
|
|
856
|
+
definition,
|
|
857
|
+
references: limitedReferences,
|
|
858
|
+
totalReferences,
|
|
859
|
+
hitLocation
|
|
860
|
+
});
|
|
861
|
+
if (isTargeted) {
|
|
862
|
+
path5.stop();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
} catch (err) {
|
|
867
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
868
|
+
throw new Error(`Analysis error: ${message}`);
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
bindings,
|
|
872
|
+
identifier,
|
|
873
|
+
isTargeted,
|
|
874
|
+
targetLine
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function formatSourcePosition2(line, column) {
|
|
878
|
+
if (line !== null && column !== null) {
|
|
879
|
+
return `L${line}:${column}`;
|
|
880
|
+
}
|
|
881
|
+
return "";
|
|
882
|
+
}
|
|
883
|
+
function formatCodeLine2(lineNumber, sourcePos, code, prefix = " ") {
|
|
884
|
+
const lineNumStr = String(lineNumber).padStart(5, " ");
|
|
885
|
+
const srcPosPadded = sourcePos ? sourcePos.padEnd(10, " ") : " ";
|
|
886
|
+
return `${prefix}${lineNumStr} ${srcPosPadded} ${code}`;
|
|
887
|
+
}
|
|
888
|
+
function locationsMatch(loc1, loc2) {
|
|
889
|
+
return loc1.line === loc2.line && loc1.column === loc2.column;
|
|
890
|
+
}
|
|
891
|
+
function formatAnalysisResult(filePath, result, maxReferences = 10) {
|
|
892
|
+
const { bindings, identifier, isTargeted, targetLine } = result;
|
|
893
|
+
const outputParts = [];
|
|
894
|
+
outputParts.push(`${filePath}`);
|
|
895
|
+
outputParts.push(`Identifier="${identifier}"`);
|
|
896
|
+
outputParts.push(`Src=original position for breakpoints`);
|
|
897
|
+
if (bindings.length === 0) {
|
|
898
|
+
if (isTargeted && targetLine !== void 0) {
|
|
899
|
+
outputParts.push(`Bindings: None at line ${targetLine}`);
|
|
900
|
+
outputParts.push(`The variable may be global, externally defined, or not present at this line.`);
|
|
901
|
+
} else {
|
|
902
|
+
outputParts.push("Bindings: None");
|
|
903
|
+
}
|
|
904
|
+
return outputParts.join("\n");
|
|
905
|
+
}
|
|
906
|
+
if (isTargeted) {
|
|
907
|
+
outputParts.push(`Bindings: 1 (Targeted at line ${targetLine})`);
|
|
908
|
+
} else {
|
|
909
|
+
const scopeInfo = bindings.length > 1 ? " (different scopes)" : "";
|
|
910
|
+
outputParts.push(`Bindings: ${bindings.length}${scopeInfo}`);
|
|
911
|
+
}
|
|
912
|
+
for (let i = 0; i < bindings.length; i++) {
|
|
913
|
+
const binding = bindings[i];
|
|
914
|
+
if (isTargeted) {
|
|
915
|
+
outputParts.push(`--- Targeted Scope (${binding.kind}) ---`);
|
|
916
|
+
} else {
|
|
917
|
+
outputParts.push(`--- Scope #${i + 1} (${binding.kind}) ---`);
|
|
918
|
+
}
|
|
919
|
+
const defIsHit = isTargeted && binding.hitLocation && locationsMatch(binding.definition, binding.hitLocation);
|
|
920
|
+
const defPrefix = defIsHit ? "\u{1F4CD} Definition (hit):" : "\u{1F4CD} Definition:";
|
|
921
|
+
outputParts.push(defPrefix);
|
|
922
|
+
const defSrcPos = formatSourcePosition2(
|
|
923
|
+
binding.definition.originalPosition.line,
|
|
924
|
+
binding.definition.originalPosition.column
|
|
925
|
+
);
|
|
926
|
+
const defMarker = defIsHit ? " \u25C0\u2500\u2500 hit" : "";
|
|
927
|
+
outputParts.push(formatCodeLine2(binding.definition.line, defSrcPos, binding.definition.lineContent + defMarker, " "));
|
|
928
|
+
const totalRefs = binding.totalReferences;
|
|
929
|
+
if (totalRefs === 0) {
|
|
930
|
+
outputParts.push("\u{1F50E} References: None");
|
|
931
|
+
} else {
|
|
932
|
+
outputParts.push(`\u{1F50E} References (${totalRefs}):`);
|
|
933
|
+
for (const ref of binding.references) {
|
|
934
|
+
const refIsHit = isTargeted && binding.hitLocation && locationsMatch(ref, binding.hitLocation);
|
|
935
|
+
const refSrcPos = formatSourcePosition2(
|
|
936
|
+
ref.originalPosition.line,
|
|
937
|
+
ref.originalPosition.column
|
|
938
|
+
);
|
|
939
|
+
const refMarker = refIsHit ? " \u25C0\u2500\u2500 hit" : "";
|
|
940
|
+
outputParts.push(formatCodeLine2(ref.line, refSrcPos, ref.lineContent + refMarker, " "));
|
|
941
|
+
}
|
|
942
|
+
if (totalRefs > maxReferences) {
|
|
943
|
+
const remaining = totalRefs - maxReferences;
|
|
944
|
+
outputParts.push(` ... (${remaining} more references not shown)`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return outputParts.join("\n");
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/transformer.ts
|
|
952
|
+
import * as path3 from "path";
|
|
953
|
+
import * as fs2 from "fs/promises";
|
|
954
|
+
import { transformSync } from "@babel/core";
|
|
955
|
+
function cleanBasename(filename) {
|
|
956
|
+
const base = path3.basename(filename);
|
|
957
|
+
let name = base.endsWith(".js") ? base.slice(0, -3) : base;
|
|
958
|
+
name = name.replace(/_deob[^/]*$/, "");
|
|
959
|
+
if (name.endsWith(".beautified")) {
|
|
960
|
+
name = name.slice(0, -".beautified".length);
|
|
961
|
+
}
|
|
962
|
+
return name;
|
|
963
|
+
}
|
|
964
|
+
function getOutputPaths(targetFile, outputSuffix = "_deob") {
|
|
965
|
+
const absolutePath = path3.resolve(targetFile);
|
|
966
|
+
const dir = path3.dirname(absolutePath);
|
|
967
|
+
const basename3 = cleanBasename(absolutePath);
|
|
968
|
+
const outputPath = path3.join(dir, `${basename3}${outputSuffix}.js`);
|
|
969
|
+
const mapPath = `${outputPath}.map`;
|
|
970
|
+
return { outputPath, mapPath };
|
|
971
|
+
}
|
|
972
|
+
async function loadBabelPlugin(scriptPath) {
|
|
973
|
+
const absolutePath = path3.resolve(scriptPath);
|
|
974
|
+
try {
|
|
975
|
+
await fs2.access(absolutePath);
|
|
976
|
+
} catch {
|
|
977
|
+
throw new Error(`Script not found: ${absolutePath}`);
|
|
978
|
+
}
|
|
979
|
+
const fileUrl = `file://${absolutePath}?t=${Date.now()}`;
|
|
980
|
+
let module;
|
|
981
|
+
try {
|
|
982
|
+
module = await import(fileUrl);
|
|
983
|
+
} catch (err) {
|
|
984
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
985
|
+
throw new Error(`Failed to load script: ${message}`);
|
|
986
|
+
}
|
|
987
|
+
const plugin = module.default ?? module;
|
|
988
|
+
if (typeof plugin !== "function") {
|
|
989
|
+
throw new Error(
|
|
990
|
+
`Invalid Babel plugin: Script must export a function that returns a visitor object. Got ${typeof plugin} instead.`
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
return plugin;
|
|
994
|
+
}
|
|
995
|
+
function runBabelTransform(code, inputSourceMap, plugin, filename) {
|
|
996
|
+
let result;
|
|
997
|
+
try {
|
|
998
|
+
result = transformSync(code, {
|
|
999
|
+
filename,
|
|
1000
|
+
plugins: [plugin],
|
|
1001
|
+
// Source map configuration for cascade
|
|
1002
|
+
// @ts-expect-error - SourceMap is compatible with InputSourceMap at runtime
|
|
1003
|
+
inputSourceMap,
|
|
1004
|
+
sourceMaps: true,
|
|
1005
|
+
// Readability settings
|
|
1006
|
+
retainLines: false,
|
|
1007
|
+
compact: false,
|
|
1008
|
+
minified: false,
|
|
1009
|
+
// Preserve code structure
|
|
1010
|
+
parserOpts: {
|
|
1011
|
+
sourceType: "unambiguous"
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1016
|
+
throw new Error(`Babel Error: ${message}`);
|
|
1017
|
+
}
|
|
1018
|
+
if (!result || !result.code) {
|
|
1019
|
+
throw new Error("Babel Error: Transform produced no output");
|
|
1020
|
+
}
|
|
1021
|
+
if (!result.map) {
|
|
1022
|
+
throw new Error("Babel Error: Transform produced no source map");
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
code: result.code,
|
|
1026
|
+
map: result.map
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
async function applyCustomTransform(targetFile, options) {
|
|
1030
|
+
const { scriptPath, outputSuffix = "_deob" } = options;
|
|
1031
|
+
const absoluteTargetPath = path3.resolve(targetFile);
|
|
1032
|
+
try {
|
|
1033
|
+
await fs2.access(absoluteTargetPath);
|
|
1034
|
+
} catch {
|
|
1035
|
+
throw new Error(`File not found: ${targetFile}`);
|
|
1036
|
+
}
|
|
1037
|
+
const plugin = await loadBabelPlugin(scriptPath);
|
|
1038
|
+
const beautifyResult = await ensureBeautified(absoluteTargetPath);
|
|
1039
|
+
const { code: beautifiedCode, rawMap: inputSourceMap } = beautifyResult;
|
|
1040
|
+
const transformResult = runBabelTransform(
|
|
1041
|
+
beautifiedCode,
|
|
1042
|
+
inputSourceMap,
|
|
1043
|
+
plugin,
|
|
1044
|
+
absoluteTargetPath
|
|
1045
|
+
);
|
|
1046
|
+
const { outputPath, mapPath } = getOutputPaths(absoluteTargetPath, outputSuffix);
|
|
1047
|
+
const mapFileName = path3.basename(mapPath);
|
|
1048
|
+
const outputCode = `${transformResult.code}
|
|
1049
|
+
//# sourceMappingURL=${mapFileName}`;
|
|
1050
|
+
const outputMap = {
|
|
1051
|
+
...transformResult.map,
|
|
1052
|
+
file: path3.basename(outputPath)
|
|
1053
|
+
};
|
|
1054
|
+
try {
|
|
1055
|
+
await Promise.all([
|
|
1056
|
+
fs2.writeFile(outputPath, outputCode, "utf-8"),
|
|
1057
|
+
fs2.writeFile(mapPath, JSON.stringify(outputMap, null, 2), "utf-8")
|
|
1058
|
+
]);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
const error = err;
|
|
1061
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
1062
|
+
throw new Error(`Permission denied: Cannot write to ${path3.dirname(outputPath)}`);
|
|
1063
|
+
}
|
|
1064
|
+
throw new Error(`Failed to write output files: ${error.message || String(err)}`);
|
|
1065
|
+
}
|
|
1066
|
+
return {
|
|
1067
|
+
code: outputCode,
|
|
1068
|
+
map: outputMap,
|
|
1069
|
+
outputPath,
|
|
1070
|
+
mapPath
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/index.ts
|
|
1075
|
+
async function smartRead(filePath, options) {
|
|
1076
|
+
const absolutePath = path4.resolve(filePath);
|
|
1077
|
+
try {
|
|
1078
|
+
await fs3.access(absolutePath);
|
|
1079
|
+
} catch {
|
|
1080
|
+
return {
|
|
1081
|
+
code: "",
|
|
1082
|
+
sourceMap: null,
|
|
1083
|
+
language: "unknown",
|
|
1084
|
+
usedFallback: true,
|
|
1085
|
+
error: `File not found: ${filePath}`
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const langInfo = options?.language ? getLanguageInfo(options.language) : detectLanguage(absolutePath);
|
|
1089
|
+
try {
|
|
1090
|
+
const beautifyResult = await ensureBeautified(absolutePath, {
|
|
1091
|
+
language: options?.language,
|
|
1092
|
+
saveLocal: options?.saveLocal
|
|
1093
|
+
});
|
|
1094
|
+
let code = beautifyResult.code;
|
|
1095
|
+
if (options?.startLine !== void 0 || options?.endLine !== void 0) {
|
|
1096
|
+
const lines = code.split("\n");
|
|
1097
|
+
const startLine = Math.max(1, options?.startLine ?? 1);
|
|
1098
|
+
const endLine = Math.min(lines.length, options?.endLine ?? lines.length);
|
|
1099
|
+
code = lines.slice(startLine - 1, endLine).join("\n");
|
|
1100
|
+
}
|
|
1101
|
+
const truncateResult = truncateCodeFromFile(absolutePath, code, {
|
|
1102
|
+
language: options?.language,
|
|
1103
|
+
charLimit: options?.charLimit,
|
|
1104
|
+
maxLineChars: options?.maxLineChars,
|
|
1105
|
+
previewLength: options?.previewLength
|
|
1106
|
+
});
|
|
1107
|
+
return {
|
|
1108
|
+
code: truncateResult.code,
|
|
1109
|
+
sourceMap: beautifyResult.rawMap,
|
|
1110
|
+
language: langInfo.language,
|
|
1111
|
+
usedFallback: beautifyResult.usedFallback || truncateResult.usedFallback,
|
|
1112
|
+
localPath: beautifyResult.localPath
|
|
1113
|
+
};
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1116
|
+
return {
|
|
1117
|
+
code: "",
|
|
1118
|
+
sourceMap: null,
|
|
1119
|
+
language: langInfo.language,
|
|
1120
|
+
usedFallback: true,
|
|
1121
|
+
error: message
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async function smartSearch(filePath, query, options) {
|
|
1126
|
+
const absolutePath = path4.resolve(filePath);
|
|
1127
|
+
try {
|
|
1128
|
+
await fs3.access(absolutePath);
|
|
1129
|
+
} catch {
|
|
1130
|
+
return {
|
|
1131
|
+
matches: [],
|
|
1132
|
+
totalMatches: 0,
|
|
1133
|
+
truncated: false,
|
|
1134
|
+
formatted: `File not found: ${filePath}`,
|
|
1135
|
+
error: `File not found: ${filePath}`
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
const beautifyResult = await ensureBeautified(absolutePath);
|
|
1140
|
+
if (!beautifyResult.rawMap) {
|
|
1141
|
+
return {
|
|
1142
|
+
matches: [],
|
|
1143
|
+
totalMatches: 0,
|
|
1144
|
+
truncated: false,
|
|
1145
|
+
formatted: `Search not supported for this file type (no source map available)`,
|
|
1146
|
+
error: `Search requires source map support`
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const searchResult = searchInCode(
|
|
1150
|
+
beautifyResult.code,
|
|
1151
|
+
beautifyResult.rawMap,
|
|
1152
|
+
options ?? { query }
|
|
1153
|
+
);
|
|
1154
|
+
const formatted = formatSearchResult(
|
|
1155
|
+
filePath,
|
|
1156
|
+
query,
|
|
1157
|
+
options?.caseSensitive ?? false,
|
|
1158
|
+
searchResult,
|
|
1159
|
+
options?.maxMatches ?? 50,
|
|
1160
|
+
options?.isRegex ?? false
|
|
1161
|
+
);
|
|
1162
|
+
return {
|
|
1163
|
+
...searchResult,
|
|
1164
|
+
formatted
|
|
1165
|
+
};
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1168
|
+
return {
|
|
1169
|
+
matches: [],
|
|
1170
|
+
totalMatches: 0,
|
|
1171
|
+
truncated: false,
|
|
1172
|
+
formatted: `Search error: ${message}`,
|
|
1173
|
+
error: message
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async function findUsage(filePath, identifier, options) {
|
|
1178
|
+
const absolutePath = path4.resolve(filePath);
|
|
1179
|
+
try {
|
|
1180
|
+
await fs3.access(absolutePath);
|
|
1181
|
+
} catch {
|
|
1182
|
+
return {
|
|
1183
|
+
bindings: [],
|
|
1184
|
+
identifier,
|
|
1185
|
+
isTargeted: options?.targetLine !== void 0,
|
|
1186
|
+
targetLine: options?.targetLine,
|
|
1187
|
+
formatted: `File not found: ${filePath}`,
|
|
1188
|
+
error: `File not found: ${filePath}`
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
try {
|
|
1192
|
+
const beautifyResult = await ensureBeautified(absolutePath);
|
|
1193
|
+
if (!beautifyResult.rawMap) {
|
|
1194
|
+
return {
|
|
1195
|
+
bindings: [],
|
|
1196
|
+
identifier,
|
|
1197
|
+
isTargeted: options?.targetLine !== void 0,
|
|
1198
|
+
targetLine: options?.targetLine,
|
|
1199
|
+
formatted: `Analysis not supported for this file type (no source map available)`,
|
|
1200
|
+
error: `Analysis requires source map support`
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
const analysisResult = await analyzeBindings(
|
|
1204
|
+
beautifyResult.code,
|
|
1205
|
+
beautifyResult.rawMap,
|
|
1206
|
+
identifier,
|
|
1207
|
+
options
|
|
1208
|
+
);
|
|
1209
|
+
const formatted = formatAnalysisResult(
|
|
1210
|
+
filePath,
|
|
1211
|
+
analysisResult,
|
|
1212
|
+
options?.maxReferences ?? 10
|
|
1213
|
+
);
|
|
1214
|
+
return {
|
|
1215
|
+
...analysisResult,
|
|
1216
|
+
formatted
|
|
1217
|
+
};
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1220
|
+
return {
|
|
1221
|
+
bindings: [],
|
|
1222
|
+
identifier,
|
|
1223
|
+
isTargeted: options?.targetLine !== void 0,
|
|
1224
|
+
targetLine: options?.targetLine,
|
|
1225
|
+
formatted: `Analysis error: ${message}`,
|
|
1226
|
+
error: message
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
export {
|
|
1231
|
+
analyzeBindings,
|
|
1232
|
+
applyCustomTransform,
|
|
1233
|
+
beautifyCode,
|
|
1234
|
+
beautifyCss,
|
|
1235
|
+
beautifyHtml,
|
|
1236
|
+
beautifyJson,
|
|
1237
|
+
cleanBasename,
|
|
1238
|
+
createRegex,
|
|
1239
|
+
detectLanguage,
|
|
1240
|
+
ensureBeautified,
|
|
1241
|
+
escapeRegex,
|
|
1242
|
+
findUsage,
|
|
1243
|
+
formatAnalysisResult,
|
|
1244
|
+
formatSourcePosition2 as formatAnalyzeSourcePosition,
|
|
1245
|
+
formatSearchResult,
|
|
1246
|
+
formatSourcePosition as formatSearchSourcePosition,
|
|
1247
|
+
getLanguageInfo,
|
|
1248
|
+
getLocalPaths,
|
|
1249
|
+
getOutputPaths,
|
|
1250
|
+
getSupportedExtensions,
|
|
1251
|
+
isExtensionSupported,
|
|
1252
|
+
isFullySupportedLanguage,
|
|
1253
|
+
isLocalCacheValid,
|
|
1254
|
+
loadBabelPlugin,
|
|
1255
|
+
parseCode,
|
|
1256
|
+
runBabelTransform,
|
|
1257
|
+
searchInCode,
|
|
1258
|
+
smartRead,
|
|
1259
|
+
smartSearch,
|
|
1260
|
+
truncateCode,
|
|
1261
|
+
truncateCodeFromFile,
|
|
1262
|
+
truncateCodeHighPerf,
|
|
1263
|
+
truncateFallback,
|
|
1264
|
+
truncateLongLines,
|
|
1265
|
+
unescapeBackslashes
|
|
1266
|
+
};
|
|
1267
|
+
//# sourceMappingURL=index.js.map
|