mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-14 13:25:21 +00:00
further wip on query language
This commit is contained in:
parent
ac9d05a262
commit
b966b63976
11 changed files with 369 additions and 400 deletions
|
|
@ -4,3 +4,4 @@ type Tree = ReturnType<typeof parser.parse>;
|
||||||
type SyntaxNode = Tree['topNode'];
|
type SyntaxNode = Tree['topNode'];
|
||||||
export type { Tree, SyntaxNode };
|
export type { Tree, SyntaxNode };
|
||||||
export * from "./parser";
|
export * from "./parser";
|
||||||
|
export * from "./parser.terms";
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
export const
|
export const
|
||||||
negate = 24,
|
negate = 21,
|
||||||
Program = 1,
|
Program = 1,
|
||||||
OrExpr = 2,
|
OrExpr = 2,
|
||||||
AndExpr = 3,
|
AndExpr = 3,
|
||||||
NegateExpr = 4,
|
NegateExpr = 4,
|
||||||
PrefixExpr = 5,
|
PrefixExpr = 5,
|
||||||
ParenExpr = 19,
|
ArchivedExpr = 6,
|
||||||
Term = 20
|
RevisionExpr = 7,
|
||||||
|
ContentExpr = 8,
|
||||||
|
FileExpr = 9,
|
||||||
|
ForkExpr = 10,
|
||||||
|
VisibilityExpr = 11,
|
||||||
|
RepoExpr = 12,
|
||||||
|
LangExpr = 13,
|
||||||
|
SymExpr = 14,
|
||||||
|
RepoSetExpr = 15,
|
||||||
|
ParenExpr = 16,
|
||||||
|
Term = 17
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -32,21 +32,31 @@ NegateExpr { !negate negate (PrefixExpr | ParenExpr) }
|
||||||
ParenExpr { "(" query ")" }
|
ParenExpr { "(" query ")" }
|
||||||
|
|
||||||
PrefixExpr {
|
PrefixExpr {
|
||||||
ArchivedExpr { archivedKw value } |
|
ArchivedExpr |
|
||||||
BranchExpr { branchKw value } |
|
RevisionExpr |
|
||||||
ContentExpr { contentKw value } |
|
ContentExpr |
|
||||||
CaseExpr { caseKw value } |
|
FileExpr |
|
||||||
FileExpr { fileKw value } |
|
ForkExpr |
|
||||||
ForkExpr { forkKw value } |
|
VisibilityExpr |
|
||||||
PublicExpr { publicKw value } |
|
RepoExpr |
|
||||||
RepoExpr { repoKw value } |
|
LangExpr |
|
||||||
RegexExpr { regexKw value } |
|
SymExpr |
|
||||||
LangExpr { langKw value } |
|
RepoSetExpr
|
||||||
SymExpr { symKw value } |
|
|
||||||
TypeExpr { typeKw value } |
|
|
||||||
RepoSetExpr { reposetKw value }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RevisionExpr { revisionKw value }
|
||||||
|
ContentExpr { contentKw value }
|
||||||
|
FileExpr { fileKw value }
|
||||||
|
RepoExpr { repoKw value }
|
||||||
|
LangExpr { langKw value }
|
||||||
|
SymExpr { symKw value }
|
||||||
|
RepoSetExpr { reposetKw value }
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
ArchivedExpr { archivedKw value }
|
||||||
|
ForkExpr { forkKw value }
|
||||||
|
VisibilityExpr { visibilityKw value }
|
||||||
|
|
||||||
|
|
||||||
Term { quotedString | word }
|
Term { quotedString | word }
|
||||||
|
|
||||||
|
|
@ -56,17 +66,14 @@ value { quotedString | word }
|
||||||
|
|
||||||
@tokens {
|
@tokens {
|
||||||
archivedKw { "archived:" }
|
archivedKw { "archived:" }
|
||||||
branchKw { "branch:" | "b:" }
|
revisionKw { "rev:" }
|
||||||
contentKw { "content:" | "c:" }
|
contentKw { "content:" | "c:" }
|
||||||
caseKw { "case:" }
|
|
||||||
fileKw { "file:" | "f:" }
|
fileKw { "file:" | "f:" }
|
||||||
forkKw { "fork:" }
|
forkKw { "fork:" }
|
||||||
publicKw { "public:" }
|
visibilityKw { "visibility:" }
|
||||||
repoKw { "repo:" | "r:" }
|
repoKw { "repo:" | "r:" }
|
||||||
regexKw { "regex:" }
|
|
||||||
langKw { "lang:" }
|
langKw { "lang:" }
|
||||||
symKw { "sym:" }
|
symKw { "sym:" }
|
||||||
typeKw { "type:" | "t:" }
|
|
||||||
reposetKw { "reposet:" }
|
reposetKw { "reposet:" }
|
||||||
|
|
||||||
or { "or" ![a-zA-Z0-9_] }
|
or { "or" ![a-zA-Z0-9_] }
|
||||||
|
|
@ -81,9 +88,9 @@ value { quotedString | word }
|
||||||
|
|
||||||
@precedence {
|
@precedence {
|
||||||
quotedString,
|
quotedString,
|
||||||
archivedKw, branchKw, contentKw, caseKw, fileKw,
|
archivedKw, revisionKw, contentKw, fileKw,
|
||||||
forkKw, publicKw, repoKw, regexKw, langKw,
|
forkKw, visibilityKw, repoKw, langKw,
|
||||||
symKw, typeKw, reposetKw, or,
|
symKw, reposetKw, or,
|
||||||
word
|
word
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { negate } from "./parser.terms";
|
||||||
|
|
||||||
// External tokenizer for negation
|
// External tokenizer for negation
|
||||||
// Only tokenizes `-` as negate when followed by a prefix keyword or `(`
|
// Only tokenizes `-` as negate when followed by a prefix keyword or `(`
|
||||||
export const negateToken = new ExternalTokenizer((input, stack) => {
|
export const negateToken = new ExternalTokenizer((input) => {
|
||||||
if (input.next !== 45 /* '-' */) return; // Not a dash
|
if (input.next !== 45 /* '-' */) return; // Not a dash
|
||||||
|
|
||||||
const startPos = input.pos;
|
const startPos = input.pos;
|
||||||
|
|
@ -25,24 +25,22 @@ export const negateToken = new ExternalTokenizer((input, stack) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if followed by a prefix keyword (by checking for keyword followed by colon)
|
// Check if followed by a prefix keyword (by checking for keyword followed by colon)
|
||||||
// We need to look ahead to find the colon
|
// Look ahead until we hit a delimiter or colon
|
||||||
const checkPos = input.pos;
|
const checkPos = input.pos;
|
||||||
let foundColon = false;
|
let foundColon = false;
|
||||||
let charCount = 0;
|
|
||||||
|
|
||||||
// Look ahead up to 10 characters to find a colon
|
// Look ahead until we hit a delimiter or colon
|
||||||
while (charCount < 10 && ch >= 0) {
|
while (ch >= 0) {
|
||||||
if (ch === 58 /* ':' */) {
|
if (ch === 58 /* ':' */) {
|
||||||
foundColon = true;
|
foundColon = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Hit a delimiter (whitespace, paren, or quote) - not a prefix keyword
|
||||||
if (ch === 32 || ch === 9 || ch === 10 || ch === 40 || ch === 41 || ch === 34) {
|
if (ch === 32 || ch === 9 || ch === 10 || ch === 40 || ch === 41 || ch === 34) {
|
||||||
// Hit whitespace, paren, or quote - not a prefix
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
input.advance();
|
input.advance();
|
||||||
ch = input.next;
|
ch = input.next;
|
||||||
charCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset position
|
// Reset position
|
||||||
|
|
|
||||||
|
|
@ -62,21 +62,13 @@ Program(NegateExpr(PrefixExpr(LangExpr)))
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(ContentExpr)))
|
Program(NegateExpr(PrefixExpr(ContentExpr)))
|
||||||
|
|
||||||
# Negate branch prefix
|
# Negate revision prefix
|
||||||
|
|
||||||
-branch:develop
|
-rev:develop
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(BranchExpr)))
|
Program(NegateExpr(PrefixExpr(RevisionExpr)))
|
||||||
|
|
||||||
# Negate case prefix
|
|
||||||
|
|
||||||
-case:yes
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(CaseExpr)))
|
|
||||||
|
|
||||||
# Negate archived prefix
|
# Negate archived prefix
|
||||||
|
|
||||||
|
|
@ -94,13 +86,13 @@ Program(NegateExpr(PrefixExpr(ArchivedExpr)))
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(ForkExpr)))
|
Program(NegateExpr(PrefixExpr(ForkExpr)))
|
||||||
|
|
||||||
# Negate public prefix
|
# Negate visibility prefix
|
||||||
|
|
||||||
-public:no
|
-visibility:any
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(PublicExpr)))
|
Program(NegateExpr(PrefixExpr(VisibilityExpr)))
|
||||||
|
|
||||||
# Negate symbol prefix
|
# Negate symbol prefix
|
||||||
|
|
||||||
|
|
@ -110,22 +102,6 @@ Program(NegateExpr(PrefixExpr(PublicExpr)))
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(SymExpr)))
|
Program(NegateExpr(PrefixExpr(SymExpr)))
|
||||||
|
|
||||||
# Negate type prefix
|
|
||||||
|
|
||||||
-type:repo
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(TypeExpr)))
|
|
||||||
|
|
||||||
# Negate regex prefix
|
|
||||||
|
|
||||||
-regex:test.*
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(RegexExpr)))
|
|
||||||
|
|
||||||
# Negate parentheses
|
# Negate parentheses
|
||||||
|
|
||||||
-(test)
|
-(test)
|
||||||
|
|
@ -222,14 +198,6 @@ Program(NegateExpr(PrefixExpr(FileExpr)))
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(RepoExpr)))
|
Program(NegateExpr(PrefixExpr(RepoExpr)))
|
||||||
|
|
||||||
# Negate short form branch
|
|
||||||
|
|
||||||
-b:main
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(BranchExpr)))
|
|
||||||
|
|
||||||
# Negate short form content
|
# Negate short form content
|
||||||
|
|
||||||
-c:console
|
-c:console
|
||||||
|
|
@ -238,14 +206,6 @@ Program(NegateExpr(PrefixExpr(BranchExpr)))
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(ContentExpr)))
|
Program(NegateExpr(PrefixExpr(ContentExpr)))
|
||||||
|
|
||||||
# Negate short form type
|
|
||||||
|
|
||||||
-t:file
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(NegateExpr(PrefixExpr(TypeExpr)))
|
|
||||||
|
|
||||||
# Negate with prefix in quotes
|
# Negate with prefix in quotes
|
||||||
|
|
||||||
-file:"test file.js"
|
-file:"test file.js"
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,13 @@ repo:project1 or repo:project2
|
||||||
|
|
||||||
Program(OrExpr(PrefixExpr(RepoExpr),PrefixExpr(RepoExpr)))
|
Program(OrExpr(PrefixExpr(RepoExpr),PrefixExpr(RepoExpr)))
|
||||||
|
|
||||||
# OR with branch prefixes
|
# OR with revision prefixes
|
||||||
|
|
||||||
branch:main or branch:develop
|
rev:main or rev:develop
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(OrExpr(PrefixExpr(BranchExpr),PrefixExpr(BranchExpr)))
|
Program(OrExpr(PrefixExpr(RevisionExpr),PrefixExpr(RevisionExpr)))
|
||||||
|
|
||||||
# OR with lang prefixes
|
# OR with lang prefixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,21 +46,13 @@ c:console.log
|
||||||
|
|
||||||
Program(PrefixExpr(ContentExpr))
|
Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
# Branch prefix
|
# Revision prefix
|
||||||
|
|
||||||
branch:main
|
rev:main
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(PrefixExpr(BranchExpr))
|
Program(PrefixExpr(RevisionExpr))
|
||||||
|
|
||||||
# Branch prefix short form
|
|
||||||
|
|
||||||
b:develop
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(BranchExpr))
|
|
||||||
|
|
||||||
# Lang prefix
|
# Lang prefix
|
||||||
|
|
||||||
|
|
@ -70,14 +62,6 @@ lang:typescript
|
||||||
|
|
||||||
Program(PrefixExpr(LangExpr))
|
Program(PrefixExpr(LangExpr))
|
||||||
|
|
||||||
# Case prefix
|
|
||||||
|
|
||||||
case:yes
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(CaseExpr))
|
|
||||||
|
|
||||||
# Archived prefix
|
# Archived prefix
|
||||||
|
|
||||||
archived:no
|
archived:no
|
||||||
|
|
@ -94,13 +78,13 @@ fork:yes
|
||||||
|
|
||||||
Program(PrefixExpr(ForkExpr))
|
Program(PrefixExpr(ForkExpr))
|
||||||
|
|
||||||
# Public prefix
|
# Visibility prefix - public
|
||||||
|
|
||||||
public:yes
|
visibility:public
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(PrefixExpr(PublicExpr))
|
Program(PrefixExpr(VisibilityExpr))
|
||||||
|
|
||||||
# Symbol prefix
|
# Symbol prefix
|
||||||
|
|
||||||
|
|
@ -110,30 +94,6 @@ sym:MyClass
|
||||||
|
|
||||||
Program(PrefixExpr(SymExpr))
|
Program(PrefixExpr(SymExpr))
|
||||||
|
|
||||||
# Type prefix
|
|
||||||
|
|
||||||
type:file
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(TypeExpr))
|
|
||||||
|
|
||||||
# Type prefix short form
|
|
||||||
|
|
||||||
t:repo
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(TypeExpr))
|
|
||||||
|
|
||||||
# Regex prefix
|
|
||||||
|
|
||||||
regex:test.*
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(RegexExpr))
|
|
||||||
|
|
||||||
# RepoSet prefix
|
# RepoSet prefix
|
||||||
|
|
||||||
reposet:repo1,repo2
|
reposet:repo1,repo2
|
||||||
|
|
@ -214,21 +174,13 @@ content:hello
|
||||||
|
|
||||||
Program(PrefixExpr(ContentExpr))
|
Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
# Branch with slashes
|
# Revision with slashes
|
||||||
|
|
||||||
branch:feature/new-feature
|
rev:feature/new-feature
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(PrefixExpr(BranchExpr))
|
Program(PrefixExpr(RevisionExpr))
|
||||||
|
|
||||||
# Case values
|
|
||||||
|
|
||||||
case:auto
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(CaseExpr))
|
|
||||||
|
|
||||||
# RepoSet with multiple repos
|
# RepoSet with multiple repos
|
||||||
|
|
||||||
|
|
@ -246,14 +198,6 @@ sym:package.Class.method
|
||||||
|
|
||||||
Program(PrefixExpr(SymExpr))
|
Program(PrefixExpr(SymExpr))
|
||||||
|
|
||||||
# Type variations
|
|
||||||
|
|
||||||
type:filename
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(TypeExpr))
|
|
||||||
|
|
||||||
# Lang with various languages
|
# Lang with various languages
|
||||||
|
|
||||||
lang:python
|
lang:python
|
||||||
|
|
@ -278,21 +222,13 @@ fork:no
|
||||||
|
|
||||||
Program(PrefixExpr(ForkExpr))
|
Program(PrefixExpr(ForkExpr))
|
||||||
|
|
||||||
# Public values
|
# Visibility prefix - private
|
||||||
|
|
||||||
public:no
|
visibility:private
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(PrefixExpr(PublicExpr))
|
Program(PrefixExpr(VisibilityExpr))
|
||||||
|
|
||||||
# Regex with complex pattern
|
|
||||||
|
|
||||||
regex:\w+\s*=\s*\d+
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(RegexExpr))
|
|
||||||
|
|
||||||
# File with dashes
|
# File with dashes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,13 @@ content:"console.log"
|
||||||
|
|
||||||
Program(PrefixExpr(ContentExpr))
|
Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
# Quoted string in branch prefix
|
# Quoted string in revision prefix
|
||||||
|
|
||||||
branch:"feature/my feature"
|
rev:"feature/my feature"
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
Program(PrefixExpr(BranchExpr))
|
Program(PrefixExpr(RevisionExpr))
|
||||||
|
|
||||||
# Multiple quoted strings
|
# Multiple quoted strings
|
||||||
|
|
||||||
|
|
@ -286,22 +286,6 @@ content:"TODO: fix this"
|
||||||
|
|
||||||
Program(PrefixExpr(ContentExpr))
|
Program(PrefixExpr(ContentExpr))
|
||||||
|
|
||||||
# Regex prefix with quoted pattern
|
|
||||||
|
|
||||||
regex:"func\\s+\\w+"
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(RegexExpr))
|
|
||||||
|
|
||||||
# Case prefix with quoted value
|
|
||||||
|
|
||||||
case:"yes"
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(PrefixExpr(CaseExpr))
|
|
||||||
|
|
||||||
# Quoted string with at symbol
|
# Quoted string with at symbol
|
||||||
|
|
||||||
"@decorator"
|
"@decorator"
|
||||||
|
|
@ -486,9 +470,9 @@ Program(AndExpr(Term,PrefixExpr(FileExpr)))
|
||||||
|
|
||||||
Program(Term)
|
Program(Term)
|
||||||
|
|
||||||
# Quoted branch prefix
|
# Quoted revision prefix
|
||||||
|
|
||||||
"branch:main"
|
"rev:main"
|
||||||
|
|
||||||
==>
|
==>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { PrismaClient, Repo } from '@sourcebot/db';
|
||||||
import { createLogger, env } from '@sourcebot/shared';
|
import { createLogger, env } from '@sourcebot/shared';
|
||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { parser } from '@sourcebot/query-language';
|
import { parser as _parser } from '@sourcebot/query-language';
|
||||||
import { transformToZoektQuery } from './transformer';
|
import { transformToZoektQuery } from './transformer';
|
||||||
|
|
||||||
const logger = createLogger('streamSearchApi');
|
const logger = createLogger('streamSearchApi');
|
||||||
|
|
@ -69,13 +69,38 @@ export const POST = async (request: NextRequest) => {
|
||||||
|
|
||||||
const { query, matches, contextLines, whole } = parsed.data;
|
const { query, matches, contextLines, whole } = parsed.data;
|
||||||
|
|
||||||
|
const isCaseSensitivityEnabled = false;
|
||||||
|
const isRegexEnabled = false;
|
||||||
|
|
||||||
|
const parser = _parser.configure({
|
||||||
|
strict: true,
|
||||||
|
})
|
||||||
|
|
||||||
const tree = parser.parse(query);
|
const tree = parser.parse(query);
|
||||||
const zoektQuery = transformToZoektQuery(tree, query);
|
const zoektQuery = transformToZoektQuery({
|
||||||
|
tree,
|
||||||
|
input: query,
|
||||||
|
isCaseSensitivityEnabled,
|
||||||
|
isRegexEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
console.log(JSON.stringify(zoektQuery, null, 2));
|
console.log(JSON.stringify(zoektQuery, null, 2));
|
||||||
|
|
||||||
const searchRequest: SearchRequest = {
|
const searchRequest: SearchRequest = {
|
||||||
query: zoektQuery,
|
query: {
|
||||||
|
and: {
|
||||||
|
children: [
|
||||||
|
zoektQuery,
|
||||||
|
// {
|
||||||
|
// raw_config: {
|
||||||
|
// flags: [
|
||||||
|
// 'FLAG_NO_FORKS',
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
// query: {
|
// query: {
|
||||||
// and: {
|
// and: {
|
||||||
// // @todo: we should use repo_ids to filter out repositories that the user
|
// // @todo: we should use repo_ids to filter out repositories that the user
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,68 @@
|
||||||
import { Tree, SyntaxNode } from "@sourcebot/query-language";
|
import { Tree, SyntaxNode } from "@sourcebot/query-language";
|
||||||
import { Q } from '@/proto/zoekt/webserver/v1/Q';
|
import { Q } from '@/proto/zoekt/webserver/v1/Q';
|
||||||
|
import {
|
||||||
|
Program,
|
||||||
|
AndExpr,
|
||||||
|
OrExpr,
|
||||||
|
NegateExpr,
|
||||||
|
ParenExpr,
|
||||||
|
PrefixExpr,
|
||||||
|
Term,
|
||||||
|
FileExpr,
|
||||||
|
RepoExpr,
|
||||||
|
RevisionExpr,
|
||||||
|
ContentExpr,
|
||||||
|
LangExpr,
|
||||||
|
SymExpr,
|
||||||
|
ArchivedExpr,
|
||||||
|
ForkExpr,
|
||||||
|
VisibilityExpr,
|
||||||
|
RepoSetExpr
|
||||||
|
} from '@sourcebot/query-language';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a Lezer parse tree into a Zoekt gRPC query
|
* Transform a Lezer parse tree into a Zoekt gRPC query
|
||||||
*/
|
*/
|
||||||
export function transformToZoektQuery(tree: Tree, input: string): Q {
|
export const transformToZoektQuery = ({
|
||||||
return transformNode(tree.topNode, input);
|
tree,
|
||||||
}
|
input,
|
||||||
|
isCaseSensitivityEnabled,
|
||||||
|
isRegexEnabled,
|
||||||
|
}: {
|
||||||
|
tree: Tree;
|
||||||
|
input: string;
|
||||||
|
isCaseSensitivityEnabled: boolean;
|
||||||
|
isRegexEnabled: boolean;
|
||||||
|
}): Q => {
|
||||||
|
|
||||||
function transformNode(node: SyntaxNode, input: string): Q {
|
const transformNode = (node: SyntaxNode): Q => {
|
||||||
const nodeName = node.type.name;
|
switch (node.type.id) {
|
||||||
|
case Program: {
|
||||||
switch (nodeName) {
|
|
||||||
case "Program": {
|
|
||||||
// Program wraps the actual query - transform its child
|
// Program wraps the actual query - transform its child
|
||||||
const child = node.firstChild;
|
const child = node.firstChild;
|
||||||
if (!child) {
|
if (!child) {
|
||||||
// Empty query - match nothing
|
// Empty query - match nothing
|
||||||
return { const: false, query: "const" };
|
return { const: false, query: "const" };
|
||||||
}
|
}
|
||||||
return transformNode(child, input);
|
return transformNode(child);
|
||||||
}
|
}
|
||||||
case "AndExpr":
|
case AndExpr:
|
||||||
return {
|
return {
|
||||||
and: {
|
and: {
|
||||||
children: getChildren(node).map(c => transformNode(c, input))
|
children: getChildren(node).map(c => transformNode(c))
|
||||||
},
|
},
|
||||||
query: "and"
|
query: "and"
|
||||||
}
|
}
|
||||||
|
|
||||||
case "OrExpr":
|
case OrExpr:
|
||||||
return {
|
return {
|
||||||
or: {
|
or: {
|
||||||
children: getChildren(node).map(c => transformNode(c, input))
|
children: getChildren(node).map(c => transformNode(c))
|
||||||
},
|
},
|
||||||
query: "or"
|
query: "or"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "NegateExpr": {
|
case NegateExpr: {
|
||||||
// Find the child after the negate token
|
// Find the child after the negate token
|
||||||
const negateChild = node.getChild("PrefixExpr") || node.getChild("ParenExpr");
|
const negateChild = node.getChild("PrefixExpr") || node.getChild("ParenExpr");
|
||||||
if (!negateChild) {
|
if (!negateChild) {
|
||||||
|
|
@ -45,30 +70,38 @@ function transformNode(node: SyntaxNode, input: string): Q {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
not: {
|
not: {
|
||||||
child: transformNode(negateChild, input)
|
child: transformNode(negateChild)
|
||||||
},
|
},
|
||||||
query: "not"
|
query: "not"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "ParenExpr": {
|
case ParenExpr: {
|
||||||
// Parentheses just group - transform the inner query
|
// Parentheses just group - transform the inner query
|
||||||
const innerQuery = node.getChild("query") || node.firstChild;
|
const innerQuery = node.getChild("query") || node.firstChild;
|
||||||
if (!innerQuery) {
|
if (!innerQuery) {
|
||||||
return { const: false, query: "const" };
|
return { const: false, query: "const" };
|
||||||
}
|
}
|
||||||
return transformNode(innerQuery, input);
|
return transformNode(innerQuery);
|
||||||
}
|
}
|
||||||
case "PrefixExpr":
|
case PrefixExpr:
|
||||||
// PrefixExpr contains specific prefix types
|
// PrefixExpr contains specific prefix types
|
||||||
return transformPrefixExpr(node, input);
|
return transformPrefixExpr(node);
|
||||||
|
|
||||||
case "Term": {
|
case Term: {
|
||||||
// Plain search term - becomes substring search in content
|
const termText = input.substring(node.from, node.to).replace(/^"|"$/g, '');
|
||||||
const termText = input.substring(node.from, node.to);
|
|
||||||
return {
|
return isRegexEnabled ? {
|
||||||
|
regexp: {
|
||||||
|
regexp: termText,
|
||||||
|
case_sensitive: isCaseSensitivityEnabled,
|
||||||
|
file_name: false,
|
||||||
|
content: true
|
||||||
|
},
|
||||||
|
query: "regexp"
|
||||||
|
} : {
|
||||||
substring: {
|
substring: {
|
||||||
pattern: termText.replace(/^"|"$/g, ''), // Remove quotes if present
|
pattern: termText,
|
||||||
case_sensitive: false,
|
case_sensitive: isCaseSensitivityEnabled,
|
||||||
file_name: false,
|
file_name: false,
|
||||||
content: true
|
content: true
|
||||||
},
|
},
|
||||||
|
|
@ -76,43 +109,43 @@ function transformNode(node: SyntaxNode, input: string): Q {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
console.warn(`Unhandled node type: ${nodeName}`);
|
console.warn(`Unhandled node type: ${node.type.name} (id: ${node.type.id})`);
|
||||||
return { const: true, query: "const" };
|
return { const: true, query: "const" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
const transformPrefixExpr = (node: SyntaxNode): Q => {
|
||||||
// Find which specific prefix type this is
|
// Find which specific prefix type this is
|
||||||
const prefixNode = node.firstChild;
|
const prefixNode = node.firstChild;
|
||||||
if (!prefixNode) {
|
if (!prefixNode) {
|
||||||
throw new Error("PrefixExpr has no child");
|
throw new Error("PrefixExpr has no child");
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefixType = prefixNode.type.name;
|
const prefixTypeId = prefixNode.type.id;
|
||||||
|
|
||||||
// Extract the full text (e.g., "file:test.js") and split on the colon
|
// Extract the full text (e.g., "file:test.js") and split on the colon
|
||||||
const fullText = input.substring(prefixNode.from, prefixNode.to);
|
const fullText = input.substring(prefixNode.from, prefixNode.to);
|
||||||
const colonIndex = fullText.indexOf(':');
|
const colonIndex = fullText.indexOf(':');
|
||||||
if (colonIndex === -1) {
|
if (colonIndex === -1) {
|
||||||
throw new Error(`${prefixType} missing colon`);
|
throw new Error(`${prefixNode.type.name} missing colon`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the value part after the colon and remove quotes if present
|
// Get the value part after the colon and remove quotes if present
|
||||||
const value = fullText.substring(colonIndex + 1).replace(/^"|"$/g, '');
|
const value = fullText.substring(colonIndex + 1).replace(/^"|"$/g, '');
|
||||||
|
|
||||||
switch (prefixType) {
|
switch (prefixTypeId) {
|
||||||
case "FileExpr":
|
case FileExpr:
|
||||||
return {
|
return {
|
||||||
substring: {
|
substring: {
|
||||||
pattern: value,
|
pattern: value,
|
||||||
case_sensitive: false,
|
case_sensitive: isCaseSensitivityEnabled,
|
||||||
file_name: true,
|
file_name: true,
|
||||||
content: false
|
content: false
|
||||||
},
|
},
|
||||||
query: "substring"
|
query: "substring"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "RepoExpr":
|
case RepoExpr:
|
||||||
return {
|
return {
|
||||||
repo: {
|
repo: {
|
||||||
regexp: value
|
regexp: value
|
||||||
|
|
@ -120,7 +153,7 @@ function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
||||||
query: "repo"
|
query: "repo"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "BranchExpr":
|
case RevisionExpr:
|
||||||
return {
|
return {
|
||||||
branch: {
|
branch: {
|
||||||
pattern: value,
|
pattern: value,
|
||||||
|
|
@ -129,32 +162,18 @@ function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
||||||
query: "branch"
|
query: "branch"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "ContentExpr":
|
case ContentExpr:
|
||||||
return {
|
return {
|
||||||
substring: {
|
substring: {
|
||||||
pattern: value,
|
pattern: value,
|
||||||
case_sensitive: false,
|
case_sensitive: isCaseSensitivityEnabled,
|
||||||
file_name: false,
|
file_name: false,
|
||||||
content: true
|
content: true
|
||||||
},
|
},
|
||||||
query: "substring"
|
query: "substring"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "CaseExpr": {
|
case LangExpr:
|
||||||
// case:yes/no wraps the next term with case sensitivity
|
|
||||||
const caseValue = value.toLowerCase();
|
|
||||||
const isCaseSensitive = caseValue === "yes" || caseValue === "true";
|
|
||||||
return {
|
|
||||||
substring: {
|
|
||||||
pattern: value,
|
|
||||||
case_sensitive: isCaseSensitive,
|
|
||||||
file_name: false,
|
|
||||||
content: true
|
|
||||||
},
|
|
||||||
query: "substring"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "LangExpr":
|
|
||||||
return {
|
return {
|
||||||
language: {
|
language: {
|
||||||
language: value
|
language: value
|
||||||
|
|
@ -162,14 +181,14 @@ function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
||||||
query: "language"
|
query: "language"
|
||||||
};
|
};
|
||||||
|
|
||||||
case "SymExpr":
|
case SymExpr:
|
||||||
// Symbol search wraps a pattern
|
// Symbol search wraps a pattern
|
||||||
return {
|
return {
|
||||||
symbol: {
|
symbol: {
|
||||||
expr: {
|
expr: {
|
||||||
substring: {
|
substring: {
|
||||||
pattern: value,
|
pattern: value,
|
||||||
case_sensitive: false,
|
case_sensitive: isCaseSensitivityEnabled,
|
||||||
file_name: false,
|
file_name: false,
|
||||||
content: true
|
content: true
|
||||||
},
|
},
|
||||||
|
|
@ -178,28 +197,54 @@ function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
||||||
},
|
},
|
||||||
query: "symbol"
|
query: "symbol"
|
||||||
};
|
};
|
||||||
case "RegexExpr":
|
|
||||||
|
case VisibilityExpr: {
|
||||||
|
const visibilityValue = value.toLowerCase();
|
||||||
|
const flags: ('FLAG_ONLY_PUBLIC' | 'FLAG_ONLY_PRIVATE')[] = [];
|
||||||
|
|
||||||
|
if (visibilityValue === 'public') {
|
||||||
|
flags.push('FLAG_ONLY_PUBLIC');
|
||||||
|
} else if (visibilityValue === 'private') {
|
||||||
|
flags.push('FLAG_ONLY_PRIVATE');
|
||||||
|
}
|
||||||
|
// 'any' means no filter
|
||||||
|
|
||||||
return {
|
return {
|
||||||
regexp: {
|
raw_config: {
|
||||||
regexp: value,
|
flags
|
||||||
case_sensitive: false,
|
|
||||||
file_name: false,
|
|
||||||
content: true
|
|
||||||
},
|
},
|
||||||
query: "regexp"
|
query: "raw_config"
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// @todo: handle this
|
// @todo: handle this
|
||||||
case "ArchivedExpr":
|
case ArchivedExpr: {
|
||||||
case "ForkExpr":
|
const archivedValue = value.toLowerCase();
|
||||||
case "PublicExpr":
|
const flags: ('FLAG_ONLY_ARCHIVED' | 'FLAG_NO_ARCHIVED')[] = [];
|
||||||
|
|
||||||
|
if (archivedValue === 'yes') {
|
||||||
|
// 'yes' means include archived repositories (default)
|
||||||
|
} else if (archivedValue === 'no') {
|
||||||
|
flags.push('FLAG_NO_ARCHIVED');
|
||||||
|
} else if (archivedValue === 'only') {
|
||||||
|
flags.push('FLAG_ONLY_ARCHIVED');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw_config: {
|
||||||
|
flags
|
||||||
|
},
|
||||||
|
query: "raw_config"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case ForkExpr:
|
||||||
// These are repo metadata filters
|
// These are repo metadata filters
|
||||||
// They need to be handled via repo filters in Zoekt
|
// They need to be handled via repo filters in Zoekt
|
||||||
// For now, return a const query (you might need custom handling)
|
// For now, return a const query (you might need custom handling)
|
||||||
console.warn(`${prefixType} not yet implemented`);
|
console.warn(`${prefixNode.type.name} not yet implemented`);
|
||||||
return { const: true, query: "const" };
|
return { const: true, query: "const" };
|
||||||
|
|
||||||
case "RepoSetExpr": {
|
case RepoSetExpr: {
|
||||||
return {
|
return {
|
||||||
repo_set: {
|
repo_set: {
|
||||||
set: value.split(',').reduce((acc, s) => {
|
set: value.split(',').reduce((acc, s) => {
|
||||||
|
|
@ -211,11 +256,14 @@ function transformPrefixExpr(node: SyntaxNode, input: string): Q {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown prefix type: ${prefixType}`);
|
throw new Error(`Unknown prefix type: ${prefixNode.type.name} (id: ${prefixTypeId})`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformNode(tree.topNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChildren(node: SyntaxNode): SyntaxNode[] {
|
const getChildren = (node: SyntaxNode): SyntaxNode[] => {
|
||||||
const children: SyntaxNode[] = [];
|
const children: SyntaxNode[] = [];
|
||||||
let child = node.firstChild;
|
let child = node.firstChild;
|
||||||
while (child) {
|
while (child) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue