From 9665928860eda07a9645c0c7a985f78ae888f6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalle=20Ahlstr=C3=B6m?= <71292737+kahlstrm@users.noreply.github.com> Date: Sat, 28 Jun 2025 17:41:25 +0300 Subject: [PATCH] chore: add proper pluralization handling for match in grep tool (#2344) Co-authored-by: Allen Hutchison --- docs/tools/file-system.md | 2 +- packages/core/src/tools/grep.test.ts | 20 ++++++++++---------- packages/core/src/tools/grep.ts | 7 +++++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/tools/file-system.md b/docs/tools/file-system.md index d1ef194b..c29f086a 100644 --- a/docs/tools/file-system.md +++ b/docs/tools/file-system.md @@ -159,7 +159,7 @@ The Gemini CLI provides a comprehensive suite of tools for interacting with the - Returns a list of matching lines, each prefixed with its file path (relative to the search directory) and line number. - **Output (`llmContent`):** A formatted string of matches, e.g.: ``` - Found 3 match(es) for pattern "myFunction" in path "." (filter: "*.ts"): + Found 3 matches for pattern "myFunction" in path "." (filter: "*.ts"): --- File: src/utils.ts L15: export function myFunction() { diff --git a/packages/core/src/tools/grep.test.ts b/packages/core/src/tools/grep.test.ts index 59eb75a4..ae629a52 100644 --- a/packages/core/src/tools/grep.test.ts +++ b/packages/core/src/tools/grep.test.ts @@ -115,38 +115,38 @@ describe('GrepTool', () => { const params: GrepToolParams = { pattern: 'world' }; const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 3 match(es) for pattern "world" in path "."', + 'Found 3 matches for pattern "world" in path "."', ); expect(result.llmContent).toContain('File: fileA.txt'); expect(result.llmContent).toContain('L1: hello world'); expect(result.llmContent).toContain('L2: second line with world'); expect(result.llmContent).toContain('File: sub/fileC.txt'); expect(result.llmContent).toContain('L1: another world in sub dir'); - expect(result.returnDisplay).toBe('Found 3 matche(s)'); + expect(result.returnDisplay).toBe('Found 3 matches'); }); it('should find matches in a specific path', async () => { const params: GrepToolParams = { pattern: 'world', path: 'sub' }; const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 1 match(es) for pattern "world" in path "sub"', + 'Found 1 match for pattern "world" in path "sub"', ); expect(result.llmContent).toContain('File: fileC.txt'); // Path relative to 'sub' expect(result.llmContent).toContain('L1: another world in sub dir'); - expect(result.returnDisplay).toBe('Found 1 matche(s)'); + expect(result.returnDisplay).toBe('Found 1 match'); }); it('should find matches with an include glob', async () => { const params: GrepToolParams = { pattern: 'hello', include: '*.js' }; const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 1 match(es) for pattern "hello" in path "." (filter: "*.js")', + 'Found 1 match for pattern "hello" in path "." (filter: "*.js")', ); expect(result.llmContent).toContain('File: fileB.js'); expect(result.llmContent).toContain( 'L2: function baz() { return "hello"; }', ); - expect(result.returnDisplay).toBe('Found 1 matche(s)'); + expect(result.returnDisplay).toBe('Found 1 match'); }); it('should find matches with an include glob and path', async () => { @@ -161,11 +161,11 @@ describe('GrepTool', () => { }; const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 1 match(es) for pattern "hello" in path "sub" (filter: "*.js")', + 'Found 1 match for pattern "hello" in path "sub" (filter: "*.js")', ); expect(result.llmContent).toContain('File: another.js'); expect(result.llmContent).toContain('L1: const greeting = "hello";'); - expect(result.returnDisplay).toBe('Found 1 matche(s)'); + expect(result.returnDisplay).toBe('Found 1 match'); }); it('should return "No matches found" when pattern does not exist', async () => { @@ -181,7 +181,7 @@ describe('GrepTool', () => { const params: GrepToolParams = { pattern: 'foo.*bar' }; // Matches 'const foo = "bar";' const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 1 match(es) for pattern "foo.*bar" in path "."', + 'Found 1 match for pattern "foo.*bar" in path "."', ); expect(result.llmContent).toContain('File: fileB.js'); expect(result.llmContent).toContain('L1: const foo = "bar";'); @@ -191,7 +191,7 @@ describe('GrepTool', () => { const params: GrepToolParams = { pattern: 'HELLO' }; const result = await grepTool.execute(params, abortSignal); expect(result.llmContent).toContain( - 'Found 2 match(es) for pattern "HELLO" in path "."', + 'Found 2 matches for pattern "HELLO" in path "."', ); expect(result.llmContent).toContain('File: fileA.txt'); expect(result.llmContent).toContain('L1: hello world'); diff --git a/packages/core/src/tools/grep.ts b/packages/core/src/tools/grep.ts index f7de190b..612291d5 100644 --- a/packages/core/src/tools/grep.ts +++ b/packages/core/src/tools/grep.ts @@ -213,7 +213,10 @@ export class GrepTool extends BaseTool { {} as Record, ); - let llmContent = `Found ${matches.length} match(es) for pattern "${params.pattern}" in path "${searchDirDisplay}"${params.include ? ` (filter: "${params.include}")` : ''}:\n---\n`; + const matchCount = matches.length; + const matchTerm = matchCount === 1 ? 'match' : 'matches'; + + let llmContent = `Found ${matchCount} ${matchTerm} for pattern "${params.pattern}" in path "${searchDirDisplay}"${params.include ? ` (filter: "${params.include}")` : ''}:\n---\n`; for (const filePath in matchesByFile) { llmContent += `File: ${filePath}\n`; @@ -226,7 +229,7 @@ export class GrepTool extends BaseTool { return { llmContent: llmContent.trim(), - returnDisplay: `Found ${matches.length} matche(s)`, + returnDisplay: `Found ${matchCount} ${matchTerm}`, }; } catch (error) { console.error(`Error during GrepLogic execution: ${error}`);