@@ -20,20 +20,83 @@ function sanitizeRipgrepArgs(args?: string[]): string[] {
20
20
) ;
21
21
}
22
22
23
+ /**
24
+ * Validates and sanitizes a search path for use with ripgrep
25
+ * @param searchPath The path to validate (can be file or directory)
26
+ * @returns The sanitized path or throws an error if invalid
27
+ */
28
+ function validateSearchPath ( searchPath : string ) : string {
29
+ // Remove any potentially dangerous characters
30
+ const dangerous = / [ | ; & ` $ ( ) { } [ \] ] / ;
31
+ if ( dangerous . test ( searchPath ) ) {
32
+ throw new Error ( `Invalid characters in search path: ${ searchPath } ` ) ;
33
+ }
34
+
35
+ // Normalize path separators and remove leading/trailing whitespace
36
+ const normalized = searchPath . trim ( ) . replace ( / \\ / g, "/" ) ;
37
+
38
+ // Don't allow absolute paths or path traversal for security
39
+ if (
40
+ normalized . startsWith ( "/" ) ||
41
+ normalized . includes ( "../" ) ||
42
+ normalized . includes ( "..\\" )
43
+ ) {
44
+ throw new Error (
45
+ `Absolute paths and path traversal not allowed: ${ searchPath } ` ,
46
+ ) ;
47
+ }
48
+
49
+ return normalized ;
50
+ }
51
+
52
+ /**
53
+ * Validates and potentially fixes common regex pattern issues for ripgrep
54
+ * @param query The regex pattern to validate
55
+ * @returns The validated pattern or throws an error if invalid
56
+ */
57
+ function validateRipgrepPattern ( query : string ) : string {
58
+ // Check for common problematic patterns and provide helpful error messages
59
+ if ( query . includes ( "\\b" ) && query . includes ( "|" ) ) {
60
+ // Common issue: complex patterns with word boundaries and alternations
61
+ // These should work but may need careful escaping
62
+ console . warn (
63
+ "Complex pattern detected with word boundaries and alternations. Ensure proper escaping." ,
64
+ ) ;
65
+ }
66
+
67
+ if ( query . match ( / \\ [ ^ b d s w n r t f a v \\ ] / ) ) {
68
+ console . warn (
69
+ "Unusual escape sequence detected in pattern. Double-check escaping." ,
70
+ ) ;
71
+ }
72
+
73
+ return query ;
74
+ }
75
+
23
76
export function buildRipgrepArgs (
24
77
query : string ,
25
- { extraArgs, maxResults } : { extraArgs ?: string [ ] ; maxResults ?: number } = { } ,
78
+ {
79
+ extraArgs,
80
+ maxResults,
81
+ path,
82
+ } : { extraArgs ?: string [ ] ; maxResults ?: number ; path ?: string } = { } ,
26
83
) : string [ ] {
27
84
const args = [ ...DEFAULT_RIPGREP_ARGS ] ;
28
85
const sanitized = sanitizeRipgrepArgs ( extraArgs ) ;
29
86
87
+ // Validate the query pattern
88
+ const validatedQuery = validateRipgrepPattern ( query ) ;
89
+
30
90
let before = DEFAULT_CONTEXT_BEFORE ;
31
91
let after = DEFAULT_CONTEXT_AFTER ;
32
92
const remaining : string [ ] = [ ] ;
33
93
34
94
for ( let i = 0 ; i < sanitized . length ; i ++ ) {
35
95
const arg = sanitized [ i ] ;
36
- if ( ( arg === "-A" || arg === "-B" || arg === "-C" ) && i + 1 < sanitized . length ) {
96
+ if (
97
+ ( arg === "-A" || arg === "-B" || arg === "-C" ) &&
98
+ i + 1 < sanitized . length
99
+ ) {
37
100
const val = parseInt ( sanitized [ i + 1 ] ! , 10 ) ;
38
101
if ( ! isNaN ( val ) ) {
39
102
if ( arg === "-A" ) {
@@ -64,14 +127,22 @@ export function buildRipgrepArgs(
64
127
}
65
128
66
129
args . push ( ...remaining ) ;
67
- args . push ( "-e" , query , "." ) ;
130
+
131
+ // Determine search target (path or current directory)
132
+ const searchTarget = path ? validateSearchPath ( path ) : "." ;
133
+ args . push ( "-e" , validatedQuery , searchTarget ) ;
68
134
return args ;
69
135
}
70
136
71
137
/*
72
138
Formats the output of a grep search to reduce unnecessary indentation, lines, etc
73
- Assumes a command with these params
139
+ Handles both standard ripgrep output with --heading and simple file lists (e.g., with -l flag)
140
+
141
+ Standard format:
74
142
ripgrep -i --ignore-file .continueignore --ignore-file .gitignore -C 2 --heading -m 100 -e <query> .
143
+
144
+ File list format (with -l flag):
145
+ ripgrep -l -i --ignore-file .continueignore --ignore-file .gitignore -e <query> .
75
146
76
147
Also can truncate the output to a specified number of characters
77
148
*/
@@ -86,15 +157,48 @@ export function formatGrepSearchResults(
86
157
let numResults = 0 ;
87
158
const keepLines : string [ ] = [ ] ;
88
159
160
+ // Check if this looks like a simple file list (all lines start with ./ and no content)
161
+ const lines = results . split ( "\n" ) . filter ( ( l ) => ! ! l ) ;
162
+ const isFileListOnly =
163
+ lines . length > 0 &&
164
+ lines . every ( ( line ) => line . startsWith ( "./" ) || line === "No matches found" ) ;
165
+
166
+ if ( isFileListOnly ) {
167
+ // Handle simple file list output (e.g., from -l flag)
168
+ const fileLines = lines . filter ( ( line ) => line . startsWith ( "./" ) ) ;
169
+ numResults = fileLines . length ;
170
+ const formatted = fileLines . join ( "\n" ) ;
171
+
172
+ if ( maxChars && formatted . length > maxChars ) {
173
+ return {
174
+ formatted : formatted . substring ( 0 , maxChars ) ,
175
+ numResults,
176
+ truncated : true ,
177
+ } ;
178
+ } else {
179
+ return {
180
+ formatted,
181
+ numResults,
182
+ truncated : false ,
183
+ } ;
184
+ }
185
+ }
186
+
187
+ // Handle standard format with content
89
188
function countLeadingSpaces ( line : string ) {
90
189
return line ?. match ( / ^ * / ) ?. [ 0 ] . length ?? 0 ;
91
190
}
92
191
93
192
const processResult = ( lines : string [ ] ) => {
94
- // Skip results in which only the file path was kept
193
+ // Handle file path lines
95
194
const resultPath = lines [ 0 ] ;
96
195
const resultContent = lines . slice ( 1 ) ;
196
+
197
+ // For file-only results (like with -l), still include the path
97
198
if ( resultContent . length === 0 ) {
199
+ if ( resultPath && resultPath . startsWith ( "./" ) ) {
200
+ keepLines . push ( resultPath ) ;
201
+ }
98
202
return ;
99
203
}
100
204
@@ -126,7 +230,7 @@ export function formatGrepSearchResults(
126
230
} ;
127
231
128
232
let resultLines : string [ ] = [ ] ;
129
- for ( const line of results . split ( "\n" ) . filter ( ( l ) => ! ! l ) ) {
233
+ for ( const line of lines ) {
130
234
if ( line . startsWith ( "./" ) || line === "--" ) {
131
235
processResult ( resultLines ) ; // process previous result
132
236
resultLines = [ line ] ;
0 commit comments