Files
vscode-codeql/extensions/ql-vscode/src/query-history/history-item-label-provider.ts

148 lines
4.2 KiB
TypeScript

import { env } from "vscode";
import { basename } from "path";
import type { QueryHistoryConfig } from "../config";
import type { LocalQueryInfo } from "../query-results";
import type { QueryHistoryInfo } from "./query-history-info";
import {
buildRepoLabel,
getLanguage,
getRawQueryName,
} from "./query-history-info";
import type { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
import { assertNever } from "../common/helpers-pure";
import { pluralize } from "../common/word";
import { humanizeQueryStatus } from "./query-status";
import { substituteConfigVariables } from "../common/config-template";
type LabelVariables = {
startTime: string;
queryName: string;
databaseName: string;
resultCount: string;
status: string;
queryFileBasename: string;
queryLanguage: string;
};
const legacyVariableInterpolateReplacements: Record<
keyof LabelVariables,
string
> = {
startTime: "t",
queryName: "q",
databaseName: "d",
resultCount: "r",
status: "s",
queryFileBasename: "f",
queryLanguage: "l",
};
// If any of the "legacy" variables are used, we need to use legacy interpolation.
const legacyLabelRegex = new RegExp(
`%([${Object.values(legacyVariableInterpolateReplacements).join("")}%])`,
"g",
);
export class HistoryItemLabelProvider {
constructor(private config: QueryHistoryConfig) {
/**/
}
getLabel(item: QueryHistoryInfo) {
let variables: LabelVariables;
switch (item.t) {
case "local":
variables = this.getLocalVariables(item);
break;
case "variant-analysis":
variables = this.getVariantAnalysisVariables(item);
break;
default:
assertNever(item);
}
const rawLabel =
item.userSpecifiedLabel ?? (this.config.format || "${queryName}");
if (legacyLabelRegex.test(rawLabel)) {
return this.legacyInterpolate(rawLabel, variables);
}
return substituteConfigVariables(rawLabel, variables).replace(/\s+/g, " ");
}
/**
* If there is a user-specified label for this query, interpolate and use that.
* Otherwise, use the raw name of this query.
*
* @returns the name of the query, unless there is a custom label for this query.
*/
getShortLabel(item: QueryHistoryInfo): string {
return item.userSpecifiedLabel
? this.getLabel(item)
: getRawQueryName(item);
}
private legacyInterpolate(
rawLabel: string,
variables: LabelVariables,
): string {
const replacements = Object.entries(variables).reduce(
(acc, [key, value]) => {
acc[
legacyVariableInterpolateReplacements[key as keyof LabelVariables]
] = value;
return acc;
},
{
"%": "%",
} as Record<string, string>,
);
const label = rawLabel.replace(/%(.)/g, (match, key: string) => {
const replacement = replacements[key];
return replacement !== undefined ? replacement : match;
});
return label.replace(/\s+/g, " ");
}
private getLocalVariables(item: LocalQueryInfo): LabelVariables {
const { resultCount = 0, message = "in progress" } =
item.completedQuery || {};
return {
startTime: item.startTime,
queryName: item.getQueryName(),
databaseName: item.databaseName,
resultCount: `(${resultCount} results)`,
status: message,
queryFileBasename: item.getQueryFileName(),
queryLanguage: this.getLanguageLabel(item),
};
}
private getVariantAnalysisVariables(
item: VariantAnalysisHistoryItem,
): LabelVariables {
const resultCount = item.resultCount
? `(${pluralize(item.resultCount, "result", "results")})`
: "";
return {
startTime: new Date(
item.variantAnalysis.executionStartTime,
).toLocaleString(env.language),
queryName: `${item.variantAnalysis.query.name} (${item.variantAnalysis.language})`,
databaseName: buildRepoLabel(item),
resultCount,
status: humanizeQueryStatus(item.status),
queryFileBasename: basename(item.variantAnalysis.query.filePath),
queryLanguage: this.getLanguageLabel(item),
};
}
private getLanguageLabel(item: QueryHistoryInfo): string {
const language = getLanguage(item);
return language === undefined ? "unknown" : `${language}`;
}
}