Skip to content

Commit e2fc7eb

Browse files
committed
fix ParenExpr with word with paren inside
1 parent 3d86df4 commit e2fc7eb

2 files changed

Lines changed: 43 additions & 12 deletions

File tree

packages/queryLanguage/src/tokens.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,26 @@ function startsWithPrefix(input: InputStream): boolean {
106106
* Checks if a '(' at the given offset starts a balanced ParenExpr.
107107
* Uses peek() to avoid modifying stream position.
108108
* Returns true if we find a matching ')' that closes the initial '('.
109+
* Handles escaped characters (backslash followed by any character).
109110
*/
110111
function hasBalancedParensAt(input: InputStream, startOffset: number): boolean {
111112
if (input.peek(startOffset) !== OPEN_PAREN) {
112113
return false;
113114
}
114-
115+
115116
let offset = startOffset + 1;
116117
let depth = 1;
117-
118+
118119
while (true) {
119120
const ch = input.peek(offset);
120121
if (ch === EOF) break;
121-
122+
123+
// Handle escaped characters - skip the next character after a backslash
124+
if (ch === 92 /* backslash */) {
125+
offset += 2; // Skip backslash and the escaped character
126+
continue;
127+
}
128+
122129
if (ch === OPEN_PAREN) {
123130
depth++;
124131
} else if (ch === CLOSE_PAREN) {
@@ -129,7 +136,7 @@ function hasBalancedParensAt(input: InputStream, startOffset: number): boolean {
129136
}
130137
offset++;
131138
}
132-
139+
133140
return false;
134141
}
135142

@@ -139,6 +146,7 @@ function hasBalancedParensAt(input: InputStream, startOffset: number): boolean {
139146
*
140147
* We only consider a '(' as a ParenExpr start if it's preceded by whitespace or
141148
* start-of-input, since "test()" has a '(' that's part of a word, not a ParenExpr.
149+
* Handles escaped characters (backslash followed by any character).
142150
*/
143151
function hasUnmatchedOpenParen(input: InputStream): boolean {
144152
// Count parens backwards from current position
@@ -153,27 +161,42 @@ function hasUnmatchedOpenParen(input: InputStream): boolean {
153161
return depth < 0;
154162
}
155163

164+
// Check if this character is escaped (preceded by backslash)
165+
// Note: we need to be careful about escaped backslashes (\\)
166+
// For simplicity, if we see a backslash immediately before, skip this char
167+
const prevCh = input.peek(offset - 1);
168+
if (prevCh === 92 /* backslash */) {
169+
// Check if the backslash itself is escaped
170+
const prevPrevCh = input.peek(offset - 2);
171+
if (prevPrevCh !== 92) {
172+
// Single backslash - this char is escaped, skip it
173+
offset--;
174+
continue;
175+
}
176+
// Double backslash - the backslash is escaped, so current char is not
177+
}
178+
156179
if (ch === CLOSE_PAREN) {
157180
depth++;
158181
} else if (ch === OPEN_PAREN) {
159182
// Check what's before this '('
160-
const prevCh = input.peek(offset - 1);
183+
const beforeParen = input.peek(offset - 1);
161184

162185
// A '(' starts a ParenExpr if it's preceded by:
163186
// - EOF or whitespace (e.g., "(hello)" or "test (hello)")
164187
// - '-' for negation (e.g., "-(hello)")
165188
// - ':' for prefix values (e.g., "repo:(foo or bar)")
166189
const isDefinitelyParenExprStart =
167-
prevCh === EOF ||
168-
isWhitespace(prevCh) ||
169-
prevCh === DASH ||
170-
prevCh === COLON;
190+
beforeParen === EOF ||
191+
isWhitespace(beforeParen) ||
192+
beforeParen === DASH ||
193+
beforeParen === COLON;
171194

172195
// Special case: '(' preceded by '(' could be nested ParenExprs like "((hello))"
173196
// BUT it could also be part of a word like "test((nested))"
174197
// To distinguish: if prev is '(', check what's before THAT '('
175198
let isParenExprStart = isDefinitelyParenExprStart;
176-
if (!isParenExprStart && prevCh === OPEN_PAREN) {
199+
if (!isParenExprStart && beforeParen === OPEN_PAREN) {
177200
// Check what's before the previous '('
178201
const prevPrevCh = input.peek(offset - 2);
179202
// Only count as ParenExpr if the preceding '(' is also at a token boundary
@@ -192,7 +215,7 @@ function hasUnmatchedOpenParen(input: InputStream): boolean {
192215
return true;
193216
}
194217
}
195-
// If prevCh is something else, this '(' is part of a word like "test()"
218+
// If beforeParen is something else, this '(' is part of a word like "test()"
196219
// Don't count it in our depth tracking
197220
}
198221
offset--;

packages/queryLanguage/test/parenthesis.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,12 @@ file:(test search
148148

149149
==>
150150

151-
Program(AndExpr(PrefixExpr(FileExpr),Term))
151+
Program(AndExpr(PrefixExpr(FileExpr),Term))
152+
153+
# Grouped file filter with parenthesis
154+
155+
( file:\(pr )
156+
157+
==>
158+
159+
Program(ParenExpr(PrefixExpr(FileExpr)))

0 commit comments

Comments
 (0)