Improved type safety / cleanup for query lang

This commit is contained in:
bkellam 2025-11-17 18:09:55 -08:00
parent 69e0a1a101
commit e6859cbf5c
10 changed files with 156 additions and 110 deletions

File diff suppressed because one or more lines are too long

View file

@ -53,10 +53,13 @@ SymExpr { symKw value }
RepoSetExpr { reposetKw value } RepoSetExpr { reposetKw value }
// Modifiers // Modifiers
ArchivedExpr { archivedKw value } ArchivedExpr { archivedKw archivedValue }
ForkExpr { forkKw value } ForkExpr { forkKw forkValue }
VisibilityExpr { visibilityKw value } VisibilityExpr { visibilityKw visibilityValue }
archivedValue { "yes" | "no" | "only" }
forkValue { "yes" | "no" | "only" }
visibilityValue { "public" | "private" | "any" }
Term { quotedString | word } Term { quotedString | word }

View file

@ -62,7 +62,7 @@ lang:typescript
Program(PrefixExpr(LangExpr)) Program(PrefixExpr(LangExpr))
# Archived prefix # Archived prefix - no
archived:no archived:no
@ -70,7 +70,15 @@ archived:no
Program(PrefixExpr(ArchivedExpr)) Program(PrefixExpr(ArchivedExpr))
# Fork prefix # Archived prefix - only
archived:only
==>
Program(PrefixExpr(ArchivedExpr))
# Fork prefix - yes
fork:yes fork:yes
@ -78,6 +86,14 @@ fork:yes
Program(PrefixExpr(ForkExpr)) Program(PrefixExpr(ForkExpr))
# Fork prefix - only
fork:only
==>
Program(PrefixExpr(ForkExpr))
# Visibility prefix - public # Visibility prefix - public
visibility:public visibility:public
@ -206,7 +222,7 @@ lang:python
Program(PrefixExpr(LangExpr)) Program(PrefixExpr(LangExpr))
# Archived values # Archived prefix - yes
archived:yes archived:yes
@ -214,7 +230,15 @@ archived:yes
Program(PrefixExpr(ArchivedExpr)) Program(PrefixExpr(ArchivedExpr))
# Fork values # Archived prefix - invalid value (error case)
archived:invalid
==>
Program(AndExpr(PrefixExpr(ArchivedExpr(⚠)),Term))
# Fork prefix - no
fork:no fork:no
@ -222,6 +246,14 @@ fork:no
Program(PrefixExpr(ForkExpr)) Program(PrefixExpr(ForkExpr))
# Fork prefix - invalid value (error case)
fork:invalid
==>
Program(AndExpr(PrefixExpr(ForkExpr(⚠)),Term))
# Visibility prefix - private # Visibility prefix - private
visibility:private visibility:private
@ -230,6 +262,22 @@ visibility:private
Program(PrefixExpr(VisibilityExpr)) Program(PrefixExpr(VisibilityExpr))
# Visibility prefix - any
visibility:any
==>
Program(PrefixExpr(VisibilityExpr))
# Visibility prefix - invalid value (error case)
visibility:invalid
==>
Program(AndExpr(PrefixExpr(VisibilityExpr(⚠)),Term))
# File with dashes # File with dashes
file:my-component.tsx file:my-component.tsx

View file

@ -16,57 +16,53 @@ export enum SearchPrefix {
sym = "sym:", sym = "sym:",
content = "content:", content = "content:",
archived = "archived:", archived = "archived:",
case = "case:",
fork = "fork:", fork = "fork:",
public = "public:", visibility = "visibility:",
context = "context:", context = "context:",
} }
export const publicModeSuggestions: Suggestion[] = [ export const visibilityModeSuggestions: Suggestion[] = [
{ {
value: "yes", value: "public",
description: "Only include results from public repositories." description: "Only include results from public repositories."
}, },
{ {
value: "no", value: "private",
description: "Only include results from private repositories." description: "Only include results from private repositories."
}, },
{
value: "any",
description: "Include results from both public and private repositories (default)."
},
]; ];
export const forkModeSuggestions: Suggestion[] = [ export const forkModeSuggestions: Suggestion[] = [
{ {
value: "yes", value: "yes",
description: "Include results from forked repositories (default)."
},
{
value: "no",
description: "Exclude results from forked repositories."
},
{
value: "only",
description: "Only include results from forked repositories." description: "Only include results from forked repositories."
}, }
{
value: "no",
description: "Only include results from non-forked repositories."
},
];
export const caseModeSuggestions: Suggestion[] = [
{
value: "auto",
description: "Search patterns are case-insensitive if all characters are lowercase, and case sensitive otherwise (default)."
},
{
value: "yes",
description: "Case sensitive search."
},
{
value: "no",
description: "Case insensitive search."
},
]; ];
export const archivedModeSuggestions: Suggestion[] = [ export const archivedModeSuggestions: Suggestion[] = [
{ {
value: "yes", value: "yes",
description: "Only include results in archived repositories." description: "Include results from archived repositories (default)."
}, },
{ {
value: "no", value: "no",
description: "Only include results in non-archived repositories." description: "Exclude results from archived repositories."
}, },
{
value: "only",
description: "Only include results from archived repositories."
}
]; ];

