Skip to main content
Glama
RipgrepCommandBuilder.test.ts12.3 kB
/** * Tests for RipgrepCommandBuilder */ import { describe, it, expect } from 'vitest'; import { RipgrepCommandBuilder } from '../../src/commands/RipgrepCommandBuilder.js'; import { RipgrepQuerySchema } from '../../src/scheme/local_ripgrep.js'; const createQuery = ( query: Parameters<typeof RipgrepQuerySchema.parse>[0] ) => RipgrepQuerySchema.parse(query); describe('RipgrepCommandBuilder', () => { describe('basic command building', () => { it('should build a simple search command', () => { const builder = new RipgrepCommandBuilder(); const { command, args } = builder.simple('pattern', '/path').build(); expect(command).toBe('rg'); expect(args).toContain('-n'); // Line numbers expect(args).toContain('-S'); // Smart case expect(args).toContain('pattern'); expect(args).toContain('/path'); }); it('should include default optimizations', () => { const builder = new RipgrepCommandBuilder(); const { args } = builder.simple('test', '/repo').build(); expect(args).toContain('--sort'); // Sorting enabled expect(args).toContain('path'); expect(args).toContain('--color'); expect(args).toContain('never'); }); }); describe('pattern modes', () => { it('should use fixed string mode', () => { const query = createQuery({ pattern: 'literal.string', path: './src', fixedString: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-F'); }); it('should use PCRE2 mode', () => { const query = createQuery({ pattern: '(?<=export )\\w+', path: './src', perlRegex: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-P'); }); }); describe('case sensitivity', () => { it('should use smart case by default', () => { const query = createQuery({ pattern: 'function', path: './src', }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-S'); }); it('should override with case insensitive', () => { const query = createQuery({ pattern: 'FUNCTION', path: './src', caseInsensitive: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-i'); expect(args).not.toContain('-S'); }); }); describe('file filtering', () => { it('should use type filtering', () => { const query = createQuery({ pattern: 'import', path: './src', type: 'ts', }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-t'); expect(args).toContain('ts'); }); it('should consolidate simple globs (optimization)', () => { const query = createQuery({ pattern: 'export', path: './src', include: ['*.ts', '*.tsx'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); // Should consolidate into single glob const globIndex = args.indexOf('-g'); expect(globIndex).not.toBe(-1); const glob = args[globIndex + 1]; expect(glob).toBe('*.{ts,tsx}'); }); it('should handle complex globs separately', () => { const query = createQuery({ pattern: 'test', path: './src', include: ['*.ts', 'src/**/*.test.*'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); // Should have both globs const firstGlobIndex = args.indexOf('-g'); expect(firstGlobIndex).not.toBe(-1); // Check for both globs expect(args.filter((a) => a === '-g').length).toBeGreaterThanOrEqual(1); }); it('should handle exclude patterns', () => { const query = createQuery({ pattern: 'function', path: './src', exclude: ['*.test.*', '*.spec.*'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-g'); expect(args).toContain('!*.test.*'); expect(args).toContain('!*.spec.*'); }); it('should handle excludeDir', () => { const query = createQuery({ pattern: 'import', path: './src', excludeDir: ['node_modules', 'dist'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('!node_modules/'); expect(args).toContain('!dist/'); }); }); describe('output control', () => { it('should use filesOnly mode', () => { const query = createQuery({ pattern: 'auth', path: './src', filesOnly: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-l'); }); it('should apply default maxMatchesPerFile', () => { const query = createQuery({ pattern: 'function', path: './src', }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-m'); expect(args).toContain('10'); // Default (matchesPerPage) }); it('should not apply maxMatches in filesOnly mode', () => { const query = createQuery({ pattern: 'function', path: './src', filesOnly: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); // Should have -l but not -m expect(args).toContain('-l'); const mIndex = args.indexOf('-m'); expect(mIndex).toBe(-1); }); it('should use custom maxMatchesPerFile', () => { const query = createQuery({ pattern: 'export', path: './src', maxMatchesPerFile: 10, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-m'); expect(args).toContain('10'); }); }); describe('context lines', () => { it('should add context lines', () => { const query = createQuery({ pattern: 'error', path: './logs', contextLines: 5, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-C'); expect(args).toContain('5'); }); it('should handle before/after context separately', () => { const query = createQuery({ pattern: 'error', path: './logs', beforeContext: 3, afterContext: 2, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-B'); expect(args).toContain('3'); expect(args).toContain('-A'); expect(args).toContain('2'); }); }); describe('advanced features', () => { it('should enable multiline mode', () => { const query = createQuery({ pattern: 'async.*\\n.*await', path: './src', multiline: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('-U'); }); it('should enable stats', () => { const query = createQuery({ pattern: 'function', path: './src', includeStats: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('--stats'); }); it('should enable JSON output', () => { const query = createQuery({ pattern: 'export', path: './src', jsonOutput: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('--json'); }); }); describe('sorting', () => { it('should sort by path by default', () => { const query = createQuery({ pattern: 'test', path: './src', }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('--sort'); expect(args).toContain('path'); }); it('should sort by modified time', () => { const query = createQuery({ pattern: 'FIXME', path: './src', sort: 'modified', }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('--sort'); expect(args).toContain('modified'); }); it('should reverse sort', () => { const query = createQuery({ pattern: 'TODO', path: './src', sort: 'path', sortReverse: true, }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); expect(args).toContain('--sortr'); expect(args).toContain('path'); expect(args).not.toContain('--sort'); }); it('should properly handle sort option removal when switching', () => { // Test that --sort is removed when switching to --sortr const queryReverse = createQuery({ pattern: 'test', path: './src', sort: 'modified', sortReverse: true, }); const { args: argsReverse } = new RipgrepCommandBuilder().fromQuery(queryReverse).build(); // Should have --sortr, not --sort const sortIndex = argsReverse.indexOf('--sort'); const sortrIndex = argsReverse.indexOf('--sortr'); expect(sortrIndex).not.toBe(-1); expect(sortIndex).toBe(-1); // --sort should not exist // Test that --sortr is removed when switching to --sort const queryNormal = createQuery({ pattern: 'test', path: './src', sort: 'modified', sortReverse: false, }); const { args: argsNormal } = new RipgrepCommandBuilder().fromQuery(queryNormal).build(); // Should have --sort, not --sortr const sortIndex2 = argsNormal.indexOf('--sort'); const sortrIndex2 = argsNormal.indexOf('--sortr'); expect(sortIndex2).not.toBe(-1); expect(sortrIndex2).toBe(-1); // --sortr should not exist }); }); describe('glob consolidation', () => { it('should consolidate 3+ simple globs', () => { const query = createQuery({ pattern: 'import', path: './src', include: ['*.ts', '*.tsx', '*.js', '*.jsx'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); const globIndex = args.indexOf('-g'); expect(globIndex).not.toBe(-1); const glob = args[globIndex + 1]; expect(glob).toBe('*.{ts,tsx,js,jsx}'); }); it('should not consolidate already-consolidated globs', () => { const query = createQuery({ pattern: 'test', path: './src', include: ['*.{ts,tsx}'], }); const { args } = new RipgrepCommandBuilder().fromQuery(query).build(); const globIndex = args.indexOf('-g'); expect(globIndex).not.toBe(-1); const glob = args[globIndex + 1]; expect(glob).toBe('*.{ts,tsx}'); }); }); describe('complete query', () => { it('should build a complex query with all features', () => { const query = createQuery({ pattern: '(login|logout|session)', path: '/repo/src', type: 'ts', contextLines: 3, maxMatchesPerFile: 5, smartCase: true, excludeDir: ['node_modules', '__tests__'], includeStats: true, sort: 'modified', }); const { command, args } = new RipgrepCommandBuilder() .fromQuery(query) .build(); expect(command).toBe('rg'); // Pattern mode expect(args).toContain('-S'); // Smart case // File filtering expect(args).toContain('-t'); expect(args).toContain('ts'); expect(args).toContain('!node_modules/'); expect(args).toContain('!__tests__/'); // Output control expect(args).toContain('-C'); expect(args).toContain('3'); expect(args).toContain('-m'); expect(args).toContain('5'); // Advanced expect(args).toContain('--stats'); expect(args).toContain('--sort'); expect(args).toContain('modified'); // Pattern and path expect(args).toContain('(login|logout|session)'); expect(args).toContain('/repo/src'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bgauryy/local-explorer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server