sourcebot/packages/web/src/features/codeNav/api.ts
Brendan Kellam 92578881df
Some checks are pending
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
Update Roadmap Released / update (push) Waiting to run
chore(web): Scope code nav to current repository by default (#647)
2025-11-30 18:53:09 -08:00

195 lines
No EOL
6 KiB
TypeScript

import 'server-only';
import { sew } from "@/actions";
import { search } from "@/features/search";
import { ServiceError } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { withOptionalAuthV2 } from "@/withAuthV2";
import { SearchResponse } from "../search/types";
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
import { QueryIR } from '../search/ir';
import escapeStringRegexp from "escape-string-regexp";
// The maximum number of matches to return from the search API.
const MAX_REFERENCE_COUNT = 1000;
export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withOptionalAuthV2(async () => {
const {
symbolName,
language,
revisionName = "HEAD",
repoName,
} = props;
const languageFilter = getExpandedLanguageFilter(language);
const query: QueryIR = {
and: {
children: [
{
regexp: {
regexp: `\\b${symbolName}\\b`,
case_sensitive: true,
file_name: false,
content: true,
}
},
{
branch: {
pattern: revisionName,
exact: true,
}
},
languageFilter,
...(repoName ? [{
repo: {
regexp: `^${escapeStringRegexp(repoName)}$`,
}
}]: [])
]
}
}
const searchResult = await search({
queryType: 'ir',
query,
options: {
matches: MAX_REFERENCE_COUNT,
contextLines: 0,
}
});
if (isServiceError(searchResult)) {
return searchResult;
}
return parseRelatedSymbolsSearchResponse(searchResult);
}));
export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbolsRequest): Promise<FindRelatedSymbolsResponse | ServiceError> => sew(() =>
withOptionalAuthV2(async () => {
const {
symbolName,
language,
revisionName = "HEAD",
repoName
} = props;
const languageFilter = getExpandedLanguageFilter(language);
const query: QueryIR = {
and: {
children: [
{
symbol: {
expr: {
regexp: {
regexp: `\\b${symbolName}\\b`,
case_sensitive: true,
file_name: false,
content: true,
}
},
}
},
{
branch: {
pattern: revisionName,
exact: true,
}
},
languageFilter,
...(repoName ? [{
repo: {
regexp: `^${escapeStringRegexp(repoName)}$`,
}
}]: [])
]
}
}
const searchResult = await search({
queryType: 'ir',
query,
options: {
matches: MAX_REFERENCE_COUNT,
contextLines: 0,
}
});
if (isServiceError(searchResult)) {
return searchResult;
}
return parseRelatedSymbolsSearchResponse(searchResult);
}));
const parseRelatedSymbolsSearchResponse = (searchResult: SearchResponse): FindRelatedSymbolsResponse => {
return {
stats: {
matchCount: searchResult.stats.actualMatchCount,
},
files: searchResult.files.flatMap((file) => {
const chunks = file.chunks;
return {
fileName: file.fileName.text,
repository: file.repository,
repositoryId: file.repositoryId,
webUrl: file.webUrl,
language: file.language,
matches: chunks.flatMap((chunk) => {
return chunk.matchRanges.map((range) => ({
lineContent: chunk.content,
range: range,
}))
})
}
}).filter((file) => file.matches.length > 0),
repositoryInfo: searchResult.repositoryInfo
};
}
// Expands the language filter to include all variants of the language.
const getExpandedLanguageFilter = (language: string): QueryIR => {
switch (language) {
case "TypeScript":
case "JavaScript":
case "JSX":
case "TSX":
return {
or: {
children: [
{
language: {
language: "TypeScript",
}
},
{
language: {
language: "JavaScript",
}
},
{
language: {
language: "JSX",
}
},
{
language: {
language: "TSX",
}
},
]
},
}
default:
return {
language: {
language: language,
},
}
}
}