fix(ast-grep): add validation for incomplete function declaration patterns (#5)
* fix(publish): make git operations idempotent - Check for staged changes before commit - Check if tag exists before creating - Check if release exists before creating * fix(ast-grep): add validation for incomplete function declaration patterns - Add validatePatternForCli function to detect incomplete patterns like 'export async function $METHOD' (missing params and body) - Only validates JS/TS languages (javascript, typescript, tsx) - Provides helpful error message with correct pattern examples - Update tool description to clarify complete AST nodes required This fixes the issue where incomplete patterns would fail silently with no results instead of providing actionable feedback.
This commit is contained in:
@@ -78,13 +78,35 @@ async function gitTagAndRelease(newVersion: string, changelog: string): Promise<
|
|||||||
await $`git config user.email "github-actions[bot]@users.noreply.github.com"`
|
await $`git config user.email "github-actions[bot]@users.noreply.github.com"`
|
||||||
await $`git config user.name "github-actions[bot]"`
|
await $`git config user.name "github-actions[bot]"`
|
||||||
await $`git add package.json`
|
await $`git add package.json`
|
||||||
await $`git commit -m "release: v${newVersion}"`
|
|
||||||
await $`git tag v${newVersion}`
|
// Commit only if there are staged changes (idempotent)
|
||||||
|
const hasStagedChanges = await $`git diff --cached --quiet`.nothrow()
|
||||||
|
if (hasStagedChanges.exitCode !== 0) {
|
||||||
|
await $`git commit -m "release: v${newVersion}"`
|
||||||
|
} else {
|
||||||
|
console.log("No changes to commit (version already updated)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag only if it doesn't exist (idempotent)
|
||||||
|
const tagExists = await $`git rev-parse v${newVersion}`.nothrow()
|
||||||
|
if (tagExists.exitCode !== 0) {
|
||||||
|
await $`git tag v${newVersion}`
|
||||||
|
} else {
|
||||||
|
console.log(`Tag v${newVersion} already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push (idempotent - git push is already idempotent)
|
||||||
await $`git push origin HEAD --tags`
|
await $`git push origin HEAD --tags`
|
||||||
|
|
||||||
|
// Create release only if it doesn't exist (idempotent)
|
||||||
console.log("\nCreating GitHub release...")
|
console.log("\nCreating GitHub release...")
|
||||||
const releaseNotes = changelog || "No notable changes"
|
const releaseNotes = changelog || "No notable changes"
|
||||||
await $`gh release create v${newVersion} --title "v${newVersion}" --notes ${releaseNotes}`
|
const releaseExists = await $`gh release view v${newVersion}`.nothrow()
|
||||||
|
if (releaseExists.exitCode !== 0) {
|
||||||
|
await $`gh release create v${newVersion} --title "v${newVersion}" --notes ${releaseNotes}`
|
||||||
|
} else {
|
||||||
|
console.log(`Release v${newVersion} already exists`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkVersionExists(version: string): Promise<boolean> {
|
async function checkVersionExists(version: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -10,13 +10,55 @@ function showOutputToUser(context: unknown, output: string): void {
|
|||||||
ctx.metadata?.({ metadata: { output } })
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ast_grep_search = tool({
|
export const ast_grep_search = tool({
|
||||||
description:
|
description:
|
||||||
"Search code patterns across filesystem using AST-aware matching. Supports 25 languages. " +
|
"Search code patterns across filesystem using AST-aware matching. Supports 25 languages. " +
|
||||||
"Use meta-variables: $VAR (single node), $$$ (multiple nodes). " +
|
"Use meta-variables: $VAR (single node), $$$ (multiple nodes). " +
|
||||||
|
"IMPORTANT: Patterns must be complete AST nodes (valid code). " +
|
||||||
|
"For functions, include params and body: 'export async function $NAME($$$) { $$$ }' not 'export async function $NAME'. " +
|
||||||
"Examples: 'console.log($MSG)', 'def $FUNC($$$):', 'async function $NAME($$$)'",
|
"Examples: 'console.log($MSG)', 'def $FUNC($$$):', 'async function $NAME($$$)'",
|
||||||
args: {
|
args: {
|
||||||
pattern: tool.schema.string().describe("AST pattern with meta-variables ($VAR, $$$)"),
|
pattern: tool.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
||||||
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
lang: tool.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
||||||
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search (default: ['.'])"),
|
paths: tool.schema.array(tool.schema.string()).optional().describe("Paths to search (default: ['.'])"),
|
||||||
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
globs: tool.schema.array(tool.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
||||||
@@ -24,6 +66,8 @@ export const ast_grep_search = tool({
|
|||||||
},
|
},
|
||||||
execute: async (args, context) => {
|
execute: async (args, context) => {
|
||||||
try {
|
try {
|
||||||
|
validatePatternForCli(args.pattern, args.lang as CliLanguage)
|
||||||
|
|
||||||
const matches = await runSg({
|
const matches = await runSg({
|
||||||
pattern: args.pattern,
|
pattern: args.pattern,
|
||||||
lang: args.lang as CliLanguage,
|
lang: args.lang as CliLanguage,
|
||||||
|
|||||||
Reference in New Issue
Block a user