Skip to content

Commit 8cc896d

Browse files
committed
[search] support uppercase OR/AND
1 parent ee90edc commit 8cc896d

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

docs/docs/features/search/syntax-reference.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ Queries consist of space-separated regular expressions. Wrapping expressions in
1515
| `foo bar` | Match files with regex `/foo/` **and** `/bar/` |
1616
| `"foo bar"` | Match files with regex `/foo bar/` |
1717

18-
Multiple expressions can be or'd together with `or`, negated with `-`, or grouped with `()`.
18+
Multiple expressions can be or'd together with `or` (or `OR`), negated with `-`, or grouped with `()`. Both lowercase and uppercase boolean operators are supported.
1919

2020
| Example | Explanation |
2121
| :--- | :--- |
2222
| `foo or bar` | Match files with regex `/foo/` **or** `/bar/` |
23+
| `foo OR bar` | Same as above - uppercase `OR` is also supported |
2324
| `foo -bar` | Match files with regex `/foo/` but **not** `/bar/` |
2425
| `foo (bar or baz)` | Match files with regex `/foo/` **and** either `/bar/` **or** `/baz/` |
26+
| `(file:yarn.lock OR file:package.json)` | Match files named `yarn.lock` **or** `package.json` |
2527

2628
Expressions can be prefixed with certain keywords to modify search behavior. Some keywords can be negated using the `-` prefix.
2729

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
// Simple function to test the query transformation logic
4+
// We'll extract just the normalization part to test it separately
5+
const normalizeQueryOperators = (query: string): string => {
6+
return query
7+
// Replace standalone uppercase OR with lowercase or
8+
.replace(/\bOR\b/g, 'or')
9+
// Replace standalone uppercase AND with lowercase and (though AND is implicit in Zoekt)
10+
.replace(/\bAND\b/g, 'and');
11+
};
12+
13+
describe('Query transformation', () => {
14+
describe('normalizeQueryOperators', () => {
15+
it('should convert uppercase OR to lowercase or', () => {
16+
expect(normalizeQueryOperators('file:yarn.lock OR file:package.json'))
17+
.toBe('file:yarn.lock or file:package.json');
18+
});
19+
20+
it('should convert uppercase AND to lowercase and', () => {
21+
expect(normalizeQueryOperators('foo AND bar'))
22+
.toBe('foo and bar');
23+
});
24+
25+
it('should handle parenthesized expressions', () => {
26+
expect(normalizeQueryOperators('(file:yarn.lock OR file:package.json)'))
27+
.toBe('(file:yarn.lock or file:package.json)');
28+
});
29+
30+
it('should handle complex queries with multiple operators', () => {
31+
expect(normalizeQueryOperators('(file:*.json OR file:*.lock) AND content:react'))
32+
.toBe('(file:*.json or file:*.lock) and content:react');
33+
});
34+
35+
it('should not affect lowercase operators', () => {
36+
expect(normalizeQueryOperators('file:yarn.lock or file:package.json'))
37+
.toBe('file:yarn.lock or file:package.json');
38+
});
39+
40+
it('should not affect OR/AND when part of other words', () => {
41+
expect(normalizeQueryOperators('ORDER BY something'))
42+
.toBe('ORDER BY something');
43+
44+
expect(normalizeQueryOperators('ANDROID app'))
45+
.toBe('ANDROID app');
46+
});
47+
48+
it('should handle mixed case queries', () => {
49+
expect(normalizeQueryOperators('file:src OR file:test and lang:typescript'))
50+
.toBe('file:src or file:test and lang:typescript');
51+
});
52+
53+
it('should handle multiple ORs and ANDs', () => {
54+
expect(normalizeQueryOperators('A OR B OR C AND D AND E'))
55+
.toBe('A or B or C and D and E');
56+
});
57+
});
58+
});

packages/web/src/features/search/searchApi.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,19 @@ enum zoektPrefixes {
3636
}
3737

3838
const transformZoektQuery = async (query: string, orgId: number): Promise<string | ServiceError> => {
39-
const prevQueryParts = query.split(" ");
39+
// First, normalize boolean operators to lowercase (Zoekt requirement)
40+
// Zoekt only recognizes lowercase 'or' and 'and' operators, but users often expect
41+
// uppercase OR/AND to work. This transformation allows both cases to work.
42+
// Examples:
43+
// - "(file:yarn.lock OR file:package.json)" → "(file:yarn.lock or file:package.json)"
44+
// - "foo AND bar" → "foo and bar" (though AND is implicit in Zoekt)
45+
let normalizedQuery = query
46+
// Replace standalone uppercase OR with lowercase or
47+
.replace(/\bOR\b/g, 'or')
48+
// Replace standalone uppercase AND with lowercase and (though AND is implicit in Zoekt)
49+
.replace(/\bAND\b/g, 'and');
50+
51+
const prevQueryParts = normalizedQuery.split(" ");
4052
const newQueryParts = [];
4153

4254
for (const part of prevQueryParts) {

0 commit comments

Comments
 (0)