Skip to content

Commit b8f5599

Browse files
committed
feat(ast-grep): add helpful hints for incomplete Python patterns
- Show hints when Python class/function patterns return empty results - Detect patterns ending with ':' that need body (class :, def ():) - Removed validation that could cause false positives - Hints only appear on empty results, not on successful matches
1 parent ea2b09e commit b8f5599

File tree

1 file changed

+25
-37
lines changed

1 file changed

+25
-37
lines changed

src/tools/ast-grep/tools.ts

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,25 @@ function showOutputToUser(context: unknown, output: string): void {
1010
ctx.metadata?.({ metadata: { output } })
1111
}
1212

13-
/**
14-
* JS/TS languages that require complete function declaration patterns
15-
*/
16-
const JS_TS_LANGUAGES = ["javascript", "typescript", "tsx"] as const
17-
18-
/**
19-
* Validates AST pattern for common incomplete patterns that will fail silently.
20-
* Only validates JS/TS languages where function declarations require body.
21-
*
22-
* @throws Error with helpful message if pattern is incomplete
23-
*/
24-
function validatePatternForCli(pattern: string, lang: CliLanguage): void {
25-
if (!JS_TS_LANGUAGES.includes(lang as (typeof JS_TS_LANGUAGES)[number])) {
26-
return
27-
}
28-
13+
function getEmptyResultHint(pattern: string, lang: CliLanguage): string | null {
2914
const src = pattern.trim()
3015

31-
// Detect incomplete function declarations:
32-
// - "function $NAME" (no params/body)
33-
// - "export function $NAME" (no params/body)
34-
// - "export async function $NAME" (no params/body)
35-
// - "export default function $NAME" (no params/body)
36-
// Pattern: ends with $METAVAR (uppercase, underscore, digits) without ( or {
37-
const incompleteFunctionDecl =
38-
/^(export\s+)?(default\s+)?(async\s+)?function\s+\$[A-Z_][A-Z0-9_]*\s*$/i.test(src)
16+
if (lang === "python") {
17+
if (src.startsWith("class ") && src.endsWith(":")) {
18+
return `💡 Hint: Python class patterns need body. Try "class $NAME" or include body with $$$BODY`
19+
}
20+
if ((src.startsWith("def ") || src.startsWith("async def ")) && src.endsWith(":")) {
21+
return `💡 Hint: Python function patterns need body. Try "def $FUNC($$$):\\n $$$BODY"`
22+
}
23+
}
3924

40-
if (incompleteFunctionDecl) {
41-
throw new Error(
42-
`Incomplete AST pattern for ${lang}: "${pattern}"\n\n` +
43-
`ast-grep requires complete AST nodes. Function declarations must include parameters and body.\n\n` +
44-
`Examples of correct patterns:\n` +
45-
` - "export async function $NAME($$$) { $$$ }" (matches export async functions)\n` +
46-
` - "function $NAME($$$) { $$$ }" (matches all function declarations)\n` +
47-
` - "async function $NAME($$$) { $$$ }" (matches async functions)\n\n` +
48-
`Your pattern "${pattern}" is missing the parameter list and body.`
49-
)
25+
if (["javascript", "typescript", "tsx"].includes(lang)) {
26+
if (/^(export\s+)?(async\s+)?function\s+\$[A-Z_]+\s*$/i.test(src)) {
27+
return `💡 Hint: Function patterns need params and body. Try "function $NAME($$$) { $$$ }"`
28+
}
5029
}
30+
31+
return null
5132
}
5233

5334
export const ast_grep_search = tool({
@@ -66,16 +47,23 @@ export const ast_grep_search = tool({
6647
},
6748
execute: async (args, context) => {
6849
try {
69-
validatePatternForCli(args.pattern, args.lang as CliLanguage)
70-
7150
const matches = await runSg({
7251
pattern: args.pattern,
7352
lang: args.lang as CliLanguage,
7453
paths: args.paths,
7554
globs: args.globs,
7655
context: args.context,
7756
})
78-
const output = formatSearchResult(matches)
57+
58+
let output = formatSearchResult(matches)
59+
60+
if (matches.length === 0) {
61+
const hint = getEmptyResultHint(args.pattern, args.lang as CliLanguage)
62+
if (hint) {
63+
output += `\n\n${hint}`
64+
}
65+
}
66+
7967
showOutputToUser(context, output)
8068
return output
8169
} catch (e) {

0 commit comments

Comments
 (0)