diff options
| author | Anas H. Sulaiman <[email protected]> | 2025-06-14 10:25:34 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-14 14:25:34 +0000 |
| commit | 4873fce7919b4d74cee183a91fa8a3af58aef993 (patch) | |
| tree | c08502c1e4592667160cb006528f868fd6283294 /packages/cli/src/ui/hooks | |
| parent | e6d54771686b3f9537a5a05c9f9101afad3ffdcd (diff) | |
centralize file filtering in `FileDiscoveryService` (#1039)
Diffstat (limited to 'packages/cli/src/ui/hooks')
| -rw-r--r-- | packages/cli/src/ui/hooks/atCommandProcessor.test.ts | 22 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/atCommandProcessor.ts | 4 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/useCompletion.integration.test.ts | 29 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/useCompletion.ts | 14 |
4 files changed, 33 insertions, 36 deletions
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts index 9a80c95c..6380a187 100644 --- a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts @@ -89,7 +89,7 @@ describe('handleAtCommand', () => { // Mock FileDiscoveryService mockFileDiscoveryService = { initialize: vi.fn(), - shouldIgnoreFile: vi.fn(() => false), + shouldGitIgnoreFile: vi.fn(() => false), filterFiles: vi.fn((files) => files), getIgnoreInfo: vi.fn(() => ({ gitIgnored: [] })), isGitRepository: vi.fn(() => true), @@ -101,7 +101,7 @@ describe('handleAtCommand', () => { // Mock getFileService to return the mocked FileDiscoveryService mockConfig.getFileService = vi .fn() - .mockResolvedValue(mockFileDiscoveryService); + .mockReturnValue(mockFileDiscoveryService); }); afterEach(() => { @@ -581,7 +581,7 @@ describe('handleAtCommand', () => { const query = `@${gitIgnoredFile}`; // Mock the file discovery service to report this file as git-ignored - mockFileDiscoveryService.shouldIgnoreFile.mockImplementation( + mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation( (path: string) => path === gitIgnoredFile, ); @@ -594,7 +594,7 @@ describe('handleAtCommand', () => { signal: abortController.signal, }); - expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith( + expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith( gitIgnoredFile, ); expect(mockOnDebugMessage).toHaveBeenCalledWith( @@ -613,7 +613,7 @@ describe('handleAtCommand', () => { const query = `@${validFile}`; const fileContent = 'console.log("Hello world");'; - mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(false); + mockFileDiscoveryService.shouldGitIgnoreFile.mockReturnValue(false); mockReadManyFilesExecute.mockResolvedValue({ llmContent: [`--- ${validFile} ---\n\n${fileContent}\n\n`], returnDisplay: 'Read 1 file.', @@ -628,7 +628,7 @@ describe('handleAtCommand', () => { signal: abortController.signal, }); - expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith( + expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith( validFile, ); expect(mockReadManyFilesExecute).toHaveBeenCalledWith( @@ -651,7 +651,7 @@ describe('handleAtCommand', () => { const query = `@${validFile} @${gitIgnoredFile}`; const fileContent = '# Project README'; - mockFileDiscoveryService.shouldIgnoreFile.mockImplementation( + mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation( (path: string) => path === gitIgnoredFile, ); mockReadManyFilesExecute.mockResolvedValue({ @@ -668,10 +668,10 @@ describe('handleAtCommand', () => { signal: abortController.signal, }); - expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith( + expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith( validFile, ); - expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith( + expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith( gitIgnoredFile, ); expect(mockOnDebugMessage).toHaveBeenCalledWith( @@ -698,7 +698,7 @@ describe('handleAtCommand', () => { const gitFile = '.git/config'; const query = `@${gitFile}`; - mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(true); + mockFileDiscoveryService.shouldGitIgnoreFile.mockReturnValue(true); const result = await handleAtCommand({ query, @@ -709,7 +709,7 @@ describe('handleAtCommand', () => { signal: abortController.signal, }); - expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith( + expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith( gitFile, ); expect(mockOnDebugMessage).toHaveBeenCalledWith( diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts index edbbdc21..d6c5eded 100644 --- a/packages/cli/src/ui/hooks/atCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts @@ -135,7 +135,7 @@ export async function handleAtCommand({ addItem({ type: 'user', text: query }, userMessageTimestamp); // Get centralized file discovery service - const fileDiscovery = await config.getFileService(); + const fileDiscovery = config.getFileService(); const respectGitIgnore = config.getFileFilteringRespectGitIgnore(); const pathSpecsToRead: string[] = []; @@ -182,7 +182,7 @@ export async function handleAtCommand({ } // Check if path should be ignored by git - if (fileDiscovery.shouldIgnoreFile(pathName)) { + if (fileDiscovery.shouldGitIgnoreFile(pathName)) { const reason = respectGitIgnore ? 'git-ignored and will be skipped' : 'ignored by custom patterns'; diff --git a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts index 3ee24a8a..43ded9d0 100644 --- a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts +++ b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts @@ -10,6 +10,7 @@ import { renderHook, act } from '@testing-library/react'; import { useCompletion } from './useCompletion.js'; import * as fs from 'fs/promises'; import { FileDiscoveryService } from '@gemini-cli/core'; +import { glob } from 'glob'; // Mock dependencies vi.mock('fs/promises'); @@ -24,6 +25,7 @@ vi.mock('@gemini-cli/core', async () => { getErrorMessage: vi.fn((error) => error.message), }; }); +vi.mock('glob'); describe('useCompletion git-aware filtering integration', () => { let mockFileDiscoveryService: Mocked<FileDiscoveryService>; @@ -38,16 +40,13 @@ describe('useCompletion git-aware filtering integration', () => { beforeEach(() => { mockFileDiscoveryService = { - initialize: vi.fn(), - shouldIgnoreFile: vi.fn(), + shouldGitIgnoreFile: vi.fn(), filterFiles: vi.fn(), - getIgnoreInfo: vi.fn(() => ({ gitIgnored: [] })), - glob: vi.fn().mockResolvedValue([]), }; mockConfig = { getFileFilteringRespectGitIgnore: vi.fn(() => true), - getFileService: vi.fn().mockResolvedValue(mockFileDiscoveryService), + getFileService: vi.fn().mockReturnValue(mockFileDiscoveryService), }; vi.mocked(FileDiscoveryService).mockImplementation( @@ -71,7 +70,7 @@ describe('useCompletion git-aware filtering integration', () => { ] as Array<{ name: string; isDirectory: () => boolean }>); // Mock git ignore service to ignore certain files - mockFileDiscoveryService.shouldIgnoreFile.mockImplementation( + mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation( (path: string) => path.includes('node_modules') || path.includes('dist') || @@ -125,7 +124,7 @@ describe('useCompletion git-aware filtering integration', () => { ); // Mock git ignore service - mockFileDiscoveryService.shouldIgnoreFile.mockImplementation( + mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation( (path: string) => path.includes('node_modules') || path.includes('temp'), ); @@ -173,10 +172,6 @@ describe('useCompletion git-aware filtering integration', () => { }); it('should handle git discovery service initialization failure gracefully', async () => { - mockFileDiscoveryService.initialize.mockRejectedValue( - new Error('Git not found'), - ); - vi.mocked(fs.readdir).mockResolvedValue([ { name: 'src', isDirectory: () => true }, { name: 'README.md', isDirectory: () => false }, @@ -208,7 +203,7 @@ describe('useCompletion git-aware filtering integration', () => { { name: 'index.ts', isDirectory: () => false }, ] as Array<{ name: string; isDirectory: () => boolean }>); - mockFileDiscoveryService.shouldIgnoreFile.mockImplementation( + mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation( (path: string) => path.includes('.log'), ); @@ -228,7 +223,7 @@ describe('useCompletion git-aware filtering integration', () => { it('should use glob for top-level @ completions when available', async () => { const globResults = [`${testCwd}/src/index.ts`, `${testCwd}/README.md`]; - mockFileDiscoveryService.glob.mockResolvedValue(globResults); + vi.mocked(glob).mockResolvedValue(globResults); const { result } = renderHook(() => useCompletion('@s', testCwd, true, slashCommands, mockConfig), @@ -238,9 +233,10 @@ describe('useCompletion git-aware filtering integration', () => { await new Promise((resolve) => setTimeout(resolve, 150)); }); - expect(mockFileDiscoveryService.glob).toHaveBeenCalledWith('**/s*', { + expect(glob).toHaveBeenCalledWith('**/s*', { cwd: testCwd, dot: false, + nocase: true, }); expect(fs.readdir).not.toHaveBeenCalled(); // Ensure glob is used instead of readdir expect(result.current.suggestions).toEqual([ @@ -255,7 +251,7 @@ describe('useCompletion git-aware filtering integration', () => { `${testCwd}/.gitignore`, `${testCwd}/src/index.ts`, ]; - mockFileDiscoveryService.glob.mockResolvedValue(globResults); + vi.mocked(glob).mockResolvedValue(globResults); const { result } = renderHook(() => useCompletion('@.', testCwd, true, slashCommands, mockConfig), @@ -265,9 +261,10 @@ describe('useCompletion git-aware filtering integration', () => { await new Promise((resolve) => setTimeout(resolve, 150)); }); - expect(mockFileDiscoveryService.glob).toHaveBeenCalledWith('**/.*', { + expect(glob).toHaveBeenCalledWith('**/.*', { cwd: testCwd, dot: true, + nocase: true, }); expect(fs.readdir).not.toHaveBeenCalled(); expect(result.current.suggestions).toEqual([ diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts index 0aa04263..b7603720 100644 --- a/packages/cli/src/ui/hooks/useCompletion.ts +++ b/packages/cli/src/ui/hooks/useCompletion.ts @@ -7,6 +7,7 @@ import { useState, useEffect, useCallback } from 'react'; import * as fs from 'fs/promises'; import * as path from 'path'; +import { glob } from 'glob'; import { isNodeError, escapePath, @@ -187,7 +188,7 @@ export function useCompletion( const findFilesRecursively = async ( startDir: string, searchPrefix: string, - fileDiscovery: { shouldIgnoreFile: (path: string) => boolean } | null, + fileDiscovery: { shouldGitIgnoreFile: (path: string) => boolean } | null, currentRelativePath = '', depth = 0, maxDepth = 10, // Limit recursion depth @@ -218,7 +219,7 @@ export function useCompletion( // Check if this entry should be ignored by git-aware filtering if ( fileDiscovery && - fileDiscovery.shouldIgnoreFile(entryPathFromRoot) + fileDiscovery.shouldGitIgnoreFile(entryPathFromRoot) ) { continue; } @@ -263,9 +264,10 @@ export function useCompletion( maxResults = 50, ): Promise<Suggestion[]> => { const globPattern = `**/${searchPrefix}*`; - const files = await fileDiscoveryService.glob(globPattern, { + const files = await glob(globPattern, { cwd, dot: searchPrefix.startsWith('.'), + nocase: true, }); const suggestions: Suggestion[] = files @@ -285,9 +287,7 @@ export function useCompletion( setIsLoadingSuggestions(true); let fetchedSuggestions: Suggestion[] = []; - const fileDiscoveryService = config - ? await config.getFileService() - : null; + const fileDiscoveryService = config ? config.getFileService() : null; try { // If there's no slash, or it's the root, do a recursive search from cwd @@ -326,7 +326,7 @@ export function useCompletion( ); if ( fileDiscoveryService && - fileDiscoveryService.shouldIgnoreFile(relativePath) + fileDiscoveryService.shouldGitIgnoreFile(relativePath) ) { continue; } |
