From b8f5599e6138cdcf740d0fbd8ba42ef1fdbe8084 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 5 Dec 2025 20:59:05 +0900 Subject: [PATCH] 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 --- src/tools/ast-grep/tools.ts | 64 +++++++++++++++---------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/tools/ast-grep/tools.ts b/src/tools/ast-grep/tools.ts index 49ff8c7..b8e3b63 100644 --- a/src/tools/ast-grep/tools.ts +++ b/src/tools/ast-grep/tools.ts @@ -10,44 +10,25 @@ function showOutputToUser(context: unknown, output: string): void { ctx.metadata?.({ metadata: { output } }) } -/** - * JS/TS languages that require complete function declaration patterns - */ -const JS_TS_LANGUAGES = ["javascript", "typescript", "tsx"] as const - -/** - * Validates AST pattern for common incomplete patterns that will fail silently. - * Only validates JS/TS languages where function declarations require body. - * - * @throws Error with helpful message if pattern is incomplete - */ -function validatePatternForCli(pattern: string, lang: CliLanguage): void { - if (!JS_TS_LANGUAGES.includes(lang as (typeof JS_TS_LANGUAGES)[number])) { - return - } - +function getEmptyResultHint(pattern: string, lang: CliLanguage): string | null { const src = pattern.trim() - // Detect incomplete function declarations: - // - "function $NAME" (no params/body) - // - "export function $NAME" (no params/body) - // - "export async function $NAME" (no params/body) - // - "export default function $NAME" (no params/body) - // Pattern: ends with $METAVAR (uppercase, underscore, digits) without ( or { - const incompleteFunctionDecl = - /^(export\s+)?(default\s+)?(async\s+)?function\s+\$[A-Z_][A-Z0-9_]*\s*$/i.test(src) - - if (incompleteFunctionDecl) { - throw new Error( - `Incomplete AST pattern for ${lang}: "${pattern}"\n\n` + - `ast-grep requires complete AST nodes. Function declarations must include parameters and body.\n\n` + - `Examples of correct patterns:\n` + - ` - "export async function $NAME($$$) { $$$ }" (matches export async functions)\n` + - ` - "function $NAME($$$) { $$$ }" (matches all function declarations)\n` + - ` - "async function $NAME($$$) { $$$ }" (matches async functions)\n\n` + - `Your pattern "${pattern}" is missing the parameter list and body.` - ) + if (lang === "python") { + if (src.startsWith("class ") && src.endsWith(":")) { + return `💡 Hint: Python class patterns need body. Try "class $NAME" or include body with $$$BODY` + } + if ((src.startsWith("def ") || src.startsWith("async def ")) && src.endsWith(":")) { + return `💡 Hint: Python function patterns need body. Try "def $FUNC($$$):\\n $$$BODY"` + } } + + if (["javascript", "typescript", "tsx"].includes(lang)) { + if (/^(export\s+)?(async\s+)?function\s+\$[A-Z_]+\s*$/i.test(src)) { + return `💡 Hint: Function patterns need params and body. Try "function $NAME($$$) { $$$ }"` + } + } + + return null } export const ast_grep_search = tool({ @@ -66,8 +47,6 @@ export const ast_grep_search = tool({ }, execute: async (args, context) => { try { - validatePatternForCli(args.pattern, args.lang as CliLanguage) - const matches = await runSg({ pattern: args.pattern, lang: args.lang as CliLanguage, @@ -75,7 +54,16 @@ export const ast_grep_search = tool({ globs: args.globs, context: args.context, }) - const output = formatSearchResult(matches) + + let output = formatSearchResult(matches) + + if (matches.length === 0) { + const hint = getEmptyResultHint(args.pattern, args.lang as CliLanguage) + if (hint) { + output += `\n\n${hint}` + } + } + showOutputToUser(context, output) return output } catch (e) {