Merge branch 'main' into robertbrignull/data-unsaved-changes

This commit is contained in:
Robert
2023-07-11 15:11:24 +01:00
17 changed files with 1202 additions and 484 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1740,7 +1740,7 @@
"prepare": "cd ../.. && husky install"
},
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/plugin-retry": "^4.1.6",
"@octokit/rest": "^19.0.4",
"@vscode/codicons": "^0.0.31",
"@vscode/debugadapter": "^1.59.0",
@@ -1845,7 +1845,7 @@
"eslint-plugin-etc": "^2.0.2",
"eslint-plugin-github": "^4.4.1",
"eslint-plugin-jest-dom": "^5.0.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.6.4",
@@ -1866,7 +1866,7 @@
"mini-css-extract-plugin": "^2.6.1",
"npm-run-all": "^4.1.5",
"patch-package": "^7.0.0",
"prettier": "^2.7.1",
"prettier": "^3.0.0",
"tar-stream": "^3.0.0",
"through2": "^4.0.2",
"ts-jest": "^29.0.1",

View File

@@ -954,7 +954,10 @@ export interface GithubReleaseAsset {
}
export class GithubApiError extends Error {
constructor(public status: number, public body: string) {
constructor(
public status: number,
public body: string,
) {
super(`API call failed with status code ${status}, body: ${body}`);
}
}

View File

@@ -14,7 +14,10 @@ export class File implements vscode.FileStat {
mtime: number;
size: number;
constructor(public name: string, public data: Uint8Array) {
constructor(
public name: string,
public data: Uint8Array,
) {
this.type = vscode.FileType.File;
this.ctime = Date.now();
this.mtime = Date.now();

View File

@@ -67,7 +67,10 @@ export abstract class FilePathDiscovery<T extends PathData> extends Discovery {
* @param name Name of the discovery operation, for logging purposes.
* @param fileWatchPattern Passed to `vscode.RelativePattern` to determine the files to watch for changes to.
*/
constructor(name: string, private readonly fileWatchPattern: string) {
constructor(
name: string,
private readonly fileWatchPattern: string,
) {
super(name, extLogger);
this.onDidChangePathDataEmitter = this.push(new EventEmitter<void>());

View File

@@ -10,7 +10,10 @@ export class UserCancellationException extends Error {
* @param message The error message
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
*/
constructor(message?: string, public readonly silent = false) {
constructor(
message?: string,
public readonly silent = false,
) {
super(message);
}
}

View File

@@ -72,10 +72,10 @@ export function getHtmlForWebview(
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'${
allowWasmEval ? " 'wasm-unsafe-eval'" : ""
}; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
webview.cspSource
};">
allowWasmEval ? " 'wasm-unsafe-eval'" : ""
}; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
webview.cspSource
};">
${stylesheetsHtmlLines.join(` ${EOL}`)}
</head>
<body>

View File

@@ -693,7 +693,7 @@ const AUTOGENERATE_QL_PACKS = new Setting(
);
const AutogenerateQLPacksValues = ["ask", "never"] as const;
type AutogenerateQLPacks = typeof AutogenerateQLPacksValues[number];
type AutogenerateQLPacks = (typeof AutogenerateQLPacksValues)[number];
export function getAutogenerateQlPacks(): AutogenerateQLPacks {
const value = AUTOGENERATE_QL_PACKS.getValue<AutogenerateQLPacks>();

View File

@@ -216,9 +216,8 @@ function getCommands(
"codeQL.restartLegacyQueryServerOnConfigChange": restartQueryServer,
"codeQL.restartQueryServerOnExternalConfigChange": restartQueryServer,
"codeQL.copyVersion": async () => {
const text = `CodeQL extension version: ${
extension?.packageJSON.version
} \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`;
const text = `CodeQL extension version: ${extension?.packageJSON
.version} \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`;
await env.clipboard.writeText(text);
void showAndLogInformationMessage(extLogger, text);
},

View File

@@ -279,7 +279,10 @@ export class TemplatePrintAstProvider {
export class TemplatePrintCfgProvider {
private cache: CachedOperation<[Uri, Record<string, string>] | undefined>;
constructor(private cli: CodeQLCliServer, private dbm: DatabaseManager) {
constructor(
private cli: CodeQLCliServer,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<[Uri, Record<string, string>] | undefined>(
this.getCfgUri.bind(this),
);

View File

@@ -40,7 +40,10 @@ class QLTestListener extends DisposableObject {
export class TestUIService extends TestManagerBase implements TestController {
private readonly listeners: Map<TestAdapter, QLTestListener> = new Map();
public constructor(app: App, private readonly testHub: TestHub) {
public constructor(
app: App,
private readonly testHub: TestHub,
) {
super(app);
testHub.registerTestController(this);

View File

@@ -289,10 +289,13 @@ export async function exportToGist(
}
// Convert markdownFiles to the appropriate format for uploading to gist
const gistFiles = markdownFiles.reduce((acc, cur) => {
acc[`${cur.fileName}.md`] = { content: cur.content.join("\n") };
return acc;
}, {} as { [key: string]: { content: string } });
const gistFiles = markdownFiles.reduce(
(acc, cur) => {
acc[`${cur.fileName}.md`] = { content: cur.content.join("\n") };
return acc;
},
{} as { [key: string]: { content: string } },
);
const gistUrl = await createGist(credentials, description, gistFiles);
if (gistUrl) {

View File

@@ -158,8 +158,8 @@ export function filterAndSortRepositoriesWithResults<
filterSortState.repositoryIds.length > 0
) {
return repositories
.filter((repo) =>
filterSortState.repositoryIds?.includes(repo.repository.id),
.filter(
(repo) => filterSortState.repositoryIds?.includes(repo.repository.id),
)
.sort(compareWithResults(filterSortState));
}

View File

@@ -197,7 +197,7 @@ export function DataExtensionsEditor({
[],
);
const onGenerateClick = useCallback(() => {
const onGenerateFromSourceClick = useCallback(() => {
vscode.postMessage({
t: "generateExternalApi",
});
@@ -297,7 +297,7 @@ export function DataExtensionsEditor({
Refresh
</VSCodeButton>
)}
<VSCodeButton onClick={onGenerateClick}>
<VSCodeButton onClick={onGenerateFromSourceClick}>
{viewState?.mode === Mode.Framework
? "Generate"
: "Download and generate"}
@@ -319,6 +319,7 @@ export function DataExtensionsEditor({
onChange={onChange}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onGenerateFromSourceClick={onGenerateFromSourceClick}
/>
</EditorContainer>
</>

View File

@@ -87,6 +87,7 @@ type Props = {
externalApiUsages: ExternalApiUsage[],
modeledMethods: Record<string, ModeledMethod>,
) => void;
onGenerateFromSourceClick: () => void;
};
export const LibraryRow = ({
@@ -99,6 +100,7 @@ export const LibraryRow = ({
onChange,
onSaveModelClick,
onGenerateFromLlmClick,
onGenerateFromSourceClick,
}: Props) => {
const modeledPercentage = useMemo(() => {
return calculateModeledPercentage(externalApiUsages);
@@ -119,10 +121,14 @@ export const LibraryRow = ({
[externalApiUsages, modeledMethods, onGenerateFromLlmClick],
);
const handleModelFromSource = useCallback(async (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
}, []);
const handleModelFromSource = useCallback(
async (e: React.MouseEvent) => {
onGenerateFromSourceClick();
e.stopPropagation();
e.preventDefault();
},
[onGenerateFromSourceClick],
);
const handleModelDependency = useCallback(async (e: React.MouseEvent) => {
e.stopPropagation();

View File

@@ -30,6 +30,7 @@ type Props = {
externalApiUsages: ExternalApiUsage[],
modeledMethods: Record<string, ModeledMethod>,
) => void;
onGenerateFromSourceClick: () => void;
};
export const ModeledMethodsList = ({
@@ -41,6 +42,7 @@ export const ModeledMethodsList = ({
onChange,
onSaveModelClick,
onGenerateFromLlmClick,
onGenerateFromSourceClick,
}: Props) => {
const grouped = useMemo(
() => groupMethods(externalApiUsages, mode),
@@ -63,6 +65,7 @@ export const ModeledMethodsList = ({
onChange={onChange}
onSaveModelClick={onSaveModelClick}
onGenerateFromLlmClick={onGenerateFromLlmClick}
onGenerateFromSourceClick={onGenerateFromSourceClick}
/>
))}
</>

View File

@@ -18,6 +18,7 @@ import { EventHandlers as EventHandlerList } from "./event-handler-list";
import { ResultTables } from "./result-tables";
import "./resultsView.css";
import { useCallback, useEffect } from "react";
/**
* results.tsx
@@ -70,239 +71,236 @@ export const onNavigation = new EventHandlerList<NavigateMsg>();
/**
* A minimal state container for displaying results.
*/
export class ResultsApp extends React.Component<
Record<string, never>,
ResultsViewState
> {
constructor(props: any) {
super(props);
this.state = {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "",
},
nextResultsInfo: null,
isExpectingResultsUpdate: true,
};
}
export function ResultsApp() {
const [state, setState] = React.useState<ResultsViewState>({
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "",
},
nextResultsInfo: null,
isExpectingResultsUpdate: true,
});
handleMessage(msg: IntoResultsViewMsg): void {
switch (msg.t) {
case "setState":
this.updateStateWithNewResultsInfo({
resultsPath: msg.resultsPath,
parsedResultSets: msg.parsedResultSets,
origResultsPaths: msg.origResultsPaths,
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering:
msg.shouldKeepOldResultsWhileRendering,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
case "showInterpretedPage": {
const tableName =
msg.interpretation.data.t === "GraphInterpretationData"
? GRAPH_TABLE_NAME
: ALERTS_TABLE_NAME;
this.updateStateWithNewResultsInfo({
resultsPath: "", // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,
resultSet: {
t: "InterpretedResultSet",
name: tableName,
schema: {
name: tableName,
rows: 1,
columns: [],
},
interpretation: msg.interpretation,
const updateStateWithNewResultsInfo = useCallback(
(resultsInfo: ResultsInfo): void => {
setState((prevState) => {
if (resultsInfo === null && prevState.isExpectingResultsUpdate) {
// Display loading message
return {
...prevState,
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
},
selectedTable: tableName,
},
origResultsPaths: undefined as any, // FIXME: Not used for interpreted, refactor so this is not needed
sortedResultsMap: new Map(), // FIXME: Not used for interpreted, refactor so this is not needed
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
}
case "resultsUpdating":
this.setState({
isExpectingResultsUpdate: true,
});
break;
case "navigate":
onNavigation.fire(msg);
break;
nextResultsInfo: resultsInfo,
};
} else if (resultsInfo === null) {
// No results to display
return {
...prevState,
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "No results to display",
},
nextResultsInfo: resultsInfo,
};
}
case "untoggleShowProblems":
// noop
break;
let results: Results | null = null;
let statusText = "";
try {
const resultSets = getResultSets(resultsInfo);
results = {
resultSets,
database: resultsInfo.database,
sortStates: getSortStates(resultsInfo),
};
} catch (e) {
const errorMessage = getErrorMessage(e);
default:
assertNever(msg);
}
}
statusText = `Error loading results: ${errorMessage}`;
}
private updateStateWithNewResultsInfo(resultsInfo: ResultsInfo): void {
this.setState((prevState) => {
if (resultsInfo === null && prevState.isExpectingResultsUpdate) {
// Display loading message
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
resultsInfo,
results,
errorMessage: statusText,
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
};
} else if (resultsInfo === null) {
// No results to display
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "No results to display",
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
nextResultsInfo: null,
isExpectingResultsUpdate: false,
};
});
},
[],
);
const handleMessage = useCallback(
(msg: IntoResultsViewMsg): void => {
switch (msg.t) {
case "setState":
updateStateWithNewResultsInfo({
resultsPath: msg.resultsPath,
parsedResultSets: msg.parsedResultSets,
origResultsPaths: msg.origResultsPaths,
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering:
msg.shouldKeepOldResultsWhileRendering,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
case "showInterpretedPage": {
const tableName =
msg.interpretation.data.t === "GraphInterpretationData"
? GRAPH_TABLE_NAME
: ALERTS_TABLE_NAME;
updateStateWithNewResultsInfo({
resultsPath: "", // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,
resultSet: {
t: "InterpretedResultSet",
name: tableName,
schema: {
name: tableName,
rows: 1,
columns: [],
},
interpretation: msg.interpretation,
},
selectedTable: tableName,
},
origResultsPaths: undefined as any, // FIXME: Not used for interpreted, refactor so this is not needed
sortedResultsMap: new Map(), // FIXME: Not used for interpreted, refactor so this is not needed
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
}
case "resultsUpdating":
setState((prevState) => ({
...prevState,
isExpectingResultsUpdate: true,
}));
break;
case "navigate":
onNavigation.fire(msg);
break;
case "untoggleShowProblems":
// noop
break;
default:
assertNever(msg);
}
},
[updateStateWithNewResultsInfo],
);
let results: Results | null = null;
let statusText = "";
try {
const resultSets = this.getResultSets(resultsInfo);
results = {
resultSets,
database: resultsInfo.database,
sortStates: this.getSortStates(resultsInfo),
};
} catch (e) {
const errorMessage = getErrorMessage(e);
const vscodeMessageHandler = useCallback(
(evt: MessageEvent) => {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
evt.origin === window.origin
? handleMessage(evt.data as IntoResultsViewMsg)
: console.error(`Invalid event origin ${origin}`);
},
[handleMessage],
);
statusText = `Error loading results: ${errorMessage}`;
}
useEffect(() => {
window.addEventListener("message", vscodeMessageHandler);
return () => {
window.removeEventListener("message", vscodeMessageHandler);
};
}, [vscodeMessageHandler]);
return {
displayedResults: {
resultsInfo,
results,
errorMessage: statusText,
},
nextResultsInfo: null,
isExpectingResultsUpdate: false,
};
});
}
const { displayedResults, nextResultsInfo, isExpectingResultsUpdate } = state;
if (
displayedResults.results !== null &&
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key =
(parsedResultSets.selectedTable || "") + parsedResultSets.pageNumber;
const data = displayedResults.resultsInfo.interpretation?.data;
private getResultSets(resultsInfo: ResultsInfo): readonly ResultSet[] {
const parsedResultSets = resultsInfo.parsedResultSets;
const resultSet = parsedResultSets.resultSet;
if (
resultSet.t !== "InterpretedResultSet" &&
resultSet.t !== "RawResultSet"
) {
throw new Error(
`Invalid result set type. Should be either "InterpretedResultSet" or "RawResultSet", but got "${
(resultSet as { t: string }).t
}".`,
);
}
return [resultSet];
}
private getSortStates(
resultsInfo: ResultsInfo,
): Map<string, RawResultsSortState> {
const entries = Array.from(resultsInfo.sortedResultsMap.entries());
return new Map(
entries.map(([key, sortedResultSetInfo]) => [
key,
sortedResultSetInfo.sortState,
]),
return (
<ResultTables
key={key}
parsedResultSets={parsedResultSets}
rawResultSets={displayedResults.results.resultSets}
interpretation={
displayedResults.resultsInfo
? displayedResults.resultsInfo.interpretation
: undefined
}
database={displayedResults.results.database}
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
resultsPath={displayedResults.resultsInfo.resultsPath}
metadata={
displayedResults.resultsInfo
? displayedResults.resultsInfo.metadata
: undefined
}
sortStates={displayedResults.results.sortStates}
interpretedSortState={
data?.t === "SarifInterpretationData" ? data.sortState : undefined
}
isLoadingNewResults={
isExpectingResultsUpdate || nextResultsInfo !== null
}
queryName={displayedResults.resultsInfo.queryName}
queryPath={displayedResults.resultsInfo.queryPath}
/>
);
}
render(): JSX.Element {
const displayedResults = this.state.displayedResults;
if (
displayedResults.results !== null &&
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key =
(parsedResultSets.selectedTable || "") + parsedResultSets.pageNumber;
const data = displayedResults.resultsInfo.interpretation?.data;
return (
<ResultTables
key={key}
parsedResultSets={parsedResultSets}
rawResultSets={displayedResults.results.resultSets}
interpretation={
displayedResults.resultsInfo
? displayedResults.resultsInfo.interpretation
: undefined
}
database={displayedResults.results.database}
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
resultsPath={displayedResults.resultsInfo.resultsPath}
metadata={
displayedResults.resultsInfo
? displayedResults.resultsInfo.metadata
: undefined
}
sortStates={displayedResults.results.sortStates}
interpretedSortState={
data?.t === "SarifInterpretationData" ? data.sortState : undefined
}
isLoadingNewResults={
this.state.isExpectingResultsUpdate ||
this.state.nextResultsInfo !== null
}
queryName={displayedResults.resultsInfo.queryName}
queryPath={displayedResults.resultsInfo.queryPath}
/>
);
} else {
return <span>{displayedResults.errorMessage}</span>;
}
}
componentDidMount(): void {
this.vscodeMessageHandler = this.vscodeMessageHandler.bind(this);
window.addEventListener("message", this.vscodeMessageHandler);
}
componentWillUnmount(): void {
if (this.vscodeMessageHandler) {
window.removeEventListener("message", this.vscodeMessageHandler);
}
}
private vscodeMessageHandler(evt: MessageEvent) {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
evt.origin === window.origin
? this.handleMessage(evt.data as IntoResultsViewMsg)
: console.error(`Invalid event origin ${origin}`);
} else {
return <span>{displayedResults.errorMessage}</span>;
}
}
function getSortStates(
resultsInfo: ResultsInfo,
): Map<string, RawResultsSortState> {
const entries = Array.from(resultsInfo.sortedResultsMap.entries());
return new Map(
entries.map(([key, sortedResultSetInfo]) => [
key,
sortedResultSetInfo.sortState,
]),
);
}
function getResultSets(resultsInfo: ResultsInfo): readonly ResultSet[] {
const parsedResultSets = resultsInfo.parsedResultSets;
const resultSet = parsedResultSets.resultSet;
if (
resultSet.t !== "InterpretedResultSet" &&
resultSet.t !== "RawResultSet"
) {
throw new Error(
`Invalid result set type. Should be either "InterpretedResultSet" or "RawResultSet", but got "${
(resultSet as { t: string }).t
}".`,
);
}
return [resultSet];
}