@justmpm/supergrep 0.2.0 → 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-5PJJZ4CK.js → chunk-LUKDYG3I.js} +151 -20
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
|
|
8
8
|
// src/tools/find.ts
|
|
9
9
|
import { findInFiles } from "@ast-grep/napi";
|
|
10
|
-
import { relative, resolve } from "path";
|
|
10
|
+
import { relative, resolve as resolve2 } from "path";
|
|
11
11
|
import { stat } from "fs/promises";
|
|
12
12
|
|
|
13
13
|
// src/utils/lang.ts
|
|
@@ -65,8 +65,115 @@ function langName(lang) {
|
|
|
65
65
|
};
|
|
66
66
|
return friendly[langStr] ?? langStr;
|
|
67
67
|
}
|
|
68
|
+
var IGNORED_EXTENSIONS_SUBSTRINGS = [
|
|
69
|
+
".min.js",
|
|
70
|
+
".min.css",
|
|
71
|
+
".bundle.js",
|
|
72
|
+
".chunk.js",
|
|
73
|
+
".map",
|
|
74
|
+
".d.ts"
|
|
75
|
+
];
|
|
76
|
+
function shouldIgnoreFile(filePath) {
|
|
77
|
+
const lower = filePath.toLowerCase();
|
|
78
|
+
for (const ext of IGNORED_EXTENSIONS_SUBSTRINGS) {
|
|
79
|
+
if (lower.includes(ext)) return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
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([
|
|
93
|
+
"node_modules",
|
|
94
|
+
".git",
|
|
95
|
+
"dist",
|
|
96
|
+
"build",
|
|
97
|
+
".next",
|
|
98
|
+
".turbo",
|
|
99
|
+
"coverage",
|
|
100
|
+
"out"
|
|
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
|
+
}
|
|
68
171
|
|
|
69
172
|
// src/tools/find.ts
|
|
173
|
+
function isHtmlCssLang(lang) {
|
|
174
|
+
const langStr = String(lang);
|
|
175
|
+
return langStr === "Html" || langStr === "Css";
|
|
176
|
+
}
|
|
70
177
|
var MULTI_META_RE = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
|
|
71
178
|
var SINGLE_META_RE = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
|
|
72
179
|
async function findPattern(options) {
|
|
@@ -80,7 +187,7 @@ async function findPattern(options) {
|
|
|
80
187
|
warnings.push(`Linguagem "${options.lang}" nao reconhecida. Usando autodeteccao...`);
|
|
81
188
|
}
|
|
82
189
|
}
|
|
83
|
-
const searchPath =
|
|
190
|
+
const searchPath = resolve2(cwd, options.path ?? ".");
|
|
84
191
|
try {
|
|
85
192
|
await stat(searchPath);
|
|
86
193
|
} catch {
|
|
@@ -100,8 +207,9 @@ async function findPattern(options) {
|
|
|
100
207
|
}
|
|
101
208
|
const langForSearch = lang ?? resolveLang("typescript");
|
|
102
209
|
const matches = [];
|
|
210
|
+
const useFallback = lang !== void 0 && isHtmlCssLang(lang);
|
|
103
211
|
try {
|
|
104
|
-
const fileResults = await findInFilesWrapper(langForSearch, findConfig);
|
|
212
|
+
const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper(langForSearch, findConfig);
|
|
105
213
|
for (const { file, nodes } of fileResults) {
|
|
106
214
|
for (const node of nodes) {
|
|
107
215
|
const range = node.range();
|
|
@@ -245,9 +353,9 @@ var LanguageNotDetectedError = class extends Error {
|
|
|
245
353
|
};
|
|
246
354
|
|
|
247
355
|
// src/tools/tree.ts
|
|
248
|
-
import { parse } from "@ast-grep/napi";
|
|
249
|
-
import { readFile, stat as stat2 } from "fs/promises";
|
|
250
|
-
import { resolve as
|
|
356
|
+
import { parse as parse2 } from "@ast-grep/napi";
|
|
357
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
358
|
+
import { resolve as resolve3, relative as relative2, extname as extname2 } from "path";
|
|
251
359
|
async function exploreTree(options) {
|
|
252
360
|
const warnings = [];
|
|
253
361
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -266,13 +374,13 @@ async function exploreTree(options) {
|
|
|
266
374
|
detectedLang = resolveLang("typescript");
|
|
267
375
|
}
|
|
268
376
|
} else if (options.path) {
|
|
269
|
-
filePath =
|
|
377
|
+
filePath = resolve3(cwd, options.path);
|
|
270
378
|
try {
|
|
271
379
|
await stat2(filePath);
|
|
272
380
|
} catch {
|
|
273
381
|
throw new FileNotFoundError(filePath);
|
|
274
382
|
}
|
|
275
|
-
source = await
|
|
383
|
+
source = await readFile2(filePath, "utf-8");
|
|
276
384
|
if (options.lang) {
|
|
277
385
|
detectedLang = resolveLang(options.lang);
|
|
278
386
|
if (!detectedLang) {
|
|
@@ -283,7 +391,7 @@ async function exploreTree(options) {
|
|
|
283
391
|
detectedLang = detectLangFromFile(filePath);
|
|
284
392
|
if (!detectedLang) {
|
|
285
393
|
warnings.push(
|
|
286
|
-
`Nao foi possivel detectar linguagem da extensao "${
|
|
394
|
+
`Nao foi possivel detectar linguagem da extensao "${extname2(filePath)}". Usando TypeScript.`
|
|
287
395
|
);
|
|
288
396
|
detectedLang = resolveLang("typescript");
|
|
289
397
|
}
|
|
@@ -294,7 +402,7 @@ async function exploreTree(options) {
|
|
|
294
402
|
const lang = detectedLang;
|
|
295
403
|
let ast;
|
|
296
404
|
try {
|
|
297
|
-
ast =
|
|
405
|
+
ast = parse2(lang, source);
|
|
298
406
|
} catch (parseErr) {
|
|
299
407
|
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
300
408
|
throw new TreeParseError(filePath, lang, msg);
|
|
@@ -347,8 +455,12 @@ var TreeParseError = class extends Error {
|
|
|
347
455
|
|
|
348
456
|
// src/tools/replace.ts
|
|
349
457
|
import { findInFiles as findInFiles2 } from "@ast-grep/napi";
|
|
350
|
-
import { resolve as
|
|
458
|
+
import { resolve as resolve4, relative as relative3 } from "path";
|
|
351
459
|
import { stat as stat3 } from "fs/promises";
|
|
460
|
+
function isHtmlCssLang2(lang) {
|
|
461
|
+
const langStr = String(lang);
|
|
462
|
+
return langStr === "Html" || langStr === "Css";
|
|
463
|
+
}
|
|
352
464
|
var MULTI_META_RE2 = /\$\$\$([A-Z_][A-Z0-9_]*)/g;
|
|
353
465
|
var SINGLE_META_RE2 = /(?<!\$)\$(?:\$\$)?([A-Z_][A-Z0-9_]*)/g;
|
|
354
466
|
async function replacePreview(options) {
|
|
@@ -371,7 +483,7 @@ async function replacePreview(options) {
|
|
|
371
483
|
} else {
|
|
372
484
|
lang = resolveLang("typescript");
|
|
373
485
|
}
|
|
374
|
-
const searchPath =
|
|
486
|
+
const searchPath = resolve4(cwd, options.path ?? ".");
|
|
375
487
|
try {
|
|
376
488
|
await stat3(searchPath);
|
|
377
489
|
} catch {
|
|
@@ -389,8 +501,9 @@ async function replacePreview(options) {
|
|
|
389
501
|
languageGlobs: langToGlobs2(lang)
|
|
390
502
|
};
|
|
391
503
|
const changes = [];
|
|
504
|
+
const useFallback = isHtmlCssLang2(lang);
|
|
392
505
|
try {
|
|
393
|
-
const fileResults = await findInFilesWrapper2(lang, findConfig);
|
|
506
|
+
const fileResults = useFallback ? await findInFilesFallback(lang, { paths: [searchPath], matcher: napiConfig }) : await findInFilesWrapper2(lang, findConfig);
|
|
394
507
|
for (const { file, nodes } of fileResults) {
|
|
395
508
|
for (const node of nodes) {
|
|
396
509
|
const metaVarValues = {};
|
|
@@ -410,7 +523,7 @@ async function replacePreview(options) {
|
|
|
410
523
|
let interpolated = options.rewrite;
|
|
411
524
|
for (const [name, value] of Object.entries(metaVarValues)) {
|
|
412
525
|
interpolated = interpolated.replaceAll(
|
|
413
|
-
new RegExp(
|
|
526
|
+
new RegExp(`\\$+${name}\\b`, "g"),
|
|
414
527
|
value
|
|
415
528
|
);
|
|
416
529
|
}
|
|
@@ -1017,8 +1130,8 @@ metavariaveis ($VAR, $$VAR, $$$VARS) como curingas que capturam partes dinamicas
|
|
|
1017
1130
|
\u2022 typescript (.ts) \u2014 apenas .ts puro (sem JSX)
|
|
1018
1131
|
\u2022 javascript (.js, .jsx, .mjs, .cjs)
|
|
1019
1132
|
\u2022 tsx (.tsx) \u2014 .tsx COM JSX/React
|
|
1020
|
-
\u2022 css (.css, .scss, .less)
|
|
1021
|
-
\u2022 html (.html, .htm)
|
|
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)
|
|
1022
1135
|
|
|
1023
1136
|
\u{1F916} AUTODETECCAO: Se o parametro 'lang' NAO for especificado, o motor
|
|
1024
1137
|
detecta a linguagem automaticamente pela extensao de cada arquivo.
|
|
@@ -1057,12 +1170,30 @@ Cada arquivo e analisado isoladamente. Para analise semantica, use ai-tool.
|
|
|
1057
1170
|
\u{1F4DD} EXEMPLOS DE PATTERNS:
|
|
1058
1171
|
\u2022 console.$METHOD($MSG) \u2192 captura metodo e 1 argumento
|
|
1059
1172
|
\u2022 console.$METHOD($$$ARGS) \u2192 captura metodo e todos os argumentos
|
|
1060
|
-
\u2022
|
|
1173
|
+
\u2022 $FUNC($$$ARGS) \u2192 captura qualquer chamada de funcao (use para call_expression)
|
|
1061
1174
|
\u2022 import { $$$ITEMS } from '$MODULE' \u2192 captura imports
|
|
1062
|
-
\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 =)
|
|
1063
1177
|
\u2022 try { $$$ } catch ($ERR) { $$$ } \u2192 captura try-catch
|
|
1064
1178
|
\u2022 await $FUNC($$$ARGS) \u2192 captura chamadas assincronas
|
|
1065
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
|
+
|
|
1066
1197
|
\u{1F524} METAVARIAVEIS:
|
|
1067
1198
|
\u2022 $VAR \u2192 captura UM no nomeado (ex: um identificador, uma string, um numero)
|
|
1068
1199
|
\u2022 $$VAR \u2192 captura UM no (incluindo nos nao-nomeados como operadores +, -)
|
|
@@ -1081,7 +1212,7 @@ Workflow recomendado: supergrep_find \u2192 supergrep_tree \u2192 supergrep_repl
|
|
|
1081
1212
|
"Padrao AST Grep na linguagem alvo. Ex: 'console.$METHOD($$$ARGS)', 'function $NAME($$$PARAMS) { $$$BODY }'"
|
|
1082
1213
|
),
|
|
1083
1214
|
lang: z.string().optional().describe(
|
|
1084
|
-
"Linguagem: 'typescript' (.ts), 'javascript' (.js), 'tsx' (.tsx), 'css' (.css), 'html' (.html). Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo."
|
|
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)."
|
|
1085
1216
|
),
|
|
1086
1217
|
path: z.string().optional().describe(
|
|
1087
1218
|
"Arquivo ou diretorio onde buscar. Default: diretorio atual. Ex: 'src/', 'app/page.tsx'"
|
|
@@ -1244,7 +1375,7 @@ Workflow: supergrep_find \u2192 supergrep_replace (preview) \u2192 aplicar mudan
|
|
|
1244
1375
|
"Texto de substituicao. Use $VAR para referenciar metavariaveis capturadas. Ex: 'logger.info($$$ARGS)'"
|
|
1245
1376
|
),
|
|
1246
1377
|
lang: z.string().optional().describe(
|
|
1247
|
-
"Linguagem: 'typescript', 'javascript', 'tsx', 'css', 'html'. Opcional \u2014 se nao especificado, o motor autodetecta pela extensao de cada arquivo."
|
|
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)."
|
|
1248
1379
|
),
|
|
1249
1380
|
path: z.string().optional().describe(
|
|
1250
1381
|
"Arquivo ou diretorio onde buscar. Default: diretorio atual."
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/supergrep",
|
|
3
|
-
"version": "0.2.
|
|
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",
|