mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 01:37:50 +00:00
Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import * as cache from './crawlCache.js';
|
||||
import { crawl } from './crawler.js';
|
||||
import { createTmpDir, cleanupTmpDir } from '@qwen-code/qwen-code-test-utils';
|
||||
import { Ignore, loadIgnoreRules } from './ignore.js';
|
||||
import type { Ignore } from './ignore.js';
|
||||
import { loadIgnoreRules } from './ignore.js';
|
||||
|
||||
describe('crawler', () => {
|
||||
let tmpDir: string;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import { fdir } from 'fdir';
|
||||
import { Ignore } from './ignore.js';
|
||||
import type { Ignore } from './ignore.js';
|
||||
import * as cache from './crawlCache.js';
|
||||
|
||||
export interface CrawlOptions {
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -57,6 +58,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -84,6 +86,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -112,6 +115,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -144,6 +148,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -167,6 +172,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -201,6 +207,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -230,6 +237,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -259,6 +267,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
// Expect no errors to be thrown during initialization
|
||||
@@ -285,6 +294,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -310,6 +320,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -318,6 +329,60 @@ describe('FileSearch', () => {
|
||||
expect(results).toEqual(['src/style.css']);
|
||||
});
|
||||
|
||||
it('should not use fzf for fuzzy matching when disableFuzzySearch is true', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: {
|
||||
'file1.js': '',
|
||||
'flexible.js': '',
|
||||
'other.ts': '',
|
||||
},
|
||||
});
|
||||
|
||||
const fileSearch = FileSearchFactory.create({
|
||||
projectRoot: tmpDir,
|
||||
useGitignore: false,
|
||||
useGeminiignore: false,
|
||||
ignoreDirs: [],
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: true,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
const results = await fileSearch.search('fle');
|
||||
|
||||
expect(results).toEqual(['src/flexible.js']);
|
||||
});
|
||||
|
||||
it('should use fzf for fuzzy matching when disableFuzzySearch is false', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: {
|
||||
'file1.js': '',
|
||||
'flexible.js': '',
|
||||
'other.ts': '',
|
||||
},
|
||||
});
|
||||
|
||||
const fileSearch = FileSearchFactory.create({
|
||||
projectRoot: tmpDir,
|
||||
useGitignore: false,
|
||||
useGeminiignore: false,
|
||||
ignoreDirs: [],
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
const results = await fileSearch.search('fle');
|
||||
|
||||
expect(results).toEqual(
|
||||
expect.arrayContaining(['src/file1.js', 'src/flexible.js']),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty array when no matches are found', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: ['file1.js'],
|
||||
@@ -331,6 +396,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -361,6 +427,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await expect(fileSearch.search('')).rejects.toThrow(
|
||||
@@ -382,6 +449,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -404,6 +472,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -427,6 +496,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -463,6 +533,7 @@ describe('FileSearch', () => {
|
||||
cache: true, // Enable caching for this test
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -502,6 +573,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -545,6 +617,7 @@ describe('FileSearch', () => {
|
||||
cache: true, // Ensure caching is enabled
|
||||
cacheTtl: 10000,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -566,6 +639,36 @@ describe('FileSearch', () => {
|
||||
expect(limitedResults).toEqual(['file1.js', 'file2.js']);
|
||||
});
|
||||
|
||||
it('should handle file paths with special characters that need escaping', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
src: {
|
||||
'file with (special) chars.txt': '',
|
||||
'another-file.txt': '',
|
||||
},
|
||||
});
|
||||
|
||||
const fileSearch = FileSearchFactory.create({
|
||||
projectRoot: tmpDir,
|
||||
useGitignore: false,
|
||||
useGeminiignore: false,
|
||||
ignoreDirs: [],
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: true,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
|
||||
// Search for the file using a pattern that contains special characters.
|
||||
// The `unescapePath` function should handle the escaped path correctly.
|
||||
const results = await fileSearch.search(
|
||||
'src/file with \\(special\\) chars.txt',
|
||||
);
|
||||
|
||||
expect(results).toEqual(['src/file with (special) chars.txt']);
|
||||
});
|
||||
|
||||
describe('DirectoryFileSearch', () => {
|
||||
it('should search for files in the current directory', async () => {
|
||||
tmpDir = await createTmpDir({
|
||||
@@ -582,6 +685,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -606,6 +710,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -630,6 +735,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
@@ -652,6 +758,7 @@ describe('FileSearch', () => {
|
||||
cache: false,
|
||||
cacheTtl: 0,
|
||||
enableRecursiveFileSearch: false,
|
||||
disableFuzzySearch: false,
|
||||
});
|
||||
|
||||
await fileSearch.initialize();
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
|
||||
import path from 'node:path';
|
||||
import picomatch from 'picomatch';
|
||||
import { Ignore, loadIgnoreRules } from './ignore.js';
|
||||
import type { Ignore } from './ignore.js';
|
||||
import { loadIgnoreRules } from './ignore.js';
|
||||
import { ResultCache } from './result-cache.js';
|
||||
import { crawl } from './crawler.js';
|
||||
import { AsyncFzf, FzfResultItem } from 'fzf';
|
||||
import type { FzfResultItem } from 'fzf';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
import { unescapePath } from '../paths.js';
|
||||
|
||||
export interface FileSearchOptions {
|
||||
projectRoot: string;
|
||||
@@ -19,6 +22,7 @@ export interface FileSearchOptions {
|
||||
cache: boolean;
|
||||
cacheTtl: number;
|
||||
enableRecursiveFileSearch: boolean;
|
||||
disableFuzzySearch: boolean;
|
||||
maxDepth?: number;
|
||||
}
|
||||
|
||||
@@ -112,11 +116,15 @@ class RecursiveFileSearch implements FileSearch {
|
||||
pattern: string,
|
||||
options: SearchOptions = {},
|
||||
): Promise<string[]> {
|
||||
if (!this.resultCache || !this.fzf || !this.ignore) {
|
||||
if (
|
||||
!this.resultCache ||
|
||||
(!this.fzf && !this.options.disableFuzzySearch) ||
|
||||
!this.ignore
|
||||
) {
|
||||
throw new Error('Engine not initialized. Call initialize() first.');
|
||||
}
|
||||
|
||||
pattern = pattern || '*';
|
||||
pattern = unescapePath(pattern) || '*';
|
||||
|
||||
let filteredCandidates;
|
||||
const { files: candidates, isExactMatch } =
|
||||
@@ -127,7 +135,7 @@ class RecursiveFileSearch implements FileSearch {
|
||||
filteredCandidates = candidates;
|
||||
} else {
|
||||
let shouldCache = true;
|
||||
if (pattern.includes('*')) {
|
||||
if (pattern.includes('*') || !this.fzf) {
|
||||
filteredCandidates = await filter(candidates, pattern, options.signal);
|
||||
} else {
|
||||
filteredCandidates = await this.fzf
|
||||
@@ -171,12 +179,14 @@ class RecursiveFileSearch implements FileSearch {
|
||||
|
||||
private buildResultCache(): void {
|
||||
this.resultCache = new ResultCache(this.allFiles);
|
||||
// The v1 algorithm is much faster since it only looks at the first
|
||||
// occurence of the pattern. We use it for search spaces that have >20k
|
||||
// files, because the v2 algorithm is just too slow in those cases.
|
||||
this.fzf = new AsyncFzf(this.allFiles, {
|
||||
fuzzy: this.allFiles.length > 20000 ? 'v1' : 'v2',
|
||||
});
|
||||
if (!this.options.disableFuzzySearch) {
|
||||
// The v1 algorithm is much faster since it only looks at the first
|
||||
// occurence of the pattern. We use it for search spaces that have >20k
|
||||
// files, because the v2 algorithm is just too slow in those cases.
|
||||
this.fzf = new AsyncFzf(this.allFiles, {
|
||||
fuzzy: this.allFiles.length > 20000 ? 'v1' : 'v2',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user