@justmpm/supergrep 0.1.1 → 0.2.1
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/dist/{chunk-SENMQNTX.js → chunk-LUKDYG3I.js} +299 -188
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +5 -2
- package/package.json +1 -1
|
@@ -6,9 +6,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
8
|
// src/tools/find.ts
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { findInFiles } from "@ast-grep/napi";
|
|
10
|
+
import { relative, resolve as resolve2 } from "path";
|
|
11
|
+
import { stat } from "fs/promises";
|
|
12
12
|
|
|
13
13
|
// src/utils/lang.ts
|
|
14
14
|
import { Lang } from "@ast-grep/napi";
|
|
@@ -65,10 +65,6 @@ function langName(lang) {
|
|
|
65
65
|
};
|
|
66
66
|
return friendly[langStr] ?? langStr;
|
|
67
67
|
}
|
|
68
|
-
function isSupportedExtension(filePath) {
|
|
69
|
-
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
70
|
-
return SUPPORTED_EXTENSIONS.has(ext);
|
|
71
|
-
}
|
|
72
68
|
var IGNORED_EXTENSIONS_SUBSTRINGS = [
|
|
73
69
|
".min.js",
|
|
74
70
|
".min.css",
|
|
@@ -84,51 +80,148 @@ function shouldIgnoreFile(filePath) {
|
|
|
84
80
|
}
|
|
85
81
|
return false;
|
|
86
82
|
}
|
|
87
|
-
|
|
83
|
+
|
|
84
|
+
// src/utils/find-fallback.ts
|
|
85
|
+
import { parse } from "@ast-grep/napi";
|
|
86
|
+
import { readdir, readFile, stat as fsStat } from "fs/promises";
|
|
87
|
+
import { join, extname, resolve } from "path";
|
|
88
|
+
var LANG_EXTENSIONS = {
|
|
89
|
+
Html: [".html", ".htm"],
|
|
90
|
+
Css: [".css", ".scss", ".less"]
|
|
91
|
+
};
|
|
92
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
88
93
|
"node_modules",
|
|
94
|
+
".git",
|
|
89
95
|
"dist",
|
|
90
96
|
"build",
|
|
91
|
-
".git",
|
|
92
97
|
".next",
|
|
93
98
|
".turbo",
|
|
94
99
|
"coverage",
|
|
95
100
|
"out"
|
|
96
|
-
];
|
|
101
|
+
]);
|
|
102
|
+
async function findInFilesFallback(lang, config) {
|
|
103
|
+
const langStr = String(lang);
|
|
104
|
+
const extensions = LANG_EXTENSIONS[langStr];
|
|
105
|
+
if (!extensions) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const results = [];
|
|
109
|
+
for (const searchPath of config.paths) {
|
|
110
|
+
const absPath = resolve(searchPath);
|
|
111
|
+
try {
|
|
112
|
+
const pathStat = await fsStat(absPath);
|
|
113
|
+
if (pathStat.isFile()) {
|
|
114
|
+
const ext = extname(absPath).toLowerCase();
|
|
115
|
+
if (extensions.some((e) => ext === e)) {
|
|
116
|
+
await processFile(absPath, lang, config.matcher, results);
|
|
117
|
+
}
|
|
118
|
+
} else if (pathStat.isDirectory()) {
|
|
119
|
+
await walkDir(absPath, lang, extensions, config.matcher, results);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
async function processFile(filePath, lang, matcher, results) {
|
|
128
|
+
if (shouldIgnoreFile(filePath)) return;
|
|
129
|
+
let source;
|
|
130
|
+
try {
|
|
131
|
+
source = await readFile(filePath, "utf-8");
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let ast;
|
|
136
|
+
try {
|
|
137
|
+
ast = parse(lang, source);
|
|
138
|
+
} catch {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const nodes = [...ast.root().findAll(matcher)];
|
|
143
|
+
if (nodes.length > 0) {
|
|
144
|
+
results.push({ file: filePath, nodes });
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function walkDir(dirPath, lang, extensions, matcher, results) {
|
|
151
|
+
let entries;
|
|
152
|
+
try {
|
|
153
|
+
entries = await readdir(dirPath, { withFileTypes: true });
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const fullPath = join(dirPath, entry.name);
|
|
159
|
+
if (entry.isDirectory()) {
|
|
160
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
161
|
+
if (entry.name.startsWith(".")) continue;
|
|
162
|
+
await walkDir(fullPath, lang, extensions, matcher, results);
|
|
163
|
+
} else if (entry.isFile()) {
|
|
164
|
+
const ext = extname(entry.name).toLowerCase();
|
|
165
|
+
if (extensions.includes(ext)) {
|
|
166
|
+
await processFile(fullPath, lang, matcher, results);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
97
171
|
|
|
98
172
|
// src/tools/find.ts
|
|
173
|
+
function isHtmlCssLang(lang) {
|
|
174
|
+
const langStr = String(lang);
|
|
175
|
+
return langStr === "Html" || langStr === "Css";
|
|
176
|
+
}
|
|
99
177
|
var MULTI_META_RE = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
|
|
100
178
|
var SINGLE_META_RE = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
|
|
101
179
|
async function findPattern(options) {
|
|
102
180
|
const startTime = Date.now();
|
|
103
181
|
const warnings = [];
|
|
104
182
|
const cwd = options.cwd ?? process.cwd();
|
|
105
|
-
|
|
106
|
-
if (
|
|
183
|
+
let lang;
|
|
184
|
+
if (options.lang) {
|
|
185
|
+
lang = resolveLang(options.lang);
|
|
186
|
+
if (!lang) {
|
|
187
|
+
warnings.push(`Linguagem "${options.lang}" nao reconhecida. Usando autodeteccao...`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const searchPath = resolve2(cwd, options.path ?? ".");
|
|
191
|
+
try {
|
|
192
|
+
await stat(searchPath);
|
|
193
|
+
} catch {
|
|
194
|
+
warnings.push(`Path nao encontrado: ${searchPath}. Usando diretorio atual.`);
|
|
107
195
|
throw new LanguageNotDetectedError(options.lang ?? "desconhecida");
|
|
108
196
|
}
|
|
109
|
-
validatePattern(options.pattern, lang);
|
|
110
|
-
const searchDir = resolve(cwd, options.path ?? ".");
|
|
111
|
-
const files = await findCodeFiles(searchDir, cwd, lang);
|
|
112
|
-
warnings.push(...files.warnings);
|
|
113
197
|
const metaVars = extractMetaVarInfo(options.pattern);
|
|
198
|
+
const napiConfig = {
|
|
199
|
+
rule: { pattern: options.pattern }
|
|
200
|
+
};
|
|
201
|
+
const findConfig = {
|
|
202
|
+
paths: [searchPath],
|
|
203
|
+
matcher: napiConfig
|
|
204
|
+
};
|
|
205
|
+
if (lang) {
|
|
206
|
+
findConfig.languageGlobs = langToGlobs(lang);
|
|
207
|
+
}
|
|
208
|
+
const langForSearch = lang ?? resolveLang("typescript");
|
|
114
209
|
const matches = [];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const foundNodes = root.findAll(options.pattern);
|
|
121
|
-
for (const node of foundNodes) {
|
|
210
|
+
const useFallback = lang !== void 0 && isHtmlCssLang(lang);
|
|
211
|
+
try {
|
|
212
|
+
const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper(langForSearch, findConfig);
|
|
213
|
+
for (const { file, nodes } of fileResults) {
|
|
214
|
+
for (const node of nodes) {
|
|
122
215
|
const range = node.range();
|
|
216
|
+
const text = node.text();
|
|
123
217
|
const match = {
|
|
124
|
-
file: relative(cwd,
|
|
218
|
+
file: relative(cwd, file).replace(/\\/g, "/"),
|
|
125
219
|
line: range.start.line,
|
|
126
220
|
column: range.start.column,
|
|
127
221
|
endLine: range.end.line,
|
|
128
222
|
endColumn: range.end.column,
|
|
129
|
-
text
|
|
130
|
-
metaVariables: {}
|
|
131
|
-
context: extractContextLines(source, range.start.line, 2)
|
|
223
|
+
text,
|
|
224
|
+
metaVariables: {}
|
|
132
225
|
};
|
|
133
226
|
for (const { name, isMulti } of metaVars) {
|
|
134
227
|
if (isMulti) {
|
|
@@ -145,19 +238,23 @@ async function findPattern(options) {
|
|
|
145
238
|
}
|
|
146
239
|
matches.push(match);
|
|
147
240
|
}
|
|
148
|
-
} catch {
|
|
149
|
-
continue;
|
|
150
241
|
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
244
|
+
if (lang) {
|
|
245
|
+
throw new PatternParseError(options.pattern, lang);
|
|
246
|
+
}
|
|
247
|
+
throw new Error(`Erro na busca: ${msg}`);
|
|
151
248
|
}
|
|
152
|
-
const
|
|
249
|
+
const filesWithMatchesSet = new Set(matches.map((m) => m.file));
|
|
153
250
|
const executionTimeMs = Date.now() - startTime;
|
|
154
251
|
return {
|
|
155
252
|
result: {
|
|
156
253
|
pattern: options.pattern,
|
|
157
|
-
lang,
|
|
254
|
+
lang: langForSearch,
|
|
158
255
|
matches,
|
|
159
|
-
filesScanned:
|
|
160
|
-
filesWithMatches:
|
|
256
|
+
filesScanned: matches.length > 0 ? filesWithMatchesSet.size : 0,
|
|
257
|
+
filesWithMatches: filesWithMatchesSet.size,
|
|
161
258
|
executionTimeMs
|
|
162
259
|
},
|
|
163
260
|
warnings
|
|
@@ -185,92 +282,56 @@ function extractMetaVarInfo(pattern) {
|
|
|
185
282
|
}
|
|
186
283
|
return result;
|
|
187
284
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
if (options.path) {
|
|
204
|
-
const ext = extname(options.path).toLowerCase();
|
|
205
|
-
if (ext && isSupportedExtension(options.path)) {
|
|
206
|
-
const detected = detectLangFromFile(options.path);
|
|
207
|
-
if (detected) return detected;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return resolveLang("typescript");
|
|
285
|
+
var IGNORED_DIR_PATTERNS = [
|
|
286
|
+
/[/\\]node_modules[/\\]/,
|
|
287
|
+
/[/\\]dist[/\\]/,
|
|
288
|
+
/[/\\]build[/\\]/,
|
|
289
|
+
/[/\\]\.git[/\\]/,
|
|
290
|
+
/[/\\]\.next[/\\]/,
|
|
291
|
+
/[/\\]\.turbo[/\\]/,
|
|
292
|
+
/[/\\]coverage[/\\]/,
|
|
293
|
+
/[/\\]out[/\\]/
|
|
294
|
+
];
|
|
295
|
+
function isIgnoredPath(filePath) {
|
|
296
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
297
|
+
const wrapped = `/${normalized}/`;
|
|
298
|
+
return IGNORED_DIR_PATTERNS.some((re) => re.test(wrapped));
|
|
211
299
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
300
|
+
async function findInFilesWrapper(lang, config) {
|
|
301
|
+
const results = [];
|
|
302
|
+
let callbacksReceived = 0;
|
|
303
|
+
const totalFiles = await findInFiles(
|
|
304
|
+
lang,
|
|
305
|
+
config,
|
|
306
|
+
(err, nodes) => {
|
|
307
|
+
callbacksReceived++;
|
|
308
|
+
if (err || nodes.length === 0) return;
|
|
309
|
+
const file = nodes[0].getRoot()?.filename() ?? "unknown";
|
|
310
|
+
if (isIgnoredPath(file)) return;
|
|
311
|
+
results.push({ file, nodes });
|
|
220
312
|
}
|
|
313
|
+
);
|
|
314
|
+
while (callbacksReceived < totalFiles) {
|
|
315
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
221
316
|
}
|
|
317
|
+
return results;
|
|
222
318
|
}
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
warnings.push(`Path nao encontrado: ${searchDir}. Usando diretorio atual.`);
|
|
239
|
-
return { paths: [], warnings };
|
|
240
|
-
}
|
|
241
|
-
const paths = [];
|
|
242
|
-
async function walk(dir) {
|
|
243
|
-
try {
|
|
244
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
245
|
-
for (const entry of entries) {
|
|
246
|
-
const fullPath = join(dir, entry.name);
|
|
247
|
-
if (entry.isDirectory()) {
|
|
248
|
-
if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
await walk(fullPath);
|
|
252
|
-
} else if (entry.isFile()) {
|
|
253
|
-
if (shouldIgnoreFile(fullPath)) continue;
|
|
254
|
-
if (!isSupportedExtension(fullPath)) continue;
|
|
255
|
-
paths.push(fullPath);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
} catch {
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
await walk(searchDir);
|
|
262
|
-
if (paths.length > 5e3) {
|
|
263
|
-
warnings.push(
|
|
264
|
-
`Muitos arquivos encontrados (${paths.length}). Limitando a 5000. Use --path para focar em um diretorio.`
|
|
265
|
-
);
|
|
266
|
-
return { paths: paths.slice(0, 5e3), warnings };
|
|
267
|
-
}
|
|
268
|
-
if (paths.length === 0) {
|
|
269
|
-
warnings.push(
|
|
270
|
-
`Nenhum arquivo de codigo encontrado em: ${relative(_cwd, searchDir)}`
|
|
271
|
-
);
|
|
319
|
+
function langToGlobs(lang) {
|
|
320
|
+
const langStr = String(lang);
|
|
321
|
+
switch (langStr) {
|
|
322
|
+
case "TypeScript":
|
|
323
|
+
return ["*.ts"];
|
|
324
|
+
case "JavaScript":
|
|
325
|
+
return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
|
|
326
|
+
case "Tsx":
|
|
327
|
+
return ["*.tsx"];
|
|
328
|
+
case "Css":
|
|
329
|
+
return ["*.css", "*.scss", "*.less"];
|
|
330
|
+
case "Html":
|
|
331
|
+
return ["*.html", "*.htm"];
|
|
332
|
+
default:
|
|
333
|
+
return [];
|
|
272
334
|
}
|
|
273
|
-
return { paths, warnings };
|
|
274
335
|
}
|
|
275
336
|
var PatternParseError = class extends Error {
|
|
276
337
|
pattern;
|
|
@@ -293,8 +354,8 @@ var LanguageNotDetectedError = class extends Error {
|
|
|
293
354
|
|
|
294
355
|
// src/tools/tree.ts
|
|
295
356
|
import { parse as parse2 } from "@ast-grep/napi";
|
|
296
|
-
import { readFile as readFile2, stat } from "fs/promises";
|
|
297
|
-
import { resolve as
|
|
357
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
358
|
+
import { resolve as resolve3, relative as relative2, extname as extname2 } from "path";
|
|
298
359
|
async function exploreTree(options) {
|
|
299
360
|
const warnings = [];
|
|
300
361
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -313,9 +374,9 @@ async function exploreTree(options) {
|
|
|
313
374
|
detectedLang = resolveLang("typescript");
|
|
314
375
|
}
|
|
315
376
|
} else if (options.path) {
|
|
316
|
-
filePath =
|
|
377
|
+
filePath = resolve3(cwd, options.path);
|
|
317
378
|
try {
|
|
318
|
-
await
|
|
379
|
+
await stat2(filePath);
|
|
319
380
|
} catch {
|
|
320
381
|
throw new FileNotFoundError(filePath);
|
|
321
382
|
}
|
|
@@ -393,9 +454,13 @@ var TreeParseError = class extends Error {
|
|
|
393
454
|
};
|
|
394
455
|
|
|
395
456
|
// src/tools/replace.ts
|
|
396
|
-
import {
|
|
397
|
-
import {
|
|
398
|
-
import {
|
|
457
|
+
import { findInFiles as findInFiles2 } from "@ast-grep/napi";
|
|
458
|
+
import { resolve as resolve4, relative as relative3 } from "path";
|
|
459
|
+
import { stat as stat3 } from "fs/promises";
|
|
460
|
+
function isHtmlCssLang2(lang) {
|
|
461
|
+
const langStr = String(lang);
|
|
462
|
+
return langStr === "Html" || langStr === "Css";
|
|
463
|
+
}
|
|
399
464
|
var MULTI_META_RE2 = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
|
|
400
465
|
var SINGLE_META_RE2 = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
|
|
401
466
|
async function replacePreview(options) {
|
|
@@ -418,18 +483,29 @@ async function replacePreview(options) {
|
|
|
418
483
|
} else {
|
|
419
484
|
lang = resolveLang("typescript");
|
|
420
485
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
486
|
+
const searchPath = resolve4(cwd, options.path ?? ".");
|
|
487
|
+
try {
|
|
488
|
+
await stat3(searchPath);
|
|
489
|
+
} catch {
|
|
490
|
+
throw new ReplaceLangError(
|
|
491
|
+
`Path nao encontrado: ${searchPath}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
424
494
|
const metaVars = extractMetaVarInfo2(options.pattern);
|
|
495
|
+
const napiConfig = {
|
|
496
|
+
rule: { pattern: options.pattern }
|
|
497
|
+
};
|
|
498
|
+
const findConfig = {
|
|
499
|
+
paths: [searchPath],
|
|
500
|
+
matcher: napiConfig,
|
|
501
|
+
languageGlobs: langToGlobs2(lang)
|
|
502
|
+
};
|
|
425
503
|
const changes = [];
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
const foundNodes = root.findAll(options.pattern);
|
|
432
|
-
for (const node of foundNodes) {
|
|
504
|
+
const useFallback = isHtmlCssLang2(lang);
|
|
505
|
+
try {
|
|
506
|
+
const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper2(lang, findConfig);
|
|
507
|
+
for (const { file, nodes } of fileResults) {
|
|
508
|
+
for (const node of nodes) {
|
|
433
509
|
const metaVarValues = {};
|
|
434
510
|
for (const { name, isMulti } of metaVars) {
|
|
435
511
|
if (isMulti) {
|
|
@@ -447,22 +523,22 @@ async function replacePreview(options) {
|
|
|
447
523
|
let interpolated = options.rewrite;
|
|
448
524
|
for (const [name, value] of Object.entries(metaVarValues)) {
|
|
449
525
|
interpolated = interpolated.replaceAll(
|
|
450
|
-
new RegExp(
|
|
526
|
+
new RegExp(`\\$+${name}\\b`, "g"),
|
|
451
527
|
value
|
|
452
528
|
);
|
|
453
529
|
}
|
|
454
530
|
const range = node.range();
|
|
455
531
|
changes.push({
|
|
456
|
-
file: relative3(cwd,
|
|
532
|
+
file: relative3(cwd, file).replace(/\\/g, "/"),
|
|
457
533
|
line: range.start.line,
|
|
458
534
|
column: range.start.column,
|
|
459
535
|
original: node.text(),
|
|
460
536
|
replacement: interpolated
|
|
461
537
|
});
|
|
462
538
|
}
|
|
463
|
-
} catch {
|
|
464
|
-
continue;
|
|
465
539
|
}
|
|
540
|
+
} catch {
|
|
541
|
+
throw new ReplacePatternError(options.pattern, lang);
|
|
466
542
|
}
|
|
467
543
|
const uniqueFiles = new Set(changes.map((c) => c.file));
|
|
468
544
|
return {
|
|
@@ -471,7 +547,7 @@ async function replacePreview(options) {
|
|
|
471
547
|
rewrite: options.rewrite,
|
|
472
548
|
lang,
|
|
473
549
|
changes,
|
|
474
|
-
filesScanned:
|
|
550
|
+
filesScanned: changes.length > 0 ? uniqueFiles.size : 0,
|
|
475
551
|
filesAffected: uniqueFiles.size
|
|
476
552
|
},
|
|
477
553
|
warnings
|
|
@@ -499,55 +575,56 @@ function extractMetaVarInfo2(pattern) {
|
|
|
499
575
|
}
|
|
500
576
|
return result;
|
|
501
577
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
578
|
+
var IGNORED_DIR_PATTERNS2 = [
|
|
579
|
+
/[/\\]node_modules[/\\]/,
|
|
580
|
+
/[/\\]dist[/\\]/,
|
|
581
|
+
/[/\\]build[/\\]/,
|
|
582
|
+
/[/\\]\.git[/\\]/,
|
|
583
|
+
/[/\\]\.next[/\\]/,
|
|
584
|
+
/[/\\]\.turbo[/\\]/,
|
|
585
|
+
/[/\\]coverage[/\\]/,
|
|
586
|
+
/[/\\]out[/\\]/
|
|
587
|
+
];
|
|
588
|
+
function isIgnoredPath2(filePath) {
|
|
589
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
590
|
+
const wrapped = `/${normalized}/`;
|
|
591
|
+
return IGNORED_DIR_PATTERNS2.some((re) => re.test(wrapped));
|
|
512
592
|
}
|
|
513
|
-
async function
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
try {
|
|
526
|
-
const entries = await readdir2(dir, { withFileTypes: true });
|
|
527
|
-
for (const entry of entries) {
|
|
528
|
-
const fullPath = join2(dir, entry.name);
|
|
529
|
-
if (entry.isDirectory()) {
|
|
530
|
-
if (IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) {
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
await walk(fullPath);
|
|
534
|
-
} else if (entry.isFile()) {
|
|
535
|
-
if (shouldIgnoreFile(fullPath)) continue;
|
|
536
|
-
if (!isSupportedExtension(fullPath)) continue;
|
|
537
|
-
paths.push(fullPath);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} catch {
|
|
593
|
+
async function findInFilesWrapper2(lang, config) {
|
|
594
|
+
const results = [];
|
|
595
|
+
let callbacksReceived = 0;
|
|
596
|
+
const totalFiles = await findInFiles2(
|
|
597
|
+
lang,
|
|
598
|
+
config,
|
|
599
|
+
(err, nodes) => {
|
|
600
|
+
callbacksReceived++;
|
|
601
|
+
if (err || nodes.length === 0) return;
|
|
602
|
+
const file = nodes[0].getRoot()?.filename() ?? "unknown";
|
|
603
|
+
if (isIgnoredPath2(file)) return;
|
|
604
|
+
results.push({ file, nodes });
|
|
541
605
|
}
|
|
606
|
+
);
|
|
607
|
+
while (callbacksReceived < totalFiles) {
|
|
608
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
542
609
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
610
|
+
return results;
|
|
611
|
+
}
|
|
612
|
+
function langToGlobs2(lang) {
|
|
613
|
+
const langStr = String(lang);
|
|
614
|
+
switch (langStr) {
|
|
615
|
+
case "TypeScript":
|
|
616
|
+
return ["*.ts"];
|
|
617
|
+
case "JavaScript":
|
|
618
|
+
return ["*.js", "*.jsx", "*.mjs", "*.cjs"];
|
|
619
|
+
case "Tsx":
|
|
620
|
+
return ["*.tsx"];
|
|
621
|
+
case "Css":
|
|
622
|
+
return ["*.css", "*.scss", "*.less"];
|
|
623
|
+
case "Html":
|
|
624
|
+
return ["*.html", "*.htm"];
|
|
625
|
+
default:
|
|
626
|
+
return [];
|
|
549
627
|
}
|
|
550
|
-
return { paths, warnings };
|
|
551
628
|
}
|
|
552
629
|
var ReplacePatternError = class extends Error {
|
|
553
630
|
pattern;
|
|
@@ -1052,9 +1129,17 @@ metavariaveis ($VAR, $$VAR, $$$VARS) como curingas que capturam partes dinamicas
|
|
|
1052
1129
|
\u{1F4E6} LINGUAGENS SUPORTADAS (apenas 5):
|
|
1053
1130
|
\u2022 typescript (.ts) \u2014 apenas .ts puro (sem JSX)
|
|
1054
1131
|
\u2022 javascript (.js, .jsx, .mjs, .cjs)
|
|
1055
|
-
\u2022 tsx (.tsx) \u2014 .tsx COM JSX/React
|
|
1056
|
-
\u2022 css (.css, .scss, .less)
|
|
1057
|
-
\u2022 html (.html, .htm)
|
|
1132
|
+
\u2022 tsx (.tsx) \u2014 .tsx COM JSX/React
|
|
1133
|
+
\u2022 css (.css, .scss, .less) \u26A0\uFE0F Motor usa fallback manual (findInFiles napi nao suporta nativamente)
|
|
1134
|
+
\u2022 html (.html, .htm) \u26A0\uFE0F Motor usa fallback manual (findInFiles napi nao suporta nativamente)
|
|
1135
|
+
|
|
1136
|
+
\u{1F916} AUTODETECCAO: Se o parametro 'lang' NAO for especificado, o motor
|
|
1137
|
+
detecta a linguagem automaticamente pela extensao de cada arquivo.
|
|
1138
|
+
Voce pode buscar em um diretorio com .ts e .tsx misturados \u2014 o motor
|
|
1139
|
+
usa o parser correto para cada arquivo.
|
|
1140
|
+
|
|
1141
|
+
Use 'lang' apenas quando quiser RESTRINGIR a busca a uma linguagem
|
|
1142
|
+
especifica (ex: forcar TSX em todo o diretorio).
|
|
1058
1143
|
|
|
1059
1144
|
\u26A0\uFE0F LIMITACAO IMPORTANTE: Apenas busca SINTAXICA. Nao resolve imports, nao
|
|
1060
1145
|
entende tipos, nao segue referencias entre arquivos, nao faz analise de fluxo.
|
|
@@ -1085,12 +1170,30 @@ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
|
|
|
1085
1170
|
\u{1F4DD} EXEMPLOS DE PATTERNS:
|
|
1086
1171
|
\u2022 console.$METHOD($MSG) \u2192 captura metodo e 1 argumento
|
|
1087
1172
|
\u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e todos os argumentos
|
|
1088
|
-
\u2022
|
|
1173
|
+
\u2022 $FUNC($$$ARGS) \u2192 captura qualquer chamada de funcao (use para call_expression)
|
|
1089
1174
|
\u2022 import { $$$ITEMS } from '$MODULE' \u2192 captura imports
|
|
1090
|
-
\u2022 const $VAR = $VALUE \u2192 captura declaracoes
|
|
1175
|
+
\u2022 const $VAR = $VALUE \u2192 captura declaracoes simples
|
|
1176
|
+
\u2022 const $VAR: $TYPE = $VALUE \u2192 captura declaracoes com tipo (sempre inclua o =)
|
|
1091
1177
|
\u2022 try { $$$ } catch ($ERR) { $$$ } \u2192 captura try-catch
|
|
1092
1178
|
\u2022 await $FUNC($$$ARGS) \u2192 captura chamadas assincronas
|
|
1093
1179
|
|
|
1180
|
+
\u26A0\uFE0F PATTERNS PARA FUNCOES TYPESCRIPT (IMPORTANTE):
|
|
1181
|
+
Funcoes TS frequentemente tem tipo de retorno (: Type) como no nomeado extra.
|
|
1182
|
+
Se o pattern nao incluir o tipo de retorno, o match FALHA. Use:
|
|
1183
|
+
\u2705 function $NAME($$$PARAMS): $_RET { $$$BODY }
|
|
1184
|
+
\u2705 function $NAME($$$PARAMS): $_RET_TYPE { $$$BODY }
|
|
1185
|
+
\u2705 const $NAME = ($PARAM: $TYPE): $_RET => { $$$BODY }
|
|
1186
|
+
\u274C function $NAME($$$PARAMS) { $$$BODY } \u2014 falha se houver tipo de retorno
|
|
1187
|
+
O $_RET (com underscore) age como "coringa nao-capturador" para o tipo de retorno.
|
|
1188
|
+
|
|
1189
|
+
\u26A0\uFE0F PATTERNS CSS PRECISAM DE CONTEXTO:
|
|
1190
|
+
Padroes CSS sem contexto sao ambiguos para o parser. Use:
|
|
1191
|
+
\u2705 $SELECTOR { $$$ } \u2014 captura qualquer regra CSS completa
|
|
1192
|
+
\u2705 $PROP: $VALUE \u2014 captura propriedade (dentro de uma regra)
|
|
1193
|
+
\u2705 * { $$$ } \u2014 qualquer regra CSS
|
|
1194
|
+
\u274C .className { $$$ } \u2014 falha (ponto no inicio e ambiguo)
|
|
1195
|
+
\u274C margin: 0 \u2014 sem contexto, use $PROP: $VALUE
|
|
1196
|
+
|
|
1094
1197
|
\u{1F524} METAVARIAVEIS:
|
|
1095
1198
|
\u2022 $VAR \u2192 captura UM no nomeado (ex: um identificador, uma string, um numero)
|
|
1096
1199
|
\u2022 $$VAR \u2192 captura UM no (incluindo nos nao-nomeados como operadores +, -)
|
|
@@ -1109,7 +1212,7 @@ Workflow recomendado: supergrep_find \u2192 supergrep_tree \u2192 supergrep_repl
|
|
|
1109
1212
|
"Padrao AST Grep na linguagem alvo. Ex: 'console.$METHOD($$$ARGS)', 'function $NAME($$$PARAMS) { $$$BODY }'"
|
|
1110
1213
|
),
|
|
1111
1214
|
lang: z.string().optional().describe(
|
|
1112
|
-
"Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html).
|
|
1215
|
+
"Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo. Para HTML/CSS, o motor usa fallback parse direto (findInFiles nao suporta nativamente)."
|
|
1113
1216
|
),
|
|
1114
1217
|
path: z.string().optional().describe(
|
|
1115
1218
|
"Arquivo ou diretorio onde buscar. Default: diretorio atual. Ex: 'src/', 'app/page.tsx'"
|
|
@@ -1168,6 +1271,10 @@ Cada estrutura de codigo vira um kind diferente na AST.
|
|
|
1168
1271
|
\u{1F4A1} Como usar os kinds: ao ver call_expression no tree, voce pode buscar
|
|
1169
1272
|
chamadas de funcao com o pattern: $FUNC($$$ARGS)
|
|
1170
1273
|
|
|
1274
|
+
\u{1F916} AUTODETECCAO: se usar 'path', a linguagem e detectada automaticamente
|
|
1275
|
+
pela extensao do arquivo. O parametro 'lang' so e necessario ao usar
|
|
1276
|
+
'code' (snippet inline) ou para forcar uma linguagem especifica.
|
|
1277
|
+
|
|
1171
1278
|
QUANDO USAR:
|
|
1172
1279
|
- "Quais tipos de nos existem neste arquivo TypeScript?"
|
|
1173
1280
|
- "Como escrever um pattern para capturar certas estruturas?"
|
|
@@ -1244,6 +1351,10 @@ apos a substituicao. Util para planejar refatoracoes em larga escala.
|
|
|
1244
1351
|
O supergrep faz interpolacao manual: textos dos nos sao concatenados
|
|
1245
1352
|
com ", " e injetados no lugar do $VAR no rewrite.
|
|
1246
1353
|
|
|
1354
|
+
\u{1F916} AUTODETECCAO DE LINGUAGEM: se 'lang' nao for especificado, o motor
|
|
1355
|
+
detecta automaticamente pela extensao de cada arquivo. Use 'lang' apenas
|
|
1356
|
+
para RESTRINGIR a busca a uma linguagem especifica.
|
|
1357
|
+
|
|
1247
1358
|
QUANDO USAR:
|
|
1248
1359
|
- "Como ficaria se eu trocasse console.log por logger.info?"
|
|
1249
1360
|
- "Preview de substituir var por let em todo o projeto"
|
|
@@ -1264,7 +1375,7 @@ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudan
|
|
|
1264
1375
|
"Texto de substituicao. Use $VAR para referenciar metavariaveis capturadas. Ex: 'logger.info($$$ARGS)'"
|
|
1265
1376
|
),
|
|
1266
1377
|
lang: z.string().optional().describe(
|
|
1267
|
-
"Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'.
|
|
1378
|
+
"Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo. Para HTML/CSS, o motor usa fallback parse direto (findInFiles nao suporta nativamente)."
|
|
1268
1379
|
),
|
|
1269
1380
|
path: z.string().optional().describe(
|
|
1270
1381
|
"Arquivo ou diretorio onde buscar. Default: diretorio atual."
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startMcpServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LUKDYG3I.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
var require2 = createRequire(import.meta.url);
|
|
8
|
+
var pkg = require2("../package.json");
|
|
9
|
+
var VERSION = pkg.version;
|
|
7
10
|
export {
|
|
8
11
|
VERSION,
|
|
9
12
|
startMcpServer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/supergrep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP server para busca estrutural de código com AST Grep. Encontra padrões de código com precisão sintática, ignorando formatação e comentários. Suporta 20+ linguagens via Tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast-grep",
|