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 }
// Modifiers
ArchivedExpr { archivedKw value }
ForkExpr { forkKw value }
VisibilityExpr { visibilityKw value }
ArchivedExpr { archivedKw archivedValue }
ForkExpr { forkKw forkValue }
VisibilityExpr { visibilityKw visibilityValue }
archivedValue { "yes" | "no" | "only" }
forkValue { "yes" | "no" | "only" }
visibilityValue { "public" | "private" | "any" }
Term { quotedString | word }

View file

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

View file

@ -16,57 +16,53 @@ export enum SearchPrefix {
sym = "sym:",
content = "content:",
archived = "archived:",
case = "case:",
fork = "fork:",
public = "public:",
visibility = "visibility:",
context = "context:",
}
export const publicModeSuggestions: Suggestion[] = [
export const visibilityModeSuggestions: Suggestion[] = [
{
value: "yes",
value: "public",
description: "Only include results from public repositories."
},
{
value: "no",
value: "private",
description: "Only include results from private repositories."
},
{
value: "any",
description: "Include results from both public and private repositories (default)."
},
];
export const forkModeSuggestions: Suggestion[] = [
{
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."
},
{
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[] = [
{
value: "yes",
description: "Only include results in archived repositories."
description: "Include results from archived repositories (default)."
},
{
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 {
archivedModeSuggestions,
caseModeSuggestions,
forkModeSuggestions,
publicModeSuggestions,
visibilityModeSuggestions,
} from "./constants";
import { IconType } from "react-icons/lib";
import { VscFile, VscFilter, VscRepo, VscSymbolMisc } from "react-icons/vsc";
@ -32,9 +31,8 @@ export type SuggestionMode =
"archived" |
"file" |
"language" |
"case" |
"fork" |
"public" |
"visibility" |
"revision" |
"symbol" |
"content" |
@ -137,9 +135,9 @@ const SearchSuggestionsBox = forwardRef(({
DefaultIcon?: IconType
} => {
switch (suggestionMode) {
case "public":
case "visibility":
return {
list: publicModeSuggestions,
list: visibilityModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(),
}
case "fork":
@ -147,11 +145,6 @@ const SearchSuggestionsBox = forwardRef(({
list: forkModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(),
}
case "case":
return {
list: caseModeSuggestions,
onSuggestionClicked: createOnSuggestionClickedHandler(),
}
case "archived":
return {
list: archivedModeSuggestions,

View file

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

View file

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

View file

@ -47,7 +47,7 @@ export const zoekt = () => {
// Check for prefixes first
// 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();
}

View file

@ -91,34 +91,7 @@ export const POST = async (request: NextRequest) => {
console.log(JSON.stringify(zoektQuery, null, 2));
const searchRequest: SearchRequest = {
query: {
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,
// }
// }
// ]
// }
// },
query: zoektQuery,
opts: {
chunk_matches: true,
max_match_display_count: matches,

View file

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