View file

@ -7,9 +7,8 @@ import Fuse from "fuse.js";
import { forwardRef, Ref, useEffect, useMemo, useState } from "react"; import { forwardRef, Ref, useEffect, useMemo, useState } from "react";
import { import {
archivedModeSuggestions, archivedModeSuggestions,
caseModeSuggestions,
forkModeSuggestions, forkModeSuggestions,
publicModeSuggestions, visibilityModeSuggestions,
} from "./constants"; } from "./constants";
import { IconType } from "react-icons/lib"; import { IconType } from "react-icons/lib";
import { VscFile, VscFilter, VscRepo, VscSymbolMisc } from "react-icons/vsc"; import { VscFile, VscFilter, VscRepo, VscSymbolMisc } from "react-icons/vsc";
@ -32,9 +31,8 @@ export type SuggestionMode =
"archived" | "archived" |
"file" | "file" |
"language" | "language" |
"case" |
"fork" | "fork" |
"public" | "visibility" |
"revision" | "revision" |
"symbol" | "symbol" |
"content" | "content" |
@ -137,9 +135,9 @@ const SearchSuggestionsBox = forwardRef(({
DefaultIcon?: IconType DefaultIcon?: IconType
} => { } => {
switch (suggestionMode) { switch (suggestionMode) {
case "public": case "visibility":
return { return {
list: publicModeSuggestions, list: visibilityModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(), onSuggestionClicked: createOnSuggestionClickedHandler(),
} }
case "fork": case "fork":
@ -147,11 +145,6 @@ const SearchSuggestionsBox = forwardRef(({
list: forkModeSuggestions, list: forkModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(), onSuggestionClicked: createOnSuggestionClickedHandler(),
} }
case "case":
return {
list: caseModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(),
}
case "archived": case "archived":
return { return {
list: archivedModeSuggestions, list: archivedModeSuggestions,

View file

@ -26,7 +26,7 @@ export const useRefineModeSuggestions = () => {
}, },
] : []), ] : []),
{ {
value: SearchPrefix.public, value: SearchPrefix.visibility,
description: "Filter on repository visibility." description: "Filter on repository visibility."
}, },
{ {
@ -86,10 +86,6 @@ export const useRefineModeSuggestions = () => {
value: SearchPrefix.archived, value: SearchPrefix.archived,
description: "Include results from archived repositories.", description: "Include results from archived repositories.",
}, },
{
value: SearchPrefix.case,
description: "Control case-sensitivity of search patterns."
},
{ {
value: SearchPrefix.fork, value: SearchPrefix.fork,
description: "Include only results from forked repositories." description: "Include only results from forked repositories."

View file

@ -70,12 +70,6 @@ export const useSuggestionModeMappings = () => {
SearchPrefix.archived SearchPrefix.archived
] ]
}, },
{
suggestionMode: "case",
prefixes: [
SearchPrefix.case
]
},
{ {
suggestionMode: "fork", suggestionMode: "fork",
prefixes: [ prefixes: [
@ -83,9 +77,9 @@ export const useSuggestionModeMappings = () => {
] ]
}, },
{ {
suggestionMode: "public", suggestionMode: "visibility",
prefixes: [ prefixes: [
SearchPrefix.public SearchPrefix.visibility
] ]
}, },
...(isSearchContextsEnabled ? [ ...(isSearchContextsEnabled ? [

View file

@ -47,7 +47,7 @@ export const zoekt = () => {
// Check for prefixes first // Check for prefixes first
// If these match, we return 'keyword' // If these match, we return 'keyword'
if (stream.match(/(archived:|branch:|b:|rev:|c:|case:|content:|f:|file:|fork:|public:|r:|repo:|regex:|lang:|sym:|t:|type:|context:)/)) { if (stream.match(/(archived:|rev:|content:|f:|file:|fork:|visibility:|r:|repo:|regex:|lang:|sym:|t:|type:|context:)/)) {
return t.keyword.toString(); return t.keyword.toString();
} }

View file

@ -91,34 +91,7 @@ export const POST = async (request: NextRequest) => {
console.log(JSON.stringify(zoektQuery, null, 2)); console.log(JSON.stringify(zoektQuery, null, 2));
const searchRequest: SearchRequest = { const searchRequest: SearchRequest = {
query: { query: zoektQuery,
and: {
children: [
zoektQuery,
// {
// raw_config: {
// flags: [
// 'FLAG_NO_FORKS',
// ]
// }
// }
]
}
},
// query: {
// and: {
// // @todo: we should use repo_ids to filter out repositories that the user
// // has access to (if permission syncing is enabled!).
// children: [
// {
// regexp: {
// regexp: query,
// case_sensitive: true,
// }
// }
// ]
// }
// },
opts: { opts: {
chunk_matches: true, chunk_matches: true,
max_match_display_count: matches, max_match_display_count: matches,

View file

@ -17,9 +17,25 @@ import {
ArchivedExpr, ArchivedExpr,
ForkExpr, ForkExpr,
VisibilityExpr, VisibilityExpr,
RepoSetExpr RepoSetExpr,
} from '@sourcebot/query-language'; } from '@sourcebot/query-language';
type ArchivedValue = 'yes' | 'no' | 'only';
type VisibilityValue = 'public' | 'private' | 'any';
type ForkValue = 'yes' | 'no' | 'only';
const isArchivedValue = (value: string): value is ArchivedValue => {
return value === 'yes' || value === 'no' || value === 'only';
}
const isVisibilityValue = (value: string): value is VisibilityValue => {
return value === 'public' || value === 'private' || value === 'any';
}
const isForkValue = (value: string): value is ForkValue => {
return value === 'yes' || value === 'no' || value === 'only';
}
/** /**
* Transform a Lezer parse tree into a Zoekt gRPC query * Transform a Lezer parse tree into a Zoekt gRPC query
*/ */
@ -199,12 +215,17 @@ export const transformToZoektQuery = ({
}; };
case VisibilityExpr: { case VisibilityExpr: {
const visibilityValue = value.toLowerCase(); const rawValue = value.toLowerCase();
if (!isVisibilityValue(rawValue)) {
throw new Error(`Invalid visibility value: ${rawValue}. Expected 'public', 'private', or 'any'`);
}
const flags: ('FLAG_ONLY_PUBLIC' | 'FLAG_ONLY_PRIVATE')[] = []; const flags: ('FLAG_ONLY_PUBLIC' | 'FLAG_ONLY_PRIVATE')[] = [];
if (visibilityValue === 'public') { if (rawValue === 'public') {
flags.push('FLAG_ONLY_PUBLIC'); flags.push('FLAG_ONLY_PUBLIC');
} else if (visibilityValue === 'private') { } else if (rawValue === 'private') {
flags.push('FLAG_ONLY_PRIVATE'); flags.push('FLAG_ONLY_PRIVATE');
} }
// 'any' means no filter // 'any' means no filter
@ -217,16 +238,20 @@ export const transformToZoektQuery = ({
}; };
} }
// @todo: handle this
case ArchivedExpr: { case ArchivedExpr: {
const archivedValue = value.toLowerCase(); const rawValue = value.toLowerCase();
if (!isArchivedValue(rawValue)) {
throw new Error(`Invalid archived value: ${rawValue}. Expected 'yes', 'no', or 'only'`);
}
const flags: ('FLAG_ONLY_ARCHIVED' | 'FLAG_NO_ARCHIVED')[] = []; const flags: ('FLAG_ONLY_ARCHIVED' | 'FLAG_NO_ARCHIVED')[] = [];
if (archivedValue === 'yes') { if (rawValue === 'yes') {
// 'yes' means include archived repositories (default) // 'yes' means include archived repositories (default)
} else if (archivedValue === 'no') { } else if (rawValue === 'no') {
flags.push('FLAG_NO_ARCHIVED'); flags.push('FLAG_NO_ARCHIVED');
} else if (archivedValue === 'only') { } else if (rawValue === 'only') {
flags.push('FLAG_ONLY_ARCHIVED'); flags.push('FLAG_ONLY_ARCHIVED');
} }
@ -237,12 +262,30 @@ export const transformToZoektQuery = ({
query: "raw_config" query: "raw_config"
}; };
} }
case ForkExpr: case ForkExpr: {
// These are repo metadata filters const rawValue = value.toLowerCase();
// They need to be handled via repo filters in Zoekt
// For now, return a const query (you might need custom handling) if (!isForkValue(rawValue)) {
console.warn(`${prefixNode.type.name} not yet implemented`); throw new Error(`Invalid fork value: ${rawValue}. Expected 'yes', 'no', or 'only'`);
return { const: true, query: "const" }; }
const flags: ('FLAG_ONLY_FORKS' | 'FLAG_NO_FORKS')[] = [];
if (rawValue === 'yes') {
// 'yes' means include forks (default)
} else if (rawValue === 'no') {
flags.push('FLAG_NO_FORKS');
} else if (rawValue === 'only') {
flags.push('FLAG_ONLY_FORKS');
}
return {
raw_config: {
flags
},
query: "raw_config"
};
}
case RepoSetExpr: { case RepoSetExpr: {
return { return {