Merge branch 'main' into koesie10/react-strict-mode

This commit is contained in:
Koen Vlaswinkel
2023-04-03 10:47:29 +02:00
committed by GitHub
62 changed files with 2645 additions and 1589 deletions

View File

@@ -199,6 +199,7 @@ jobs:
run: echo "cli-versions=$(cat ./extensions/ql-vscode/supported_cli_versions.json | jq -rc)" >> $GITHUB_OUTPUT
outputs:
cli-versions: ${{ steps.set-variables.outputs.cli-versions }}
cli-test:
name: CLI Test
runs-on: ${{ matrix.os }}

View File

@@ -67,49 +67,19 @@ jobs:
# TODO Run tests, or check that a test run on the same branch succeeded.
- name: Create release
id: create-release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
# This gives us a chance to manually review the created release before publishing it,
# as well as to test the release workflow by pushing temporary tags.
# Once we have set all required release metadata in this step, we can set this to `false`.
draft: true
prerelease: false
- name: Upload release asset
uses: actions/upload-release-asset@v1.0.1
if: success()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Get the `upload_url` from the `create-release` step above.
upload_url: ${{ steps.create-release.outputs.upload_url }}
# Get the `vsix_path` and `ref_name` from the `prepare-artifacts` step above.
asset_path: ${{ steps.prepare-artifacts.outputs.vsix_path }}
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
- name: Create sourcemap ZIP file
run: |
cd dist/vscode-codeql/out
zip -r ../../vscode-codeql-sourcemaps.zip *.map
- name: Upload sourcemap ZIP file
uses: actions/upload-release-asset@v1.0.1
if: success()
- name: Create release
id: create-release
run: |
gh release create ${{ github.ref_name }} --draft --title "Release ${{ github.ref_name }}" \
'${{ steps.prepare-artifacts.outputs.vsix_path }}#${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}' \
'dist/vscode-codeql-sourcemaps.zip#${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Get the `upload_url` from the `create-release` step above.
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: dist/vscode-codeql-sourcemaps.zip
asset_name: ${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
###
# Do Post release work: version bump and changelog PR
@@ -164,10 +134,7 @@ jobs:
- name: Publish to Registry
run: |
npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix || \
echo "Failed to publish to VS Code Marketplace. \
If this was an authentication problem, please make sure the \
auth token hasn't expired."
npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix
open-vsx-publish:
name: Publish to Open VSX Registry

View File

@@ -2,6 +2,8 @@
## [UNRELEASED]
- Restart the CodeQL language server whenever the _CodeQL: Restart Query Server_ command is invoked. This avoids bugs where the CLI version changes to support new language features, but the language server is not updated. [#2238](https://github.com/github/vscode-codeql/pull/2238)
## 1.8.1 - 23 March 2023
- Show data flow paths of a variant analysis in a new tab. [#2172](https://github.com/github/vscode-codeql/pull/2172) & [#2182](https://github.com/github/vscode-codeql/pull/2182)

File diff suppressed because it is too large Load Diff

View File

@@ -1441,12 +1441,11 @@
"classnames": "~2.2.6",
"d3": "^7.6.1",
"d3-graphviz": "^5.0.2",
"fs-extra": "^10.0.1",
"glob-promise": "^6.0.2",
"fs-extra": "^11.1.1",
"immutable": "^4.0.0",
"js-yaml": "^4.1.0",
"minimist": "~1.2.6",
"msw": "^0.49.0",
"msw": "^1.2.0",
"nanoid": "^3.2.0",
"node-fetch": "~2.6.7",
"p-queue": "^6.0.0",
@@ -1492,8 +1491,7 @@
"@types/d3": "^7.4.0",
"@types/d3-graphviz": "^2.6.6",
"@types/del": "^4.0.0",
"@types/fs-extra": "^9.0.6",
"@types/glob": "^7.1.1",
"@types/fs-extra": "^11.0.1",
"@types/google-protobuf": "^3.2.7",
"@types/gulp": "^4.0.9",
"@types/gulp-replace": "^1.1.0",
@@ -1539,7 +1537,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.6.4",
"file-loader": "^6.2.0",
"glob": "^7.1.4",
"glob": "^9.3.2",
"gulp": "^4.0.2",
"gulp-esbuild": "^0.10.5",
"gulp-replace": "^1.1.3",
@@ -1573,8 +1571,5 @@
"prettier --write",
"eslint --fix"
]
},
"resolutions": {
"glob-parent": "6.0.0"
}
}

View File

@@ -5,31 +5,18 @@ import {
TemplatePrintAstProvider,
TemplatePrintCfgProvider,
} from "./contextual/templateProvider";
import { compileAndRunQuery } from "./local-queries";
import { QueryRunner } from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { AstCfgCommands } from "./common/commands";
import { LocalQueries } from "./local-queries";
type AstCfgOptions = {
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
localQueries: LocalQueries;
astViewer: AstViewer;
astTemplateProvider: TemplatePrintAstProvider;
cfgTemplateProvider: TemplatePrintCfgProvider;
};
export function getAstCfgCommands({
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
localQueries,
astViewer,
astTemplateProvider,
cfgTemplateProvider,
@@ -59,12 +46,7 @@ export function getAstCfgCommands({
window.activeTextEditor?.document,
);
if (res) {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
await localQueries.compileAndRunQuery(
false,
res[0],
progress,

View File

@@ -3,7 +3,8 @@ export interface LogOptions {
trailingNewline?: boolean;
}
export interface Logger {
/** Minimal logger interface. */
export interface BaseLogger {
/**
* Writes the given log message, optionally followed by a newline.
* This function is asynchronous and will only resolve once the message is written
@@ -15,7 +16,10 @@ export interface Logger {
* @param options Optional settings.
*/
log(message: string, options?: LogOptions): Promise<void>;
}
/** Full logger interface, including a function to show the log in the UI. */
export interface Logger extends BaseLogger {
/**
* Reveal the logger channel in the UI.
*

View File

@@ -4,7 +4,7 @@ import { DatabaseItem } from "../local-databases";
import { ChildAstItem, AstItem } from "../astViewer";
import fileRangeFromURI from "./fileRangeFromURI";
import { Uri } from "vscode";
import { QueryWithResults } from "../run-queries-shared";
import { QueryOutputDir } from "../run-queries-shared";
/**
* A class that wraps a tree of QL results from a query that
@@ -14,12 +14,12 @@ export default class AstBuilder {
private roots: AstItem[] | undefined;
private bqrsPath: string;
constructor(
queryResults: QueryWithResults,
outputDir: QueryOutputDir,
private cli: CodeQLCliServer,
public db: DatabaseItem,
public fileName: Uri,
) {
this.bqrsPath = queryResults.query.resultsPaths.resultsPath;
this.bqrsPath = outputDir.bqrsPath;
}
async getRoots(): Promise<AstItem[]> {

View File

@@ -19,8 +19,9 @@ import {
runContextualQuery,
} from "./queryResolver";
import { CancellationToken, LocationLink, Uri } from "vscode";
import { QueryWithResults } from "../run-queries-shared";
import { QueryOutputDir } from "../run-queries-shared";
import { QueryRunner } from "../queryRunner";
import { QueryResultType } from "../pure/new-messages";
export const SELECT_QUERY_NAME = "#select";
export const TEMPLATE_NAME = "selectedSourceFile";
@@ -78,21 +79,23 @@ export async function getLocationsForUriString(
token,
templates,
);
if (results.successful) {
links.push(...(await getLinksFromResults(results, cli, db, filter)));
if (results.resultType === QueryResultType.SUCCESS) {
links.push(
...(await getLinksFromResults(results.outputDir, cli, db, filter)),
);
}
}
return links;
}
async function getLinksFromResults(
results: QueryWithResults,
outputDir: QueryOutputDir,
cli: CodeQLCliServer,
db: DatabaseItem,
filter: (srcFile: string, destFile: string) => boolean,
): Promise<FullLocationLink[]> {
const localLinks: FullLocationLink[] = [];
const bqrsPath = results.query.resultsPaths.resultsPath;
const bqrsPath = outputDir.bqrsPath;
const info = await cli.bqrsInfo(bqrsPath);
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
if (isValidSelect(selectInfo)) {

View File

@@ -13,11 +13,10 @@ import {
import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType";
import { CodeQLCliServer } from "../cli";
import { DatabaseItem } from "../local-databases";
import { extLogger } from "../common";
import { createInitialQueryInfo } from "../run-queries-shared";
import { CancellationToken, Uri } from "vscode";
import { extLogger, TeeLogger } from "../common";
import { CancellationToken } from "vscode";
import { ProgressCallback } from "../progress";
import { QueryRunner } from "../queryRunner";
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
import { redactableError } from "../pure/errors";
import { QLPACK_FILENAMES } from "../pure/ql";
@@ -169,32 +168,31 @@ export async function runContextualQuery(
progress: ProgressCallback,
token: CancellationToken,
templates: Record<string, string>,
) {
): Promise<CoreCompletedQuery> {
const { packPath, createdTempLockFile } = await resolveContextualQuery(
cli,
query,
);
const initialInfo = await createInitialQueryInfo(
Uri.file(query),
{
name: db.name,
databaseUri: db.databaseUri.toString(),
},
const queryRun = qs.createQueryRun(
db.databaseUri.fsPath,
{ queryPath: query, quickEvalPosition: undefined },
false,
getOnDiskWorkspaceFolders(),
undefined,
queryStorageDir,
undefined,
templates,
);
void extLogger.log(
`Running contextual query ${query}; results will be stored in ${queryStorageDir}`,
`Running contextual query ${query}; results will be stored in ${queryRun.outputDir.querySaveDir}`,
);
const queryResult = await qs.compileAndRunQueryAgainstDatabase(
db,
initialInfo,
queryStorageDir,
const results = await queryRun.evaluate(
progress,
token,
templates,
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
);
if (createdTempLockFile) {
await removeTemporaryLockFile(packPath);
}
return queryResult;
return results;
}

View File

@@ -32,8 +32,7 @@ import {
runContextualQuery,
} from "./queryResolver";
import { isCanary, NO_CACHE_AST_VIEWER } from "../config";
import { QueryWithResults } from "../run-queries-shared";
import { QueryRunner } from "../queryRunner";
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
/**
* Runs templated CodeQL queries to find definitions in
@@ -155,17 +154,12 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
}
}
type QueryWithDb = {
query: QueryWithResults;
dbUri: Uri;
};
/**
* Run templated CodeQL queries to produce AST information for
* source-language files.
*/
export class TemplatePrintAstProvider {
private cache: CachedOperation<QueryWithDb>;
private cache: CachedOperation<CoreCompletedQuery>;
constructor(
private cli: CodeQLCliServer,
@@ -173,7 +167,9 @@ export class TemplatePrintAstProvider {
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<QueryWithDb>(this.getAst.bind(this));
this.cache = new CachedOperation<CoreCompletedQuery>(
this.getAst.bind(this),
);
}
async provideAst(
@@ -186,14 +182,14 @@ export class TemplatePrintAstProvider {
"Cannot view the AST. Please select a valid source file inside a CodeQL database.",
);
}
const { query, dbUri } = this.shouldCache()
const completedQuery = this.shouldCache()
? await this.cache.get(fileUri.toString(), progress, token)
: await this.getAst(fileUri.toString(), progress, token);
return new AstBuilder(
query,
completedQuery.outputDir,
this.cli,
this.dbm.findDatabaseItem(dbUri)!,
this.dbm.findDatabaseItem(Uri.file(completedQuery.dbPath))!,
fileUri,
);
}
@@ -206,7 +202,7 @@ export class TemplatePrintAstProvider {
uriString: string,
progress: ProgressCallback,
token: CancellationToken,
): Promise<QueryWithDb> {
): Promise<CoreCompletedQuery> {
const uri = Uri.parse(uriString, true);
if (uri.scheme !== zipArchiveScheme) {
throw new Error(
@@ -242,7 +238,7 @@ export class TemplatePrintAstProvider {
[TEMPLATE_NAME]: zippedArchive.pathWithinSourceArchive,
};
const queryResult = await runContextualQuery(
const results = await runContextualQuery(
query,
db,
this.queryStorageDir,
@@ -252,10 +248,7 @@ export class TemplatePrintAstProvider {
token,
templates,
);
return {
query: queryResult,
dbUri: db.databaseUri,
};
return results;
}
}

View File

@@ -115,10 +115,7 @@ import {
QueryServerCommands,
TestUICommands,
} from "./common/commands";
import {
getLocalQueryCommands,
showResultsForCompletedQuery,
} from "./local-queries";
import { LocalQueries } from "./local-queries";
import { getAstCfgCommands } from "./ast-cfg-commands";
import { getQueryEditorCommands } from "./query-editor";
import { App } from "./common/app";
@@ -161,6 +158,7 @@ function getCommands(
app: App,
cliServer: CodeQLCliServer,
queryRunner: QueryRunner,
ideServer: LanguageClient,
): BaseCommands {
const getCliVersion = async () => {
try {
@@ -177,9 +175,12 @@ function getCommands(
"codeQL.restartQueryServer": async () =>
withProgress(
async (progress: ProgressCallback, token: CancellationToken) => {
// We restart the CLI server too, to ensure they are the same version
// Restart all of the spawned servers: cli, query, and language.
cliServer.restartCliServer();
await queryRunner.restartQueryServer(progress, token);
await Promise.all([
queryRunner.restartQueryServer(progress, token),
ideServer.restart(),
]);
void showAndLogInformationMessage("CodeQL Query Server restarted.", {
outputLogger: queryServerLogger,
});
@@ -256,6 +257,7 @@ export interface CodeQLExtensionInterface {
readonly distributionManager: DistributionManager;
readonly databaseManager: DatabaseManager;
readonly databaseUI: DatabaseUI;
readonly localQueries: LocalQueries;
readonly variantAnalysisManager: VariantAnalysisManager;
readonly dispose: () => void;
}
@@ -289,7 +291,7 @@ const MIN_VERSION = "1.67.0";
*/
export async function activate(
ctx: ExtensionContext,
): Promise<CodeQLExtensionInterface | Record<string, never>> {
): Promise<CodeQLExtensionInterface | undefined> {
void extLogger.log(`Starting ${extensionId} extension`);
if (extension === undefined) {
throw new Error(`Can't find extension ${extensionId}`);
@@ -358,7 +360,10 @@ export async function activate(
),
);
const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(ctx);
const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(
ctx,
app,
);
Window.registerWebviewPanelSerializer(
VariantAnalysisView.viewType,
variantAnalysisViewSerializer,
@@ -379,9 +384,11 @@ export async function activate(
},
);
variantAnalysisViewSerializer.onExtensionLoaded(
codeQlExtension.variantAnalysisManager,
);
if (codeQlExtension !== undefined) {
variantAnalysisViewSerializer.onExtensionLoaded(
codeQlExtension.variantAnalysisManager,
);
}
return codeQlExtension;
}
@@ -571,7 +578,7 @@ async function installOrUpdateThenTryActivate(
distributionManager: DistributionManager,
distributionConfigListener: DistributionConfigListener,
config: DistributionUpdateConfig,
): Promise<CodeQLExtensionInterface | Record<string, never>> {
): Promise<CodeQLExtensionInterface | undefined> {
await installOrUpdateDistribution(ctx, app, distributionManager, config);
try {
@@ -585,20 +592,19 @@ async function installOrUpdateThenTryActivate(
// Display the warnings even if the extension has already activated.
const distributionResult =
await getDistributionDisplayingDistributionWarnings(distributionManager);
let extensionInterface: CodeQLExtensionInterface | Record<string, never> = {};
if (
!beganMainExtensionActivation &&
distributionResult.kind !== FindDistributionResultKind.NoDistribution
) {
extensionInterface = await activateWithInstalledDistribution(
return await activateWithInstalledDistribution(
ctx,
app,
distributionManager,
distributionConfigListener,
);
} else if (
distributionResult.kind === FindDistributionResultKind.NoDistribution
) {
}
if (distributionResult.kind === FindDistributionResultKind.NoDistribution) {
registerErrorStubs([checkForUpdatesCommand], (command) => async () => {
const installActionName = "Install CodeQL CLI";
const chosenAction = await showAndLogErrorMessage(
@@ -622,7 +628,7 @@ async function installOrUpdateThenTryActivate(
}
});
}
return extensionInterface;
return undefined;
}
const PACK_GLOBS = [
@@ -708,12 +714,6 @@ async function activateWithInstalledDistribution(
void extLogger.log("Initializing query history manager.");
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
ctx.subscriptions.push(queryHistoryConfigurationListener);
const showResults = async (item: CompletedLocalQueryInfo) =>
showResultsForCompletedQuery(
localQueryResultsView,
item,
WebviewReveal.Forced,
);
const queryStorageDir = join(ctx.globalStorageUri.fsPath, "queries");
await ensureDir(queryStorageDir);
@@ -787,8 +787,10 @@ async function activateWithInstalledDistribution(
ctx,
queryHistoryConfigurationListener,
labelProvider,
async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>
showResultsForComparison(compareView, from, to),
async (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
): Promise<void> => showResultsForComparison(compareView, from, to),
);
ctx.subscriptions.push(qhm);
@@ -809,7 +811,8 @@ async function activateWithInstalledDistribution(
cliServer,
queryServerLogger,
labelProvider,
showResults,
async (item: CompletedLocalQueryInfo) =>
localQueries.showResultsForCompletedQuery(item, WebviewReveal.Forced),
);
ctx.subscriptions.push(compareView);
@@ -845,6 +848,18 @@ async function activateWithInstalledDistribution(
true,
);
const localQueries = new LocalQueries(
app,
qs,
qhm,
dbm,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
);
ctx.subscriptions.push(localQueries);
void extLogger.log("Initializing QLTest interface.");
const testExplorerExtension = extensions.getExtension<TestHub>(
testExplorerExtensionId,
@@ -876,16 +891,16 @@ async function activateWithInstalledDistribution(
ctx.subscriptions.push(astViewer);
const summaryLanguageSupport = new SummaryLanguageSupport();
const summaryLanguageSupport = new SummaryLanguageSupport(app);
ctx.subscriptions.push(summaryLanguageSupport);
const mockServer = new VSCodeMockGitHubApiServer(ctx);
const mockServer = new VSCodeMockGitHubApiServer(app);
ctx.subscriptions.push(mockServer);
void extLogger.log("Registering top-level command palette commands.");
const allCommands: AllExtensionCommands = {
...getCommands(app, cliServer, qs),
...getCommands(app, cliServer, qs, client),
...getQueryEditorCommands({
commandManager: app.commands,
queryRunner: qs,
@@ -898,11 +913,7 @@ async function activateWithInstalledDistribution(
...databaseUI.getCommands(),
...dbModule.getCommands(),
...getAstCfgCommands({
queryRunner: qs,
queryHistoryManager: qhm,
databaseUI,
localQueryResultsView,
queryStorageDir,
localQueries,
astViewer,
astTemplateProvider,
cfgTemplateProvider,
@@ -922,16 +933,7 @@ async function activateWithInstalledDistribution(
}
const queryServerCommands: QueryServerCommands = {
...getLocalQueryCommands({
app,
queryRunner: qs,
queryHistoryManager: qhm,
databaseManager: dbm,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
}),
...localQueries.getCommands(),
};
for (const [commandName, command] of Object.entries(queryServerCommands)) {
@@ -981,6 +983,7 @@ async function activateWithInstalledDistribution(
return {
ctx,
cliServer,
localQueries,
qs,
distributionManager,
databaseManager: dbm,
@@ -1074,6 +1077,7 @@ async function createQueryServer(
);
if (await cliServer.cliConstraints.supportsNewQueryServer()) {
const qs = new QueryServerClient(
app,
qlConfigurationListener,
cliServer,
qsOpts,

View File

@@ -6,7 +6,7 @@ import {
writeFile,
opendir,
} from "fs-extra";
import { promise as glob } from "glob-promise";
import { glob } from "glob";
import { load } from "js-yaml";
import { join, basename } from "path";
import { dirSync } from "tmp-promise";
@@ -304,7 +304,7 @@ export async function prepareCodeTour(
return;
}
const tutorialWorkspaceUri = Uri.parse(tutorialWorkspacePath);
const tutorialWorkspaceUri = Uri.file(tutorialWorkspacePath);
void extLogger.log(
`In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`,
@@ -515,7 +515,9 @@ export async function getQlPackForDbscheme(
export async function getPrimaryDbscheme(
datasetFolder: string,
): Promise<string> {
const dbschemes = await glob(join(datasetFolder, "*.dbscheme"));
const dbschemes = await glob("*.dbscheme", {
cwd: datasetFolder,
});
if (dbschemes.length < 1) {
throw new Error(
@@ -752,7 +754,7 @@ export async function tryGetQueryMetadata(
* Creates a file in the query directory that indicates when this query was created.
* This is important for keeping track of when queries should be removed.
*
* @param queryPath The directory that will containt all files relevant to a query result.
* @param queryPath The directory that will contain all files relevant to a query result.
* It does not need to exist.
*/
export async function createTimestampFile(storagePath: string) {

View File

@@ -1,18 +1,19 @@
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "../cli";
import { ProgressCallback } from "../progress";
import { Logger } from "../common";
import { DatabaseItem } from "../local-databases";
import {
Dataset,
deregisterDatabases,
registerDatabases,
} from "../pure/legacy-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
import { QueryRunner } from "../queryRunner";
import { QueryWithResults } from "../run-queries-shared";
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
import { QueryOutputDir } from "../run-queries-shared";
import { QueryServerClient } from "./queryserver-client";
import {
clearCacheInDatabase,
compileAndRunQueryAgainstDatabase,
compileAndRunQueryAgainstDatabaseCore,
} from "./run-queries";
import { upgradeDatabaseExplicit } from "./upgrades";
@@ -21,10 +22,18 @@ export class LegacyQueryRunner extends QueryRunner {
super();
}
get cliServer() {
get cliServer(): CodeQLCliServer {
return this.qs.cliServer;
}
get customLogDirectory(): string | undefined {
return undefined;
}
get logger(): Logger {
return this.qs.logger;
}
async restartQueryServer(
progress: ProgressCallback,
token: CancellationToken,
@@ -47,25 +56,31 @@ export class LegacyQueryRunner extends QueryRunner {
): Promise<void> {
await clearCacheInDatabase(this.qs, dbItem, progress, token);
}
async compileAndRunQueryAgainstDatabase(
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
public async compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],
extensionPacks: string[] | undefined,
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo,
): Promise<QueryWithResults> {
return await compileAndRunQueryAgainstDatabase(
this.qs.cliServer,
templates: Record<string, string> | undefined,
logger: Logger,
): Promise<CoreQueryResults> {
return await compileAndRunQueryAgainstDatabaseCore(
this.qs,
dbItem,
initialInfo,
queryStorageDir,
dbPath,
query,
generateEvalLog,
additionalPacks,
extensionPacks,
outputDir,
progress,
token,
templates,
queryInfo,
logger,
);
}

View File

@@ -1,28 +1,168 @@
import * as tmp from "tmp-promise";
import { basename, join } from "path";
import { basename } from "path";
import { CancellationToken, Uri } from "vscode";
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
import * as cli from "../cli";
import { DatabaseItem } from "../local-databases";
import {
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
DatabaseContentsWithDbScheme,
DatabaseItem,
DatabaseResolver,
} from "../local-databases";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
tryGetQueryMetadata,
upgradesTmpDir,
} from "../helpers";
import { ProgressCallback } from "../progress";
import { QueryMetadata } from "../pure/interface-types";
import { extLogger, Logger, TeeLogger } from "../common";
import { extLogger, Logger } from "../common";
import * as messages from "../pure/legacy-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
import * as newMessages from "../pure/new-messages";
import * as qsClient from "./queryserver-client";
import { asError, getErrorMessage } from "../pure/helpers-pure";
import { compileDatabaseUpgradeSequence } from "./upgrades";
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
import { QueryEvaluationInfo, QueryOutputDir } from "../run-queries-shared";
import { redactableError } from "../pure/errors";
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
import { Position } from "../pure/messages-shared";
export async function compileQuery(
qs: qsClient.QueryServerClient,
program: messages.QlProgram,
quickEvalPosition: Position | undefined,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
logger: Logger,
): Promise<messages.CompilationMessage[]> {
let compiled: messages.CheckQueryResult | undefined;
try {
const target: messages.CompilationTarget = quickEvalPosition
? {
quickEval: { quickEvalPos: quickEvalPosition },
}
: { query: {} };
const params: messages.CompileQueryParams = {
compilationOptions: {
computeNoLocationUrls: true,
failOnWarnings: false,
fastCompilation: false,
includeDilInQlo: true,
localChecking: false,
noComputeGetUrl: false,
noComputeToString: false,
computeDefaultStrings: true,
emitDebugInfo: true,
},
extraOptions: {
timeoutSecs: qs.config.timeoutSecs,
},
queryToCheck: program,
resultPath: outputDir.compileQueryPath,
target,
};
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
compiled = await qs.sendRequest(
messages.compileQuery,
params,
token,
progress,
);
} finally {
void logger.log(" - - - COMPILATION DONE - - - ");
}
return (compiled?.messages || []).filter(
(msg) => msg.severity === messages.Severity.ERROR,
);
}
async function runQuery(
qs: qsClient.QueryServerClient,
upgradeQlo: string | undefined,
availableMlModels: cli.MlModelInfo[],
dbContents: DatabaseContentsWithDbScheme,
templates: Record<string, string> | undefined,
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
): Promise<messages.EvaluationResult> {
let result: messages.EvaluationResult | null = null;
const logPath = outputDir.logPath;
const callbackId = qs.registerCallback((res) => {
result = {
...res,
logFileLocation: logPath,
};
});
const availableMlModelUris: messages.MlModel[] = availableMlModels.map(
(model) => ({ uri: Uri.file(model.path).toString(true) }),
);
const queryToRun: messages.QueryToRun = {
resultsPath: outputDir.bqrsPath,
qlo: Uri.file(outputDir.compileQueryPath).toString(),
compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),
allowUnknownTemplates: true,
templateValues: createSimpleTemplates(templates),
availableMlModels: availableMlModelUris,
id: callbackId,
timeoutSecs: qs.config.timeoutSecs,
};
const dataset: messages.Dataset = {
dbDir: dbContents.datasetUri.fsPath,
workingSet: "default",
};
if (
generateEvalLog &&
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
) {
await qs.sendRequest(messages.startLog, {
db: dataset,
logPath: outputDir.evalLogPath,
});
}
const params: messages.EvaluateQueriesParams = {
db: dataset,
evaluateId: callbackId,
queries: [queryToRun],
stopOnError: false,
useSequenceHint: false,
};
try {
await qs.sendRequest(messages.runQueries, params, token, progress);
} finally {
qs.unRegisterCallback(callbackId);
if (
generateEvalLog &&
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
) {
await qs.sendRequest(messages.endLog, {
db: dataset,
logPath: outputDir.evalLogPath,
});
}
}
return (
result || {
evaluationTime: 0,
message: "No result from server",
queryId: -1,
runId: callbackId,
resultType: messages.QueryResultType.OTHER_ERROR,
}
);
}
/**
* A collection of evaluation-time information about a query,
@@ -33,14 +173,14 @@ import { redactableError } from "../pure/errors";
export class QueryInProgress {
public queryEvalInfo: QueryEvaluationInfo;
/**
* Note that in the {@link deserializeQueryHistory} method, we create a QueryEvaluationInfo instance
* Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(
readonly querySaveDir: string,
readonly dbItemPath: string,
databaseHasMetadataFile: boolean,
readonly queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution
readonly queryDbscheme: string, // the dbscheme file the query expects, ba`sed on library path resolution
readonly quickEvalPosition?: messages.Position,
readonly metadata?: QueryMetadata,
readonly templates?: Record<string, string>,
@@ -54,162 +194,6 @@ export class QueryInProgress {
);
/**/
}
get compiledQueryPath() {
return this.queryEvalInfo.compileQueryPath;
}
async run(
qs: qsClient.QueryServerClient,
upgradeQlo: string | undefined,
availableMlModels: cli.MlModelInfo[],
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
logger: Logger,
queryInfo: LocalQueryInfo | undefined,
): Promise<messages.EvaluationResult> {
if (!dbItem.contents || dbItem.error) {
throw new Error("Can't run query on invalid database.");
}
let result: messages.EvaluationResult | null = null;
const callbackId = qs.registerCallback((res) => {
result = {
...res,
logFileLocation: this.queryEvalInfo.logPath,
};
});
const availableMlModelUris: messages.MlModel[] = availableMlModels.map(
(model) => ({ uri: Uri.file(model.path).toString(true) }),
);
const queryToRun: messages.QueryToRun = {
resultsPath: this.queryEvalInfo.resultsPaths.resultsPath,
qlo: Uri.file(this.compiledQueryPath).toString(),
compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),
allowUnknownTemplates: true,
templateValues: createSimpleTemplates(this.templates),
availableMlModels: availableMlModelUris,
id: callbackId,
timeoutSecs: qs.config.timeoutSecs,
};
const dataset: messages.Dataset = {
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: "default",
};
if (
queryInfo &&
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
) {
await qs.sendRequest(messages.startLog, {
db: dataset,
logPath: this.queryEvalInfo.evalLogPath,
});
}
const params: messages.EvaluateQueriesParams = {
db: dataset,
evaluateId: callbackId,
queries: [queryToRun],
stopOnError: false,
useSequenceHint: false,
};
try {
await qs.sendRequest(messages.runQueries, params, token, progress);
if (qs.config.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${this.queryEvalInfo.logPath}.`,
);
}
} finally {
qs.unRegisterCallback(callbackId);
if (
queryInfo &&
(await qs.cliServer.cliConstraints.supportsPerQueryEvalLog())
) {
await qs.sendRequest(messages.endLog, {
db: dataset,
logPath: this.queryEvalInfo.evalLogPath,
});
if (await this.queryEvalInfo.hasEvalLog()) {
await this.queryEvalInfo.addQueryLogs(
queryInfo,
qs.cliServer,
logger,
);
} else {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${this.queryEvalInfo.evalLogPath}.`,
);
}
}
}
return (
result || {
evaluationTime: 0,
message: "No result from server",
queryId: -1,
runId: callbackId,
resultType: messages.QueryResultType.OTHER_ERROR,
}
);
}
async compile(
qs: qsClient.QueryServerClient,
program: messages.QlProgram,
progress: ProgressCallback,
token: CancellationToken,
logger: Logger,
): Promise<messages.CompilationMessage[]> {
let compiled: messages.CheckQueryResult | undefined;
try {
const target = this.quickEvalPosition
? {
quickEval: { quickEvalPos: this.quickEvalPosition },
}
: { query: {} };
const params: messages.CompileQueryParams = {
compilationOptions: {
computeNoLocationUrls: true,
failOnWarnings: false,
fastCompilation: false,
includeDilInQlo: true,
localChecking: false,
noComputeGetUrl: false,
noComputeToString: false,
computeDefaultStrings: true,
emitDebugInfo: true,
},
extraOptions: {
timeoutSecs: qs.config.timeoutSecs,
},
queryToCheck: program,
resultPath: this.compiledQueryPath,
target,
};
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
compiled = await qs.sendRequest(
messages.compileQuery,
params,
token,
progress,
);
} finally {
void logger.log(" - - - COMPILATION DONE - - - ");
}
return (compiled?.messages || []).filter(
(msg) => msg.severity === messages.Severity.ERROR,
);
}
}
export async function clearCacheInDatabase(
@@ -237,10 +221,10 @@ export async function clearCacheInDatabase(
function reportNoUpgradePath(
qlProgram: messages.QlProgram,
query: QueryInProgress,
queryDbscheme: string,
): void {
throw new Error(
`Query ${qlProgram.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`,
`Query ${qlProgram.queryPath} expects database scheme ${queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`,
);
}
@@ -250,32 +234,27 @@ function reportNoUpgradePath(
async function compileNonDestructiveUpgrade(
qs: qsClient.QueryServerClient,
upgradeTemp: tmp.DirectoryResult,
query: QueryInProgress,
queryDbscheme: string,
qlProgram: messages.QlProgram,
dbItem: DatabaseItem,
dbContents: DatabaseContentsWithDbScheme,
progress: ProgressCallback,
token: CancellationToken,
): Promise<string> {
if (!dbItem?.contents?.dbSchemeUri) {
throw new Error("Database is invalid, and cannot be upgraded.");
}
// Dependencies may exist outside of the workspace and they are always on the resolved search path.
const upgradesPath = qlProgram.libraryPath;
const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(
dbItem.contents.dbSchemeUri.fsPath,
dbContents.dbSchemeUri.fsPath,
upgradesPath,
true,
query.queryDbscheme,
queryDbscheme,
);
if (!matchesTarget) {
reportNoUpgradePath(qlProgram, query);
reportNoUpgradePath(qlProgram, queryDbscheme);
}
const result = await compileDatabaseUpgradeSequence(
qs,
dbItem,
scripts,
upgradeTemp,
progress,
@@ -286,34 +265,74 @@ async function compileNonDestructiveUpgrade(
throw new Error(error);
}
// We can upgrade to the actual target
qlProgram.dbschemePath = query.queryDbscheme;
qlProgram.dbschemePath = queryDbscheme;
// We are new enough that we will always support single file upgrades.
return result.compiledUpgrade;
}
export async function compileAndRunQueryAgainstDatabase(
cliServer: cli.CodeQLCliServer,
function translateLegacyResult(
legacyResult: messages.EvaluationResult,
): Omit<CoreQueryResults, "dispose"> {
let newResultType: newMessages.QueryResultType;
let newMessage = legacyResult.message;
switch (legacyResult.resultType) {
case messages.QueryResultType.SUCCESS:
newResultType = newMessages.QueryResultType.SUCCESS;
break;
case messages.QueryResultType.CANCELLATION:
newResultType = newMessages.QueryResultType.CANCELLATION;
break;
case messages.QueryResultType.OOM:
newResultType = newMessages.QueryResultType.OOM;
break;
case messages.QueryResultType.TIMEOUT:
// This is the only legacy result type that doesn't exist for the new query server. Format the
// message here, and let the later code treat it as `OTHER_ERROR`.
newResultType = newMessages.QueryResultType.OTHER_ERROR;
newMessage = `timed out after ${Math.round(
legacyResult.evaluationTime / 1000,
)} seconds`;
break;
case messages.QueryResultType.OTHER_ERROR:
default:
newResultType = newMessages.QueryResultType.OTHER_ERROR;
break;
}
return {
resultType: newResultType,
message: newMessage,
evaluationTime: legacyResult.evaluationTime,
};
}
export async function compileAndRunQueryAgainstDatabaseCore(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
dbPath: string,
query: CoreQueryTarget,
generateEvalLog: boolean,
additionalPacks: string[],
extensionPacks: string[] | undefined,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults> {
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
throw new Error(
`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`,
templates: Record<string, string> | undefined,
logger: Logger,
): Promise<CoreQueryResults> {
if (extensionPacks !== undefined && extensionPacks.length > 0) {
await showAndLogWarningMessage(
"Legacy query server does not support extension packs.",
);
}
// Get the workspace folder paths.
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
const dbContents = await DatabaseResolver.resolveDatabaseContents(
Uri.file(dbPath),
);
// Figure out the library path for the query.
const packConfig = await cliServer.resolveLibraryPath(
diskWorkspaceFolders,
initialInfo.queryPath,
const packConfig = await qs.cliServer.resolveLibraryPath(
additionalPacks,
query.queryPath,
);
if (!packConfig.dbscheme) {
@@ -327,17 +346,15 @@ export async function compileAndRunQueryAgainstDatabase(
// won't trigger this check)
// This test will produce confusing results if we ever change the name of the database schema files.
const querySchemaName = basename(packConfig.dbscheme);
const dbSchemaName = basename(dbItem.contents.dbSchemeUri.fsPath);
const dbSchemaName = basename(dbContents.dbSchemeUri?.fsPath);
if (querySchemaName !== dbSchemaName) {
void extLogger.log(
`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`,
);
throw new Error(
`The query ${basename(
initialInfo.queryPath,
)} cannot be run against the selected database (${
dbItem.name
}): their target languages are different. Please select a different database and try again.`,
query.queryPath,
)} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`,
);
}
@@ -349,20 +366,14 @@ export async function compileAndRunQueryAgainstDatabase(
// Since we are compiling and running a query against a database,
// we use the database's DB scheme here instead of the DB scheme
// from the current document's project.
dbschemePath: dbItem.contents.dbSchemeUri.fsPath,
queryPath: initialInfo.queryPath,
dbschemePath: dbContents.dbSchemeUri.fsPath,
queryPath: query.queryPath,
};
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(cliServer, qlProgram.queryPath);
let availableMlModels: cli.MlModelInfo[] = [];
try {
availableMlModels = (
await cliServer.resolveMlModels(
diskWorkspaceFolders,
initialInfo.queryPath,
)
await qs.cliServer.resolveMlModels(additionalPacks, query.queryPath)
).models;
if (availableMlModels.length) {
void extLogger.log(
@@ -381,57 +392,53 @@ export async function compileAndRunQueryAgainstDatabase(
);
}
const hasMetadataFile = await dbItem.hasMetadataFile();
const query = new QueryInProgress(
join(queryStorageDir, initialInfo.id),
dbItem.databaseUri.fsPath,
hasMetadataFile,
packConfig.dbscheme,
initialInfo.quickEvalPosition,
metadata,
templates,
);
const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath);
await query.queryEvalInfo.createTimestampFile();
let upgradeDir: tmp.DirectoryResult | undefined;
try {
upgradeDir = await tmp.dir({ dir: upgradesTmpDir, unsafeCleanup: true });
const upgradeQlo = await compileNonDestructiveUpgrade(
qs,
upgradeDir,
query,
packConfig.dbscheme,
qlProgram,
dbItem,
dbContents,
progress,
token,
);
let errors;
try {
errors = await query.compile(qs, qlProgram, progress, token, logger);
errors = await compileQuery(
qs,
qlProgram,
query.quickEvalPosition,
outputDir,
progress,
token,
logger,
);
} catch (e) {
if (
e instanceof ResponseError &&
e.code === LSPErrorCodes.RequestCancelled
) {
return createSyntheticResult(query, "Query cancelled");
return createSyntheticResult("Query cancelled");
} else {
throw e;
}
}
if (errors.length === 0) {
const result = await query.run(
const result = await runQuery(
qs,
upgradeQlo,
availableMlModels,
dbItem,
dbContents,
templates,
generateEvalLog,
outputDir,
progress,
token,
logger,
queryInfo,
);
if (result.resultType !== messages.QueryResultType.SUCCESS) {
const error = result.message
? redactableError`${result.message}`
@@ -439,22 +446,15 @@ export async function compileAndRunQueryAgainstDatabase(
void extLogger.log(error.fullMessage);
void showAndLogExceptionWithTelemetry(error);
}
const message = formatLegacyMessage(result);
return {
query: query.queryEvalInfo,
message,
result,
successful: result.resultType === messages.QueryResultType.SUCCESS,
logFileLocation: result.logFileLocation,
};
return translateLegacyResult(result);
} else {
// Error dialogs are limited in size and scrollability,
// so we include a general description of the problem,
// and direct the user to the output window for the detailed compilation messages.
// However we don't show quick eval errors there so we need to display them anyway.
void logger.log(
`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
`Failed to compile query ${query.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
);
const formattedMessages: string[] = [];
@@ -465,22 +465,12 @@ export async function compileAndRunQueryAgainstDatabase(
formattedMessages.push(formatted);
void logger.log(formatted);
}
if (initialInfo.isQuickEval && formattedMessages.length <= 2) {
// If there are more than 2 error messages, they will not be displayed well in a popup
// and will be trimmed by the function displaying the error popup. Accordingly, we only
// try to show the errors if there are 2 or less, otherwise we direct the user to the log.
void showAndLogErrorMessage(
`Quick evaluation compilation failed: ${formattedMessages.join(
"\n",
)}`,
);
} else {
void showAndLogErrorMessage(
(initialInfo.isQuickEval ? "Quick evaluation" : "Query") +
compilationFailedErrorTail,
);
}
return createSyntheticResult(query, "Query had compilation errors");
return {
evaluationTime: 0,
resultType: newMessages.QueryResultType.COMPILATION_ERROR,
message: formattedMessages[0],
};
}
} finally {
try {
@@ -493,11 +483,6 @@ export async function compileAndRunQueryAgainstDatabase(
}
}
const compilationFailedErrorTail =
" compilation failed. Please make sure there are no errors in the query, the database is up to date," +
" and the query and database use the same target language. For more details on the error, go to View > Output," +
" and choose CodeQL Query Server from the dropdown.";
export function formatLegacyMessage(result: messages.EvaluationResult) {
switch (result.resultType) {
case messages.QueryResultType.CANCELLATION:
@@ -521,21 +506,11 @@ export function formatLegacyMessage(result: messages.EvaluationResult) {
/**
* Create a synthetic result for a query that failed to compile.
*/
function createSyntheticResult(
query: QueryInProgress,
message: string,
): QueryWithResults {
function createSyntheticResult(message: string): CoreQueryResults {
return {
query: query.queryEvalInfo,
evaluationTime: 0,
resultType: newMessages.QueryResultType.OTHER_ERROR,
message,
result: {
evaluationTime: 0,
queryId: 0,
resultType: messages.QueryResultType.OTHER_ERROR,
message,
runId: 0,
},
successful: false,
};
}

View File

@@ -27,18 +27,11 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
*/
export async function compileDatabaseUpgradeSequence(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
resolvedSequence: string[],
currentUpgradeTmp: tmp.DirectoryResult,
progress: ProgressCallback,
token: vscode.CancellationToken,
): Promise<messages.CompileUpgradeSequenceResult> {
if (
dbItem.contents === undefined ||
dbItem.contents.dbSchemeUri === undefined
) {
throw new Error("Database is invalid, and cannot be upgraded.");
}
// If possible just compile the upgrade sequence
return await qs.sendRequest(
messages.compileUpgradeSequence,

View File

@@ -1,5 +1,5 @@
import { pathExists, stat, remove } from "fs-extra";
import { promise as glob } from "glob-promise";
import { glob } from "glob";
import { join, basename, resolve, relative, dirname, extname } from "path";
import * as vscode from "vscode";
import * as cli from "./cli";
@@ -95,6 +95,10 @@ export interface DatabaseContents {
dbSchemeUri?: vscode.Uri;
}
export interface DatabaseContentsWithDbScheme extends DatabaseContents {
dbSchemeUri: vscode.Uri; // Always present
}
/**
* An error thrown when we cannot find a valid database in a putative
* database directory.
@@ -165,7 +169,7 @@ async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
export class DatabaseResolver {
public static async resolveDatabaseContents(
uri: vscode.Uri,
): Promise<DatabaseContents> {
): Promise<DatabaseContentsWithDbScheme> {
if (uri.scheme !== "file") {
throw new Error(
`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`,
@@ -199,9 +203,12 @@ export class DatabaseResolver {
`Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`,
);
} else {
contents.dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
const dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0]));
return {
...contents,
dbSchemeUri,
};
}
return contents;
}
public static async resolveDatabase(
@@ -1001,6 +1008,15 @@ export class DatabaseManager extends DisposableObject {
});
}
public async removeAllDatabases(
progress: ProgressCallback,
token: vscode.CancellationToken,
) {
for (const item of this.databaseItems) {
await this.removeDatabaseItem(progress, token, item);
}
}
private async deregisterDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,

View File

@@ -7,96 +7,266 @@ import {
Uri,
window,
} from "vscode";
import { extLogger } from "./common";
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
import { MAX_QUERIES } from "./config";
import { gatherQlFiles } from "./pure/files";
import { basename } from "path";
import {
createTimestampFile,
findLanguage,
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
showBinaryChoiceDialog,
tryGetQueryMetadata,
} from "./helpers";
import { displayQuickQuery } from "./quick-query";
import { QueryRunner } from "./queryRunner";
import {
CoreCompletedQuery,
CoreQueryResults,
QueryRunner,
} from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { DatabaseItem, DatabaseManager } from "./local-databases";
import { createInitialQueryInfo } from "./run-queries-shared";
import {
createInitialQueryInfo,
determineSelectedQuery,
EvaluatorLogPaths,
generateEvalLogSummaries,
logEndSummary,
QueryEvaluationInfo,
QueryOutputDir,
QueryWithResults,
SelectedQuery,
} from "./run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
import { WebviewReveal } from "./interface-utils";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { CodeQLCliServer } from "./cli";
import { LocalQueryCommands } from "./common/commands";
import { App } from "./common/app";
import { DisposableObject } from "./pure/disposable-object";
import { QueryResultType } from "./pure/new-messages";
import { redactableError } from "./pure/errors";
type LocalQueryOptions = {
app: App;
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseManager: DatabaseManager;
cliServer: CodeQLCliServer;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
};
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}
export function getLocalQueryCommands({
app,
queryRunner,
queryHistoryManager,
databaseManager,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
}: LocalQueryOptions): LocalQueryCommands {
const runQuery = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
progress,
token,
undefined,
function formatResultMessage(result: CoreQueryResults): string {
switch (result.resultType) {
case QueryResultType.CANCELLATION:
return `cancelled after ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
case QueryResultType.OOM:
return "out of memory";
case QueryResultType.SUCCESS:
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
case QueryResultType.COMPILATION_ERROR:
return `compilation failed: ${result.message}`;
case QueryResultType.OTHER_ERROR:
default:
return result.message ? `failed: ${result.message}` : "failed";
}
}
/**
* Tracks the evaluation of a local query, including its interactions with the UI.
*
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
* the `complete()` function once the query has completed (successfully or otherwise).
*
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
* evaluation, and we can only respond to events from the debug adapter.
*/
export class LocalQueryRun {
public constructor(
private readonly outputDir: QueryOutputDir,
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
private readonly queryHistoryManager: QueryHistoryManager,
private readonly cliServer: CodeQLCliServer,
) {}
/**
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
* summaries, updates the query history item for the evaluation with the results and evaluation
* time, and displays the results view.
*
* This function must be called when the evaluation completes, whether the evaluation was
* successful or not.
* */
public async complete(results: CoreQueryResults): Promise<void> {
const evalLogPaths = await this.summarizeEvalLog(
results.resultType,
this.outputDir,
this.logger,
);
if (evalLogPaths !== undefined) {
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
}
const queryWithResults = await this.getCompletedQueryInfo(results);
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
await this.localQueries.showResultsForCompletedQuery(
this.queryInfo as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
await this.queryHistoryManager.refreshTreeView();
}
/**
* Updates the UI in the case where query evaluation throws an exception.
*/
public async fail(err: Error): Promise<void> {
err.message = `Error running query: ${err.message}`;
this.queryInfo.failureReason = err.message;
await this.queryHistoryManager.refreshTreeView();
}
/**
* Generate summaries of the structured evaluator log.
*/
private async summarizeEvalLog(
resultType: QueryResultType,
outputDir: QueryOutputDir,
logger: BaseLogger,
): Promise<EvaluatorLogPaths | undefined> {
const evalLogPaths = await generateEvalLogSummaries(
this.cliServer,
outputDir,
);
if (evalLogPaths !== undefined) {
if (evalLogPaths.endSummary !== undefined) {
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
}
} else {
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
if (resultType === QueryResultType.SUCCESS) {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
);
} else {
// Don't bother notifying the user if there's no log. For some errors, like compilation
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
// a log depends on how far execution got before termination.
}
}
return evalLogPaths;
}
/**
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
* result, in the form expected by the query history UI.
*/
private async getCompletedQueryInfo(
results: CoreQueryResults,
): Promise<QueryWithResults> {
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(
this.cliServer,
this.queryInfo.initialInfo.queryPath,
);
const query = new QueryEvaluationInfo(
this.outputDir.querySaveDir,
this.dbItem.databaseUri.fsPath,
await this.dbItem.hasMetadataFile(),
this.queryInfo.initialInfo.quickEvalPosition,
metadata,
);
if (results.resultType !== QueryResultType.SUCCESS) {
const message = results.message
? redactableError`Failed to run query: ${results.message}`
: redactableError`Failed to run query`;
void showAndLogExceptionWithTelemetry(message);
}
const message = formatResultMessage(results);
const successful = results.resultType === QueryResultType.SUCCESS;
return {
query,
result: {
evaluationTime: results.evaluationTime,
queryId: 0,
resultType: successful
? QueryResultType.SUCCESS
: QueryResultType.OTHER_ERROR,
runId: 0,
message,
},
message,
successful,
};
}
}
export class LocalQueries extends DisposableObject {
public constructor(
private readonly app: App,
private readonly queryRunner: QueryRunner,
private readonly queryHistoryManager: QueryHistoryManager,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
private readonly databaseUI: DatabaseUI,
private readonly localQueryResultsView: ResultsView,
private readonly queryStorageDir: string,
) {
super();
}
public getCommands(): LocalQueryCommands {
return {
"codeQL.runQuery": this.runQuery.bind(this),
"codeQL.runQueryContextEditor": this.runQuery.bind(this),
"codeQL.runQueryOnMultipleDatabases":
this.runQueryOnMultipleDatabases.bind(this),
"codeQL.runQueryOnMultipleDatabasesContextEditor":
this.runQueryOnMultipleDatabases.bind(this),
"codeQL.runQueries": this.runQueries.bind(this),
"codeQL.quickEval": this.quickEval.bind(this),
"codeQL.quickEvalContextEditor": this.quickEval.bind(this),
"codeQL.codeLensQuickEval": this.codeLensQuickEval.bind(this),
"codeQL.quickQuery": this.quickQuery.bind(this),
};
}
private async runQuery(uri: Uri | undefined): Promise<void> {
await withProgress(
async (progress, token) => {
await this.compileAndRunQuery(false, uri, progress, token, undefined);
},
{
title: "Running query",
cancellable: true,
},
);
}
const runQueryOnMultipleDatabases = async (uri: Uri | undefined) =>
withProgress(
private async runQueryOnMultipleDatabases(
uri: Uri | undefined,
): Promise<void> {
await withProgress(
async (progress, token) =>
await compileAndRunQueryOnMultipleDatabases(
cliServer,
queryRunner,
queryHistoryManager,
databaseManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
progress,
token,
uri,
),
await this.compileAndRunQueryOnMultipleDatabases(progress, token, uri),
{
title: "Running query on selected databases",
cancellable: true,
},
);
}
const runQueries = async (_: Uri | undefined, multi: Uri[]) =>
withProgress(
private async runQueries(_: Uri | undefined, multi: Uri[]): Promise<void> {
await withProgress(
async (progress, token) => {
const maxQueryCount = MAX_QUERIES.getValue() as number;
const [files, dirFound] = await gatherQlFiles(
@@ -142,12 +312,7 @@ export function getLocalQueryCommands({
await Promise.all(
queryUris.map(async (uri) =>
compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
this.compileAndRunQuery(
false,
uri,
wrappedProgress,
@@ -162,38 +327,24 @@ export function getLocalQueryCommands({
cancellable: true,
},
);
}
const quickEval = async (uri: Uri) =>
withProgress(
private async quickEval(uri: Uri): Promise<void> {
await withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
true,
uri,
progress,
token,
undefined,
);
await this.compileAndRunQuery(true, uri, progress, token, undefined);
},
{
title: "Running query",
cancellable: true,
},
);
}
const codeLensQuickEval = async (uri: Uri, range: Range) =>
withProgress(
private async codeLensQuickEval(uri: Uri, range: Range): Promise<void> {
await withProgress(
async (progress, token) =>
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
await this.compileAndRunQuery(
true,
uri,
progress,
@@ -206,185 +357,234 @@ export function getLocalQueryCommands({
cancellable: true,
},
);
}
const quickQuery = async () =>
withProgress(
private async quickQuery(): Promise<void> {
await withProgress(
async (progress, token) =>
displayQuickQuery(app, cliServer, databaseUI, progress, token),
displayQuickQuery(
this.app,
this.cliServer,
this.databaseUI,
progress,
token,
),
{
title: "Run Quick Query",
},
);
}
return {
"codeQL.runQuery": runQuery,
"codeQL.runQueryContextEditor": runQuery,
"codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases,
"codeQL.runQueryOnMultipleDatabasesContextEditor":
runQueryOnMultipleDatabases,
"codeQL.runQueries": runQueries,
"codeQL.quickEval": quickEval,
"codeQL.quickEvalContextEditor": quickEval,
"codeQL.codeLensQuickEval": codeLensQuickEval,
"codeQL.quickQuery": quickQuery,
};
}
/**
* Creates a new `LocalQueryRun` object to track a query evaluation. This creates a timestamp
* file in the query's output directory, creates a `LocalQueryInfo` object, and registers that
* object with the query history manager.
*
* Once the evaluation is complete, the client must call `complete()` on the `LocalQueryRun`
* object to update the UI based on the results of the query.
*/
public async createLocalQueryRun(
selectedQuery: SelectedQuery,
dbItem: DatabaseItem,
outputDir: QueryOutputDir,
tokenSource: CancellationTokenSource,
): Promise<LocalQueryRun> {
await createTimestampFile(outputDir.querySaveDir);
export async function compileAndRunQuery(
qs: QueryRunner,
qhm: QueryHistoryManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
quickEval: boolean,
selectedQuery: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
if (qs !== undefined) {
// If no databaseItem is specified, use the database currently selected in the Databases UI
databaseItem =
databaseItem || (await databaseUI.getDatabaseItem(progress, token));
if (databaseItem === undefined) {
throw new Error("Can't run query without a selected database");
if (this.queryRunner.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${outputDir.logPath}`,
);
}
const databaseInfo = {
name: databaseItem.name,
databaseUri: databaseItem.databaseUri.toString(),
};
// handle cancellation from the history view.
const source = new CancellationTokenSource();
token.onCancellationRequested(() => source.cancel());
const initialInfo = await createInitialQueryInfo(selectedQuery, {
databaseUri: dbItem.databaseUri.toString(),
name: dbItem.name,
});
const initialInfo = await createInitialQueryInfo(
selectedQuery,
databaseInfo,
// When cancellation is requested from the query history view, we just stop the debug session.
const queryInfo = new LocalQueryInfo(initialInfo, tokenSource);
this.queryHistoryManager.addQuery(queryInfo);
const logger = new TeeLogger(this.queryRunner.logger, outputDir.logPath);
return new LocalQueryRun(
outputDir,
this,
queryInfo,
dbItem,
logger,
this.queryHistoryManager,
this.cliServer,
);
}
public async compileAndRunQuery(
quickEval: boolean,
queryUri: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
await this.compileAndRunQueryInternal(
quickEval,
queryUri,
progress,
token,
databaseItem,
range,
);
}
/** Used by tests */
public async compileAndRunQueryInternal(
quickEval: boolean,
queryUri: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<CoreCompletedQuery> {
const selectedQuery = await determineSelectedQuery(
queryUri,
quickEval,
range,
);
const item = new LocalQueryInfo(initialInfo, source);
qhm.addQuery(item);
// If no databaseItem is specified, use the database currently selected in the Databases UI
databaseItem =
databaseItem || (await this.databaseUI.getDatabaseItem(progress, token));
if (databaseItem === undefined) {
throw new Error("Can't run query without a selected database");
}
const additionalPacks = getOnDiskWorkspaceFolders();
const extensionPacks = (await this.cliServer.useExtensionPacks())
? Object.keys(await this.cliServer.resolveQlpacks(additionalPacks, true))
: undefined;
const coreQueryRun = this.queryRunner.createQueryRun(
databaseItem.databaseUri.fsPath,
{
queryPath: selectedQuery.queryPath,
quickEvalPosition: selectedQuery.quickEvalPosition,
},
true,
additionalPacks,
extensionPacks,
this.queryStorageDir,
undefined,
undefined,
);
// handle cancellation from the history view.
const source = new CancellationTokenSource();
try {
const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase(
token.onCancellationRequested(() => source.cancel());
const localQueryRun = await this.createLocalQueryRun(
selectedQuery,
databaseItem,
initialInfo,
queryStorageDir,
progress,
source.token,
undefined,
item,
coreQueryRun.outputDir,
source,
);
qhm.completeQuery(item, completedQueryInfo);
await showResultsForCompletedQuery(
localQueryResultsView,
item as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
} catch (e) {
const err = asError(e);
err.message = `Error running query: ${err.message}`;
item.failureReason = err.message;
throw e;
try {
const results = await coreQueryRun.evaluate(
progress,
source.token,
localQueryRun.logger,
);
await localQueryRun.complete(results);
return results;
} catch (e) {
// It's odd that we have two different ways for a query evaluation to fail: by throwing an
// exception, and by returning a result with a failure code. This is how the code worked
// before the refactoring, so it's been preserved, but we should probably figure out how
// to unify both error handling paths.
const err = asError(e);
await localQueryRun.fail(err);
throw e;
}
} finally {
await qhm.refreshTreeView();
source.dispose();
}
}
}
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}
async function compileAndRunQueryOnMultipleDatabases(
cliServer: CodeQLCliServer,
qs: QueryRunner,
qhm: QueryHistoryManager,
dbm: DatabaseManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined,
): Promise<void> {
let filteredDBs = dbm.databaseItems;
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
"No databases found. Please add a suitable database to your workspace.",
);
return;
}
// If possible, only show databases with the right language (otherwise show all databases).
const queryLanguage = await findLanguage(cliServer, uri);
if (queryLanguage) {
filteredDBs = dbm.databaseItems.filter(
(db) => db.language === queryLanguage,
);
private async compileAndRunQueryOnMultipleDatabases(
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined,
): Promise<void> {
let filteredDBs = this.databaseManager.databaseItems;
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
"No databases found. Please add a suitable database to your workspace.",
);
return;
}
}
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>((dbItem) => ({
databaseItem: dbItem,
label: dbItem.name,
description: dbItem.language,
}));
/**
* Databases that were selected in the quick pick menu.
*/
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
quickPickItems,
{ canPickMany: true, ignoreFocusOut: true },
);
if (quickpick !== undefined) {
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
const skippedDatabases = [];
const errors = [];
for (const item of quickpick) {
try {
await compileAndRunQuery(
qs,
qhm,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
progress,
token,
item.databaseItem,
// If possible, only show databases with the right language (otherwise show all databases).
const queryLanguage = await findLanguage(this.cliServer, uri);
if (queryLanguage) {
filteredDBs = this.databaseManager.databaseItems.filter(
(db) => db.language === queryLanguage,
);
if (filteredDBs.length === 0) {
void showAndLogErrorMessage(
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
);
} catch (e) {
skippedDatabases.push(item.label);
errors.push(getErrorMessage(e));
return;
}
}
if (skippedDatabases.length > 0) {
void extLogger.log(`Errors:\n${errors.join("\n")}`);
void showAndLogWarningMessage(
`The following databases were skipped:\n${skippedDatabases.join(
"\n",
)}.\nFor details about the errors, see the logs.`,
);
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>((dbItem) => ({
databaseItem: dbItem,
label: dbItem.name,
description: dbItem.language,
}));
/**
* Databases that were selected in the quick pick menu.
*/
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
quickPickItems,
{ canPickMany: true, ignoreFocusOut: true },
);
if (quickpick !== undefined) {
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
const skippedDatabases = [];
const errors = [];
for (const item of quickpick) {
try {
await this.compileAndRunQuery(
false,
uri,
progress,
token,
item.databaseItem,
);
} catch (e) {
skippedDatabases.push(item.label);
errors.push(getErrorMessage(e));
}
}
if (skippedDatabases.length > 0) {
void extLogger.log(`Errors:\n${errors.join("\n")}`);
void showAndLogWarningMessage(
`The following databases were skipped:\n${skippedDatabases.join(
"\n",
)}.\nFor details about the errors, see the logs.`,
);
}
} else {
void showAndLogErrorMessage("No databases selected.");
}
} else {
void showAndLogErrorMessage("No databases selected.");
}
public async showResultsForCompletedQuery(
query: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
): Promise<void> {
await this.localQueryResultsView.showResults(query, forceReveal, false);
}
}
export async function showResultsForCompletedQuery(
localQueryResultsView: ResultsView,
query: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
): Promise<void> {
await localQueryResultsView.showResults(query, forceReveal, false);
}

View File

@@ -1,7 +1,6 @@
import { readFile } from "fs-extra";
import { RawSourceMap, SourceMapConsumer } from "source-map";
import {
commands,
Position,
Selection,
TextDocument,
@@ -16,6 +15,7 @@ import { DisposableObject } from "../pure/disposable-object";
import { extLogger } from "../common";
import { getErrorMessage } from "../pure/helpers-pure";
import { SummaryLanguageSupportCommands } from "../common/commands";
import { App } from "../common/app";
/** A `Position` within a specified file on disk. */
interface PositionInFile {
@@ -55,7 +55,7 @@ export class SummaryLanguageSupport extends DisposableObject {
*/
private sourceMap: SourceMapConsumer | undefined = undefined;
constructor() {
constructor(private readonly app: App) {
super();
this.push(
@@ -160,7 +160,7 @@ export class SummaryLanguageSupport extends DisposableObject {
private async updateContext(): Promise<void> {
const position = await this.getQLSourceLocation();
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeql.hasQLSource",
position !== undefined,

View File

@@ -1,6 +1,6 @@
import { join, resolve } from "path";
import { pathExists } from "fs-extra";
import { setupServer, SetupServerApi } from "msw/node";
import { setupServer, SetupServer } from "msw/node";
import { DisposableObject } from "../pure/disposable-object";
@@ -14,7 +14,7 @@ import { getDirectoryNamesInsidePath } from "../pure/files";
export class MockGitHubApiServer extends DisposableObject {
private _isListening: boolean;
private readonly server: SetupServerApi;
private readonly server: SetupServer;
private readonly recorder: Recorder;
constructor() {

View File

@@ -2,7 +2,7 @@ import { ensureDir, writeFile } from "fs-extra";
import { join } from "path";
import { MockedRequest } from "msw";
import { SetupServerApi } from "msw/node";
import { SetupServer } from "msw/node";
import { IsomorphicResponse } from "@mswjs/interceptors";
import { Headers } from "headers-polyfill";
@@ -22,7 +22,7 @@ export class Recorder extends DisposableObject {
private _isRecording = false;
constructor(private readonly server: SetupServerApi) {
constructor(private readonly server: SetupServer) {
super();
this.onRequestStart = this.onRequestStart.bind(this);
this.onResponseBypass = this.onResponseBypass.bind(this);
@@ -88,13 +88,18 @@ export class Recorder extends DisposableObject {
const bodyFileName = `${i}-${writtenRequest.request.kind}.body.${extension}`;
const bodyFilePath = join(scenarioDirectory, bodyFileName);
await writeFile(bodyFilePath, writtenRequest.response.body);
let bodyFileLink = undefined;
if (writtenRequest.response.body) {
await writeFile(bodyFilePath, writtenRequest.response.body || "");
bodyFileLink = `file:${bodyFileName}`;
}
writtenRequest = {
...writtenRequest,
response: {
...writtenRequest.response,
body: `file:${bodyFileName}`,
body: bodyFileLink,
},
};
}

View File

@@ -1,13 +1,5 @@
import { pathExists } from "fs-extra";
import {
commands,
env,
ExtensionContext,
ExtensionMode,
QuickPickItem,
Uri,
window,
} from "vscode";
import { env, QuickPickItem, Uri, window } from "vscode";
import {
getMockGitHubApiServerScenariosPath,
@@ -16,6 +8,8 @@ import {
import { DisposableObject } from "../pure/disposable-object";
import { MockGitHubApiServer } from "./mock-gh-api-server";
import { MockGitHubApiServerCommands } from "../common/commands";
import { App, AppMode } from "../common/app";
import path from "path";
/**
* "Interface" to the mock GitHub API server which implements VSCode interactions, such as
@@ -27,7 +21,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
private readonly server: MockGitHubApiServer;
private readonly config: MockGitHubApiConfigListener;
constructor(private readonly ctx: ExtensionContext) {
constructor(private readonly app: App) {
super();
this.server = new MockGitHubApiServer();
this.config = new MockGitHubApiConfigListener();
@@ -55,12 +49,12 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
public async stopServer(): Promise<void> {
this.server.stopServer();
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.scenarioLoaded",
false,
);
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.recording",
false,
@@ -92,7 +86,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
// Set a value in the context to track whether we have a scenario loaded.
// This allows us to use this to show/hide commands (see package.json)
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.scenarioLoaded",
true,
@@ -106,7 +100,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
await window.showInformationMessage("No scenario currently loaded");
} else {
await this.server.unloadScenario();
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.scenarioLoaded",
false,
@@ -125,7 +119,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
if (this.server.isScenarioLoaded) {
await this.server.unloadScenario();
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.scenarioLoaded",
false,
@@ -137,7 +131,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
await this.server.startRecording();
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.recording",
true,
@@ -155,7 +149,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
}
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.recording",
false,
@@ -210,7 +204,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
private async stopRecording(): Promise<void> {
// Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json)
await commands.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQL.mockGitHubApiServer.recording",
false,
@@ -225,11 +219,11 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
return scenariosPath;
}
if (this.ctx.extensionMode === ExtensionMode.Development) {
const developmentScenariosPath = Uri.joinPath(
this.ctx.extensionUri,
if (this.app.mode === AppMode.Development) {
const developmentScenariosPath = path.join(
this.app.extensionPath,
"src/mocks/scenarios",
).fsPath.toString();
);
if (await pathExists(developmentScenariosPath)) {
return developmentScenariosPath;
}

View File

@@ -40,8 +40,8 @@ import {
variantAnalysisStatusToQueryStatus,
} from "../query-status";
import {
deserializeQueryHistory,
serializeQueryHistory,
readQueryHistoryFromFile,
writeQueryHistoryToFile,
} from "./store/query-history-store";
import { pathExists } from "fs-extra";
import { CliVersionConstraint } from "../cli";
@@ -379,7 +379,7 @@ export class QueryHistoryManager extends DisposableObject {
void extLogger.log(
`Reading cached query history from '${this.queryMetadataStorageLocation}'.`,
);
const history = await deserializeQueryHistory(
const history = await readQueryHistoryFromFile(
this.queryMetadataStorageLocation,
);
this.treeDataProvider.allHistory = history;
@@ -395,7 +395,7 @@ export class QueryHistoryManager extends DisposableObject {
}
async writeQueryHistory(): Promise<void> {
await serializeQueryHistory(
await writeQueryHistoryToFile(
this.treeDataProvider.allHistory,
this.queryMetadataStorageLocation,
);

View File

@@ -14,7 +14,7 @@ import { QueryEvaluationInfo } from "../../run-queries-shared";
import { QueryResultType } from "../../pure/legacy-messages";
import { redactableError } from "../../pure/errors";
export async function deserializeQueryHistory(
export async function readQueryHistoryFromFile(
fsPath: string,
): Promise<QueryHistoryInfo[]> {
try {
@@ -109,7 +109,7 @@ export async function deserializeQueryHistory(
* @param queries the list of queries to save.
* @param fsPath the path to save the queries to.
*/
export async function serializeQueryHistory(
export async function writeQueryHistoryToFile(
queries: QueryHistoryInfo[],
fsPath: string,
): Promise<void> {

View File

@@ -16,7 +16,11 @@ import {
DatabaseInfo,
} from "./pure/interface-types";
import { QueryStatus } from "./query-status";
import { QueryEvaluationInfo, QueryWithResults } from "./run-queries-shared";
import {
EvaluatorLogPaths,
QueryEvaluationInfo,
QueryWithResults,
} from "./run-queries-shared";
import { formatLegacyMessage } from "./legacy-query-server/run-queries";
import { sarifParser } from "./sarif-parser";
@@ -70,7 +74,7 @@ export class CompletedQueryInfo implements QueryWithResults {
interpretedResultsSortState: InterpretedResultsSortState | undefined;
/**
* Note that in the {@link deserializeQueryHistory} method, we create a CompletedQueryInfo instance
* Note that in the {@link readQueryHistoryFromFile} method, we create a CompletedQueryInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(evaluation: QueryWithResults) {
@@ -224,7 +228,7 @@ export class LocalQueryInfo {
public evalLogSummarySymbolsLocation: string | undefined;
/**
* Note that in the {@link deserializeQueryHistory} method, we create a FullQueryInfo instance
* Note that in the {@link readQueryHistoryFromFile} method, we create a FullQueryInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(
@@ -253,6 +257,14 @@ export class LocalQueryInfo {
this.initialInfo.userSpecifiedLabel = label;
}
/** Sets the paths to the various structured evaluator logs. */
public setEvaluatorLogPaths(logPaths: EvaluatorLogPaths): void {
this.evalLogLocation = logPaths.log;
this.evalLogSummaryLocation = logPaths.humanReadableSummary;
this.jsonEvalLogSummaryLocation = logPaths.jsonSummary;
this.evalLogSummarySymbolsLocation = logPaths.summarySymbols;
}
/**
* The query's file name, unless it is a quick eval.
* Queries run through quick evaluation are not usually the entire query file.

View File

@@ -9,22 +9,32 @@ import {
registerDatabases,
upgradeDatabase,
} from "../pure/new-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
import { QueryRunner } from "../queryRunner";
import { QueryWithResults } from "../run-queries-shared";
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
import { QueryServerClient } from "./queryserver-client";
import { compileAndRunQueryAgainstDatabase } from "./run-queries";
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
import * as vscode from "vscode";
import { getOnDiskWorkspaceFolders } from "../helpers";
import { CodeQLCliServer } from "../cli";
import { Logger } from "../common";
import { QueryOutputDir } from "../run-queries-shared";
export class NewQueryRunner extends QueryRunner {
constructor(public readonly qs: QueryServerClient) {
super();
}
get cliServer() {
get cliServer(): CodeQLCliServer {
return this.qs.cliServer;
}
get customLogDirectory(): string | undefined {
return this.qs.config.customLogDirectory;
}
get logger(): Logger {
return this.qs.logger;
}
async restartQueryServer(
progress: ProgressCallback,
token: CancellationToken,
@@ -57,25 +67,31 @@ export class NewQueryRunner extends QueryRunner {
};
await this.qs.sendRequest(clearCache, params, token, progress);
}
async compileAndRunQueryAgainstDatabase(
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
public async compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],
extensionPacks: string[] | undefined,
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo,
): Promise<QueryWithResults> {
return await compileAndRunQueryAgainstDatabase(
this.qs.cliServer,
templates: Record<string, string> | undefined,
logger: Logger,
): Promise<CoreQueryResults> {
return await compileAndRunQueryAgainstDatabaseCore(
this.qs,
dbItem,
initialInfo,
queryStorageDir,
dbPath,
query,
generateEvalLog,
additionalPacks,
extensionPacks,
outputDir,
progress,
token,
templates,
queryInfo,
logger,
);
}

View File

@@ -1,7 +1,7 @@
import { ensureFile } from "fs-extra";
import { DisposableObject } from "../pure/disposable-object";
import { CancellationToken, commands } from "vscode";
import { CancellationToken } from "vscode";
import { createMessageConnection, RequestType } from "vscode-jsonrpc/node";
import * as cli from "../cli";
import { QueryServerConfig } from "../config";
@@ -13,6 +13,7 @@ import {
} from "../pure/new-messages";
import { ProgressCallback, ProgressTask } from "../progress";
import { ServerProcess } from "../json-rpc-server";
import { App } from "../common/app";
type ServerOpts = {
logger: Logger;
@@ -53,6 +54,7 @@ export class QueryServerClient extends DisposableObject {
public activeQueryLogger: Logger;
constructor(
app: App,
readonly config: QueryServerConfig,
readonly cliServer: cli.CodeQLCliServer,
readonly opts: ServerOpts,
@@ -66,7 +68,7 @@ export class QueryServerClient extends DisposableObject {
if (config.onDidChangeConfiguration !== undefined) {
this.push(
config.onDidChangeConfiguration(() =>
commands.executeCommand("codeQL.restartQueryServer"),
app.commands.execute("codeQL.restartQueryServer"),
),
);
}

View File

@@ -1,21 +1,10 @@
import { join } from "path";
import { CancellationToken } from "vscode";
import * as cli from "../cli";
import { ProgressCallback } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
getOnDiskWorkspaceFolders,
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
tryGetQueryMetadata,
} from "../helpers";
import { extLogger, TeeLogger } from "../common";
import * as messages from "../pure/new-messages";
import { QueryResultType } from "../pure/legacy-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared";
import { QueryOutputDir } from "../run-queries-shared";
import * as qsClient from "./queryserver-client";
import { redactableError } from "../pure/errors";
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
import { Logger } from "../common";
/**
* run-queries.ts
@@ -31,140 +20,55 @@ import { redactableError } from "../pure/errors";
* output and results.
*/
export async function compileAndRunQueryAgainstDatabase(
cliServer: cli.CodeQLCliServer,
export async function compileAndRunQueryAgainstDatabaseCore(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
dbPath: string,
query: CoreQueryTarget,
generateEvalLog: boolean,
additionalPacks: string[],
extensionPacks: string[] | undefined,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults> {
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
throw new Error(
`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`,
);
}
templates: Record<string, string> | undefined,
logger: Logger,
): Promise<CoreQueryResults> {
const target =
query.quickEvalPosition !== undefined
? {
quickEval: { quickEvalPos: query.quickEvalPosition },
}
: { query: {} };
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(cliServer, initialInfo.queryPath);
const hasMetadataFile = await dbItem.hasMetadataFile();
const query = new QueryEvaluationInfo(
join(queryStorageDir, initialInfo.id),
dbItem.databaseUri.fsPath,
hasMetadataFile,
initialInfo.quickEvalPosition,
metadata,
);
if (!dbItem.contents || dbItem.error) {
throw new Error("Can't run query on invalid database.");
}
const target = query.quickEvalPosition
? {
quickEval: { quickEvalPos: query.quickEvalPosition },
}
: { query: {} };
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
const extensionPacks = (await qs.cliServer.useExtensionPacks())
? Object.keys(await qs.cliServer.resolveQlpacks(diskWorkspaceFolders, true))
: undefined;
const db = dbItem.databaseUri.fsPath;
const logPath = queryInfo ? query.evalLogPath : undefined;
const evalLogPath = generateEvalLog ? outputDir.evalLogPath : undefined;
const queryToRun: messages.RunQueryParams = {
db,
additionalPacks: diskWorkspaceFolders,
db: dbPath,
additionalPacks,
externalInputs: {},
singletonExternalInputs: templates || {},
outputPath: query.resultsPaths.resultsPath,
queryPath: initialInfo.queryPath,
dilPath: query.dilPath,
logPath,
outputPath: outputDir.bqrsPath,
queryPath: query.queryPath,
dilPath: outputDir.dilPath,
logPath: evalLogPath,
target,
extensionPacks,
};
const logger = new TeeLogger(qs.logger, query.logPath);
await query.createTimestampFile();
let result: messages.RunQueryResult | undefined;
try {
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
result = await qs.sendRequest(
messages.runQuery,
queryToRun,
token,
progress,
);
if (qs.config.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${query.logPath}.`,
);
}
} finally {
if (queryInfo) {
if (await query.hasEvalLog()) {
await query.addQueryLogs(queryInfo, qs.cliServer, logger);
} else {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${query.evalLogPath}.`,
);
}
}
}
if (result.resultType !== messages.QueryResultType.SUCCESS) {
const message = result?.message
? redactableError`${result.message}`
: redactableError`Failed to run query`;
void extLogger.log(message.fullMessage);
void showAndLogExceptionWithTelemetry(
redactableError`Failed to run query: ${message}`,
);
}
let message;
switch (result.resultType) {
case messages.QueryResultType.CANCELLATION:
message = `cancelled after ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
break;
case messages.QueryResultType.OOM:
message = "out of memory";
break;
case messages.QueryResultType.SUCCESS:
message = `finished in ${Math.round(
result.evaluationTime / 1000,
)} seconds`;
break;
case messages.QueryResultType.COMPILATION_ERROR:
message = `compilation failed: ${result.message}`;
break;
case messages.QueryResultType.OTHER_ERROR:
default:
message = result.message ? `failed: ${result.message}` : "failed";
break;
}
const successful = result.resultType === messages.QueryResultType.SUCCESS;
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
const result = await qs.sendRequest(
messages.runQuery,
queryToRun,
token,
progress,
);
return {
query,
result: {
evaluationTime: result.evaluationTime,
queryId: 0,
resultType: successful
? QueryResultType.SUCCESS
: QueryResultType.OTHER_ERROR,
runId: 0,
message,
},
message,
successful,
resultType: result.resultType,
message: result.message,
evaluationTime: result.evaluationTime,
};
}

View File

@@ -2,8 +2,44 @@ import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "./cli";
import { ProgressCallback } from "./progress";
import { DatabaseItem } from "./local-databases";
import { InitialQueryInfo, LocalQueryInfo } from "./query-results";
import { QueryWithResults } from "./run-queries-shared";
import { QueryOutputDir } from "./run-queries-shared";
import { Position, QueryResultType } from "./pure/new-messages";
import { BaseLogger, Logger } from "./common";
import { basename, join } from "path";
import { nanoid } from "nanoid";
export interface CoreQueryTarget {
/** The full path to the query. */
queryPath: string;
/**
* Optional position of text to be used as QuickEval target. This need not be in the same file as
* `query`.
*/
quickEvalPosition?: Position;
}
export interface CoreQueryResults {
readonly resultType: QueryResultType;
readonly message: string | undefined;
readonly evaluationTime: number;
}
export interface CoreQueryRun {
readonly queryTarget: CoreQueryTarget;
readonly dbPath: string;
readonly id: string;
readonly outputDir: QueryOutputDir;
evaluate(
progress: ProgressCallback,
token: CancellationToken,
logger: BaseLogger,
): Promise<CoreCompletedQuery>;
}
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
export type CoreCompletedQuery = CoreQueryResults &
Omit<CoreQueryRun, "evaluate">;
export abstract class QueryRunner {
abstract restartQueryServer(
@@ -12,6 +48,8 @@ export abstract class QueryRunner {
): Promise<void>;
abstract cliServer: CodeQLCliServer;
abstract customLogDirectory: string | undefined;
abstract logger: Logger;
abstract onStart(
arg0: (
@@ -25,15 +63,21 @@ export abstract class QueryRunner {
token: CancellationToken,
): Promise<void>;
abstract compileAndRunQueryAgainstDatabase(
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
/**
* Overridden in subclasses to evaluate the query via the query server and return the results.
*/
public abstract compileAndRunQueryAgainstDatabaseCore(
dbPath: string,
query: CoreQueryTarget,
additionalPacks: string[],
extensionPacks: string[] | undefined,
generateEvalLog: boolean,
outputDir: QueryOutputDir,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults>;
templates: Record<string, string> | undefined,
logger: BaseLogger,
): Promise<CoreQueryResults>;
abstract deregisterDatabase(
progress: ProgressCallback,
@@ -54,4 +98,54 @@ export abstract class QueryRunner {
): Promise<void>;
abstract clearPackCache(): Promise<void>;
/**
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
* called to actually evaluate the query. The returned object also contains information about the
* query evaluation that is known even before evaluation starts, including the unique ID of the
* evaluation and the path to its output directory.
*/
public createQueryRun(
dbPath: string,
query: CoreQueryTarget,
generateEvalLog: boolean,
additionalPacks: string[],
extensionPacks: string[] | undefined,
queryStorageDir: string,
id = `${basename(query.queryPath)}-${nanoid()}`,
templates: Record<string, string> | undefined,
): CoreQueryRun {
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
return {
queryTarget: query,
dbPath,
id,
outputDir,
evaluate: async (
progress: ProgressCallback,
token: CancellationToken,
logger: BaseLogger,
): Promise<CoreCompletedQuery> => {
return {
id,
outputDir,
dbPath,
queryTarget: query,
...(await this.compileAndRunQueryAgainstDatabaseCore(
dbPath,
query,
additionalPacks,
extensionPacks,
generateEvalLog,
outputDir,
progress,
token,
templates,
logger,
)),
};
},
};
}
}

View File

@@ -20,18 +20,14 @@ import {
remove,
readdir,
} from "fs-extra";
import {
ensureMetadataIsComplete,
InitialQueryInfo,
LocalQueryInfo,
} from "./query-results";
import { ensureMetadataIsComplete, InitialQueryInfo } from "./query-results";
import { isQuickQueryPath } from "./quick-query";
import { nanoid } from "nanoid";
import { CodeQLCliServer } from "./cli";
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
import { DatabaseManager } from "./local-databases";
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
import { extLogger, Logger } from "./common";
import { BaseLogger, extLogger } from "./common";
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
import { getErrorMessage } from "./pure/helpers-pure";
@@ -42,7 +38,18 @@ import { getErrorMessage } from "./pure/helpers-pure";
* Compiling and running QL queries.
*/
export function findQueryLogFile(resultPath: string): string {
/**
* Holds the paths to the various structured log summary files generated for a query evaluation.
*/
export interface EvaluatorLogPaths {
log: string;
humanReadableSummary: string | undefined;
endSummary: string | undefined;
jsonSummary: string | undefined;
summarySymbols: string | undefined;
}
function findQueryLogFile(resultPath: string): string {
return join(resultPath, "query.log");
}
@@ -66,20 +73,11 @@ function findQueryEvalLogEndSummaryFile(resultPath: string): string {
return join(resultPath, "evaluator-log-end.summary");
}
export class QueryEvaluationInfo {
/**
* Note that in the {@link deserializeQueryHistory} method, we create a QueryEvaluationInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(
public readonly querySaveDir: string,
public readonly dbItemPath: string,
private readonly databaseHasMetadataFile: boolean,
public readonly quickEvalPosition?: messages.Position,
public readonly metadata?: QueryMetadata,
) {
/**/
}
/**
* Provides paths to the files that can be generated in the output directory for a query evaluation.
*/
export class QueryOutputDir {
constructor(public readonly querySaveDir: string) {}
get dilPath() {
return join(this.querySaveDir, "results.dil");
@@ -120,9 +118,35 @@ export class QueryEvaluationInfo {
return findQueryEvalLogEndSummaryFile(this.querySaveDir);
}
get bqrsPath() {
return join(this.querySaveDir, "results.bqrs");
}
}
export class QueryEvaluationInfo extends QueryOutputDir {
// We extend `QueryOutputDir`, rather than having it as a property, because we need
// `QueryOutputDir`'s `querySaveDir` property to be a property of `QueryEvaluationInfo`. This is
// because `QueryEvaluationInfo` is serialized directly as JSON, and before we hoisted
// `QueryOutputDir` out into a base class, `querySaveDir` was a property on `QueryEvaluationInfo`
// itself.
/**
* Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(
querySaveDir: string,
public readonly dbItemPath: string,
private readonly databaseHasMetadataFile: boolean,
public readonly quickEvalPosition?: messages.Position,
public readonly metadata?: QueryMetadata,
) {
super(querySaveDir);
}
get resultsPaths() {
return {
resultsPath: join(this.querySaveDir, "results.bqrs"),
resultsPath: this.bqrsPath,
interpretedResultsPath: join(
this.querySaveDir,
this.metadata?.kind === "graph"
@@ -228,85 +252,6 @@ export class QueryEvaluationInfo {
return pathExists(this.evalLogPath);
}
/**
* Add the structured evaluator log to the query evaluation info.
*/
async addQueryLogs(
queryInfo: LocalQueryInfo,
cliServer: CodeQLCliServer,
logger: Logger,
) {
queryInfo.evalLogLocation = this.evalLogPath;
queryInfo.evalLogSummaryLocation =
await this.generateHumanReadableLogSummary(cliServer);
void this.logEndSummary(queryInfo.evalLogSummaryLocation, logger); // Logged asynchrnously
if (isCanary()) {
// Generate JSON summary for viewer.
await cliServer.generateJsonLogSummary(
this.evalLogPath,
this.jsonEvalLogSummaryPath,
);
queryInfo.jsonEvalLogSummaryLocation = this.jsonEvalLogSummaryPath;
await generateSummarySymbolsFile(
this.evalLogSummaryPath,
this.evalLogSummarySymbolsPath,
);
queryInfo.evalLogSummarySymbolsLocation = this.evalLogSummarySymbolsPath;
}
}
/**
* Calls the appropriate CLI command to generate a human-readable log summary.
* @param qs The query server client.
* @returns The path to the log summary, or `undefined` if the summary could not be generated. */
private async generateHumanReadableLogSummary(
cliServer: CodeQLCliServer,
): Promise<string | undefined> {
try {
await cliServer.generateLogSummary(
this.evalLogPath,
this.evalLogSummaryPath,
this.evalLogEndSummaryPath,
);
return this.evalLogSummaryPath;
} catch (e) {
void showAndLogWarningMessage(
`Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage(
e,
)}`,
);
return undefined;
}
}
/**
* Logs the end summary to the Output window and log file.
* @param logSummaryPath Path to the human-readable log summary
* @param qs The query server client.
*/
private async logEndSummary(
logSummaryPath: string | undefined,
logger: Logger,
): Promise<void> {
if (logSummaryPath === undefined) {
// Failed to generate the log, so we don't expect an end summary either.
return;
}
try {
const endSummaryContent = await readFile(
this.evalLogEndSummaryPath,
"utf-8",
);
void logger.log(" --- Evaluator Log Summary --- ");
void logger.log(endSummaryContent);
} catch (e) {
void showAndLogWarningMessage(
`Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`,
);
}
}
/**
* Creates the CSV file containing the results of this query. This will only be called if the query
* does not have interpreted results and the CSV file does not already exist.
@@ -447,7 +392,7 @@ export interface QueryWithResults {
* Information about which query will be to be run. `quickEvalPosition` and `quickEvalText`
* is only filled in if the query is a quick query.
*/
interface SelectedQuery {
export interface SelectedQuery {
queryPath: string;
quickEvalPosition?: messages.Position;
quickEvalText?: string;
@@ -642,36 +587,113 @@ async function convertToQlPath(filePath: string): Promise<string> {
* Determines the initial information for a query. This is everything of interest
* we know about this query that is available before it is run.
*
* @param selectedQueryUri The Uri of the document containing the query to be run.
* @param selectedQuery The query to run, including any quickeval info.
* @param databaseInfo The database to run the query against.
* @param isQuickEval true if this is a quick evaluation.
* @param range the selection range of the query to be run. Only used if isQuickEval is true.
* @returns The initial information for the query to be run.
*/
export async function createInitialQueryInfo(
selectedQueryUri: Uri | undefined,
selectedQuery: SelectedQuery,
databaseInfo: DatabaseInfo,
isQuickEval: boolean,
range?: Range,
): Promise<InitialQueryInfo> {
// Determine which query to run, based on the selection and the active editor.
const { queryPath, quickEvalPosition, quickEvalText } =
await determineSelectedQuery(selectedQueryUri, isQuickEval, range);
const isQuickEval = selectedQuery.quickEvalPosition !== undefined;
return {
queryPath,
queryPath: selectedQuery.queryPath,
isQuickEval,
isQuickQuery: isQuickQueryPath(queryPath),
isQuickQuery: isQuickQueryPath(selectedQuery.queryPath),
databaseInfo,
id: `${basename(queryPath)}-${nanoid()}`,
id: `${basename(selectedQuery.queryPath)}-${nanoid()}`,
start: new Date(),
...(isQuickEval
? {
queryText: quickEvalText!, // if this query is quick eval, it must have quick eval text
quickEvalPosition,
queryText: selectedQuery.quickEvalText!, // if this query is quick eval, it must have quick eval text
quickEvalPosition: selectedQuery.quickEvalPosition,
}
: {
queryText: await readFile(queryPath, "utf8"),
queryText: await readFile(selectedQuery.queryPath, "utf8"),
}),
};
}
export async function generateEvalLogSummaries(
cliServer: CodeQLCliServer,
outputDir: QueryOutputDir,
): Promise<EvaluatorLogPaths | undefined> {
const log = outputDir.evalLogPath;
if (!(await pathExists(log))) {
// No raw JSON log, so we can't generate any summaries.
return undefined;
}
let humanReadableSummary: string | undefined = undefined;
let endSummary: string | undefined = undefined;
if (await generateHumanReadableLogSummary(cliServer, outputDir)) {
humanReadableSummary = outputDir.evalLogSummaryPath;
endSummary = outputDir.evalLogEndSummaryPath;
}
let jsonSummary: string | undefined = undefined;
let summarySymbols: string | undefined = undefined;
if (isCanary()) {
// Generate JSON summary for viewer.
jsonSummary = outputDir.jsonEvalLogSummaryPath;
await cliServer.generateJsonLogSummary(log, jsonSummary);
if (humanReadableSummary !== undefined) {
summarySymbols = outputDir.evalLogSummarySymbolsPath;
await generateSummarySymbolsFile(humanReadableSummary, summarySymbols);
}
}
return {
log,
humanReadableSummary,
endSummary,
jsonSummary,
summarySymbols,
};
}
/**
* Calls the appropriate CLI command to generate a human-readable log summary.
* @param cliServer The cli server client.
* @param outputDir The query's output directory, where all of the logs are located.
* @returns True if the summary and end summary were generated, or false if not.
*/
async function generateHumanReadableLogSummary(
cliServer: CodeQLCliServer,
outputDir: QueryOutputDir,
): Promise<boolean> {
try {
await cliServer.generateLogSummary(
outputDir.evalLogPath,
outputDir.evalLogSummaryPath,
outputDir.evalLogEndSummaryPath,
);
return true;
} catch (e) {
void showAndLogWarningMessage(
`Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage(
e,
)}`,
);
return false;
}
}
/**
* Logs the end summary to the Output window and log file.
* @param logSummaryPath Path to the human-readable log summary
* @param qs The query server client.
*/
export async function logEndSummary(
endSummary: string,
logger: BaseLogger,
): Promise<void> {
try {
const endSummaryContent = await readFile(endSummary, "utf-8");
void logger.log(" --- Evaluator Log Summary --- ");
void logger.log(endSummaryContent);
} catch (e) {
void showAndLogWarningMessage(
`Could not read structured evaluator log end of summary file at ${endSummary}.`,
);
}
}

View File

@@ -1,14 +1,7 @@
import { join } from "path";
import { ensureDir, writeFile } from "fs-extra";
import {
CancellationToken,
commands,
Uri,
ViewColumn,
window,
workspace,
} from "vscode";
import { CancellationToken, Uri, ViewColumn, window, workspace } from "vscode";
import {
ProgressCallback,
UserCancellationException,
@@ -35,6 +28,7 @@ import {
RepositoriesFilterSortStateWithIds,
} from "../pure/variant-analysis-filter-sort";
import { Credentials } from "../common/authentication";
import { AppCommandManager } from "../common/commands";
const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2;
@@ -46,6 +40,7 @@ export async function exportVariantAnalysisResults(
variantAnalysisManager: VariantAnalysisManager,
variantAnalysisId: number,
filterSort: RepositoriesFilterSortStateWithIds | undefined,
commandManager: AppCommandManager,
credentials: Credentials,
): Promise<void> {
await withProgress(
@@ -149,6 +144,7 @@ export async function exportVariantAnalysisResults(
getAnalysesResults(),
repositories?.length ?? 0,
exportFormat,
commandManager,
credentials,
progress,
token,
@@ -169,6 +165,7 @@ export async function exportVariantAnalysisAnalysisResults(
>,
expectedAnalysesResultsCount: number,
exportFormat: "gist" | "local",
commandManager: AppCommandManager,
credentials: Credentials,
progress: ProgressCallback,
token: CancellationToken,
@@ -199,6 +196,7 @@ export async function exportVariantAnalysisAnalysisResults(
description,
markdownFiles,
exportFormat,
commandManager,
credentials,
progress,
token,
@@ -243,6 +241,7 @@ export async function exportResults(
description: string,
markdownFiles: MarkdownFile[],
exportFormat: "gist" | "local",
commandManager: AppCommandManager,
credentials: Credentials,
progress?: ProgressCallback,
token?: CancellationToken,
@@ -255,6 +254,7 @@ export async function exportResults(
await exportToGist(
description,
markdownFiles,
commandManager,
credentials,
progress,
token,
@@ -263,6 +263,7 @@ export async function exportResults(
await exportToLocalMarkdown(
exportedResultsPath,
markdownFiles,
commandManager,
progress,
token,
);
@@ -272,6 +273,7 @@ export async function exportResults(
export async function exportToGist(
description: string,
markdownFiles: MarkdownFile[],
commandManager: AppCommandManager,
credentials: Credentials,
progress?: ProgressCallback,
token?: CancellationToken,
@@ -303,7 +305,7 @@ export async function exportToGist(
if (!shouldOpenGist) {
return;
}
return commands.executeCommand("vscode.open", Uri.parse(gistUrl));
return commandManager.execute("vscode.open", Uri.parse(gistUrl));
});
}
}
@@ -334,6 +336,7 @@ const buildVariantAnalysisGistDescription = (
async function exportToLocalMarkdown(
exportedResultsPath: string,
markdownFiles: MarkdownFile[],
commandManager: AppCommandManager,
progress?: ProgressCallback,
token?: CancellationToken,
) {
@@ -366,6 +369,6 @@ async function exportToLocalMarkdown(
const summaryFilePath = join(exportedResultsPath, "_summary.md");
const summaryFile = await workspace.openTextDocument(summaryFilePath);
await window.showTextDocument(summaryFile, ViewColumn.One);
await commands.executeCommand("revealFileInOS", Uri.file(summaryFilePath));
await commandManager.execute("revealFileInOS", Uri.file(summaryFilePath));
});
}

View File

@@ -6,7 +6,6 @@ import {
} from "./gh-api/gh-api-client";
import {
CancellationToken,
commands,
env,
EventEmitter,
ExtensionContext,
@@ -116,6 +115,7 @@ export class VariantAnalysisManager
super();
this.variantAnalysisMonitor = this.push(
new VariantAnalysisMonitor(
app,
this.shouldCancelMonitorVariantAnalysis.bind(this),
),
);
@@ -239,11 +239,11 @@ export class VariantAnalysisManager
`Variant analysis ${processedVariantAnalysis.query.name} submitted for processing`,
);
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.openVariantAnalysisView",
processedVariantAnalysis.id,
);
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.monitorVariantAnalysis",
processedVariantAnalysis,
);
@@ -273,7 +273,7 @@ export class VariantAnalysisManager
this.makeResultDownloadChecker(variantAnalysis),
))
) {
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.monitorVariantAnalysis",
variantAnalysis,
);
@@ -317,7 +317,9 @@ export class VariantAnalysisManager
}
if (!this.views.has(variantAnalysisId)) {
// The view will register itself with the manager, so we don't need to do anything here.
this.track(new VariantAnalysisView(this.ctx, variantAnalysisId, this));
this.track(
new VariantAnalysisView(this.ctx, this.app, variantAnalysisId, this),
);
}
const variantAnalysisView = this.views.get(variantAnalysisId)!;
@@ -502,10 +504,7 @@ export class VariantAnalysisManager
public async monitorVariantAnalysis(
variantAnalysis: VariantAnalysis,
): Promise<void> {
await this.variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
this.app.credentials,
);
await this.variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
}
public async autoDownloadVariantAnalysisResult(
@@ -641,7 +640,7 @@ export class VariantAnalysisManager
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis);
await commands.executeCommand(
await this.app.commands.execute(
"vscode.open",
Uri.parse(actionsWorkflowRunUrl),
);
@@ -689,6 +688,7 @@ export class VariantAnalysisManager
this,
variantAnalysisId,
filterSort,
this.app.commands,
this.app.credentials,
);
}

View File

@@ -1,4 +1,4 @@
import { commands, EventEmitter } from "vscode";
import { env, EventEmitter } from "vscode";
import { getVariantAnalysis } from "./gh-api/gh-api-client";
import {
@@ -13,7 +13,7 @@ import { DisposableObject } from "../pure/disposable-object";
import { sleep } from "../pure/time";
import { getErrorMessage } from "../pure/helpers-pure";
import { showAndLogWarningMessage } from "../helpers";
import { Credentials } from "../common/authentication";
import { App } from "../common/app";
export class VariantAnalysisMonitor extends DisposableObject {
// With a sleep of 5 seconds, the maximum number of attempts takes
@@ -27,6 +27,7 @@ export class VariantAnalysisMonitor extends DisposableObject {
readonly onVariantAnalysisChange = this._onVariantAnalysisChange.event;
constructor(
private readonly app: App,
private readonly shouldCancelMonitor: (
variantAnalysisId: number,
) => Promise<boolean>,
@@ -36,7 +37,6 @@ export class VariantAnalysisMonitor extends DisposableObject {
public async monitorVariantAnalysis(
variantAnalysis: VariantAnalysis,
credentials: Credentials,
): Promise<void> {
let attemptCount = 0;
const scannedReposDownloaded: number[] = [];
@@ -51,13 +51,17 @@ export class VariantAnalysisMonitor extends DisposableObject {
let variantAnalysisSummary: ApiVariantAnalysis;
try {
variantAnalysisSummary = await getVariantAnalysis(
credentials,
this.app.credentials,
variantAnalysis.controllerRepo.id,
variantAnalysis.id,
);
} catch (e) {
void showAndLogWarningMessage(
`Error while monitoring variant analysis: ${getErrorMessage(e)}`,
`Error while monitoring variant analysis ${
variantAnalysis.query.name
} (${variantAnalysis.query.language}) [${new Date(
variantAnalysis.executionStartTime,
).toLocaleString(env.language)}]: ${getErrorMessage(e)}`,
);
continue;
}
@@ -87,7 +91,7 @@ export class VariantAnalysisMonitor extends DisposableObject {
scannedRepo: VariantAnalysisScannedRepository,
variantAnalysisSummary: VariantAnalysis,
) {
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.autoDownloadVariantAnalysisResult",
scannedRepo,
variantAnalysisSummary,

View File

@@ -2,6 +2,7 @@ import { ExtensionContext, WebviewPanel, WebviewPanelSerializer } from "vscode";
import { VariantAnalysisView } from "./variant-analysis-view";
import { VariantAnalysisState } from "../pure/interface-types";
import { VariantAnalysisViewManager } from "./variant-analysis-view-manager";
import { App } from "../common/app";
export class VariantAnalysisViewSerializer implements WebviewPanelSerializer {
private resolvePromises: Array<
@@ -10,7 +11,10 @@ export class VariantAnalysisViewSerializer implements WebviewPanelSerializer {
private manager?: VariantAnalysisViewManager<VariantAnalysisView>;
public constructor(private readonly ctx: ExtensionContext) {}
public constructor(
private readonly ctx: ExtensionContext,
private readonly app: App,
) {}
onExtensionLoaded(
manager: VariantAnalysisViewManager<VariantAnalysisView>,
@@ -49,6 +53,7 @@ export class VariantAnalysisViewSerializer implements WebviewPanelSerializer {
const view = new VariantAnalysisView(
this.ctx,
this.app,
variantAnalysisState.variantAnalysisId,
manager,
);

View File

@@ -1,4 +1,4 @@
import { commands, ExtensionContext, ViewColumn } from "vscode";
import { ExtensionContext, ViewColumn } from "vscode";
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
import { extLogger } from "../common";
import {
@@ -23,6 +23,7 @@ import { telemetryListener } from "../telemetry";
import { redactableError } from "../pure/errors";
import { DataFlowPathsView } from "./data-flow-paths-view";
import { DataFlowPaths } from "./shared/data-flow-paths";
import { App } from "../common/app";
export class VariantAnalysisView
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
@@ -33,6 +34,7 @@ export class VariantAnalysisView
public constructor(
ctx: ExtensionContext,
private readonly app: App,
public readonly variantAnalysisId: number,
private readonly manager: VariantAnalysisViewManager<VariantAnalysisView>,
) {
@@ -118,7 +120,7 @@ export class VariantAnalysisView
await this.manager.cancelVariantAnalysis(this.variantAnalysisId);
break;
case "requestRepositoryResults":
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.loadVariantAnalysisRepoResults",
this.variantAnalysisId,
msg.repositoryFullName,
@@ -131,7 +133,7 @@ export class VariantAnalysisView
await this.manager.openQueryText(this.variantAnalysisId);
break;
case "copyRepositoryList":
void commands.executeCommand(
void this.app.commands.execute(
"codeQL.copyVariantAnalysisRepoList",
this.variantAnalysisId,
msg.filterSort,

View File

@@ -83,8 +83,8 @@ const config: Config = {
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/test/__mocks__/fileMock.ts",
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.ts",
"<rootDir>/../../test/__mocks__/fileMock.ts",
"\\.(css|less)$": "<rootDir>/../../test/__mocks__/styleMock.ts",
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@@ -186,7 +186,7 @@ const config: Config = {
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: [
// These use ES modules, so need to be transformed
"node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6)/.*)",
"node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6|d3|d3-(.*)|internmap|delaunator|robust-predicates)/.*)",
],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them

View File

@@ -0,0 +1,120 @@
import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import { ResultsApp } from "../results";
import {
Interpretation,
IntoResultsViewMsg,
SortDirection,
} from "../../../pure/interface-types";
import * as fs from "fs-extra";
import { resolve } from "path";
import { ColumnKindCode } from "../../../pure/bqrs-cli-types";
const exampleSarif = fs.readJSONSync(
resolve(
__dirname,
"../../../../test/vscode-tests/no-workspace/data/sarif/validSarif.sarif",
),
);
describe(ResultsApp.name, () => {
const render = () => reactRender(<ResultsApp />);
const postMessage = async (msg: IntoResultsViewMsg) => {
// window.postMessage doesn't set the origin correctly, see
// https://github.com/jsdom/jsdom/issues/2745
window.dispatchEvent(
new MessageEvent("message", {
source: window,
origin: window.location.origin,
data: msg,
}),
);
// The event is dispatched asynchronously, so we need to wait for it to be handled.
await new Promise((resolve) => setTimeout(resolve, 0));
};
it("renders results", async () => {
render();
const interpretation: Interpretation = {
sourceLocationPrefix: "/a/b/c",
numTruncatedResults: 0,
numTotalResults: 1,
data: {
t: "SarifInterpretationData",
sortState: undefined,
...exampleSarif,
},
};
const message: IntoResultsViewMsg = {
t: "setState",
resultsPath: "/a/b/c/results",
origResultsPaths: {
resultsPath: "/a/b/c/results.bqrs",
interpretedResultsPath: "/a/b/c/interpreted-results.sarif",
},
sortedResultsMap: {
"1": {
resultsPath: "/a/b/c/results.bqrs",
sortState: {
columnIndex: 1,
sortDirection: SortDirection.asc,
},
},
},
interpretation,
database: {
name: "test-db",
databaseUri: "test-db-uri",
},
metadata: undefined, // TODO
queryName: "test-query",
queryPath: "/a/b/c/query.ql",
shouldKeepOldResultsWhileRendering: false,
parsedResultSets: {
pageNumber: 1,
pageSize: 1,
numPages: 1,
numInterpretedPages: 1,
resultSetNames: ["#select"],
resultSet: {
t: "InterpretedResultSet",
schema: {
name: "#select",
rows: 1,
columns: [
{
name: "Path",
kind: ColumnKindCode.STRING,
},
],
},
name: "#select",
interpretation,
},
},
};
await postMessage(message);
expect(
screen.getByText("'x' is assigned a value but never used."),
).toBeInTheDocument();
await postMessage({
...message,
t: "showInterpretedPage",
pageNumber: 1,
numPages: 1,
pageSize: 1,
resultSetNames: ["#select"],
queryName: "test-query",
queryPath: "/a/b/c/query.ql",
interpretation,
});
expect(
screen.getByText("'x' is assigned a value but never used."),
).toBeInTheDocument();
});
});

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { assertNever } from "../../pure/helpers-pure";
import { assertNever, getErrorMessage } from "../../pure/helpers-pure";
import {
DatabaseInfo,
Interpretation,
@@ -162,30 +162,28 @@ export class ResultsApp extends React.Component<
private updateStateWithNewResultsInfo(resultsInfo: ResultsInfo): void {
this.setState((prevState) => {
if (resultsInfo === null) {
if (prevState.isExpectingResultsUpdate) {
// Display loading message
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
};
} else {
// No results to display
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "No results to display",
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
};
}
if (resultsInfo === null && prevState.isExpectingResultsUpdate) {
// Display loading message
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
},
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,
};
}
let results: Results | null = null;
@@ -198,12 +196,7 @@ export class ResultsApp extends React.Component<
sortStates: this.getSortStates(resultsInfo),
};
} catch (e) {
let errorMessage: string;
if (e instanceof Error) {
errorMessage = e.message;
} else {
errorMessage = "Unknown error";
}
const errorMessage = getErrorMessage(e);
statusText = `Error loading results: ${errorMessage}`;
}
@@ -223,9 +216,14 @@ export class ResultsApp extends React.Component<
private getResultSets(resultsInfo: ResultsInfo): readonly ResultSet[] {
const parsedResultSets = resultsInfo.parsedResultSets;
const resultSet = parsedResultSets.resultSet;
if (!resultSet.t) {
if (
resultSet.t !== "InterpretedResultSet" &&
resultSet.t !== "RawResultSet"
) {
throw new Error(
'Missing result set type. Should be either "InterpretedResultSet" or "RawResultSet".',
`Invalid result set type. Should be either "InterpretedResultSet" or "RawResultSet", but got "${
(resultSet as { t: string }).t
}".`,
);
}
return [resultSet];

View File

@@ -1,7 +1,12 @@
import { readdirSync, readFileSync } from "fs-extra";
import { join } from "path";
import * as tmp from "tmp";
import { Logger, OutputChannelLogger, TeeLogger } from "../../../src/common";
import {
BaseLogger,
Logger,
OutputChannelLogger,
TeeLogger,
} from "../../../src/common";
jest.setTimeout(999999);
@@ -88,7 +93,7 @@ describe("OutputChannelLogger tests", function () {
function createSideLogger(
logger: Logger,
additionalLogLocation: string,
): Logger {
): BaseLogger {
return new TeeLogger(
logger,
join(tempFolders.storagePath.name, additionalLogLocation),

View File

@@ -1,6 +1,5 @@
import { commands, extensions, window } from "vscode";
import { window } from "vscode";
import { CodeQLExtensionInterface } from "../../../../src/extension";
import { readJson } from "fs-extra";
import * as path from "path";
import {
@@ -15,19 +14,18 @@ import { DbListKind } from "../../../../src/databases/db-item";
import { createDbTreeViewItemSystemDefinedList } from "../../../../src/databases/ui/db-tree-view-item";
import { createRemoteSystemDefinedListDbItem } from "../../../factories/db-item-factories";
import { DbConfigStore } from "../../../../src/databases/config/db-config-store";
import { getActivatedExtension } from "../../global.helper";
import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands";
import { AllCommands } from "../../../../src/common/commands";
jest.setTimeout(60_000);
describe("Db panel UI commands", () => {
let extension: CodeQLExtensionInterface | Record<string, never>;
let storagePath: string;
const commandManager = createVSCodeCommandManager<AllCommands>();
beforeEach(async () => {
extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
const extension = await getActivatedExtension();
storagePath =
extension.ctx.storageUri?.fsPath || extension.ctx.globalStorageUri.fsPath;
@@ -36,7 +34,7 @@ describe("Db panel UI commands", () => {
it("should add new remote db list", async () => {
// Add db list
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
await commands.executeCommand(
await commandManager.execute(
"codeQLVariantAnalysisRepositories.addNewList",
);
@@ -58,7 +56,7 @@ describe("Db panel UI commands", () => {
kind: DbListKind.Local,
} as AddListQuickPickItem);
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
await commands.executeCommand(
await commandManager.execute(
"codeQLVariantAnalysisRepositories.addNewList",
);
@@ -79,7 +77,7 @@ describe("Db panel UI commands", () => {
} as RemoteDatabaseQuickPickItem);
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1");
await commands.executeCommand(
await commandManager.execute(
"codeQLVariantAnalysisRepositories.addNewDatabase",
);
@@ -102,7 +100,7 @@ describe("Db panel UI commands", () => {
} as RemoteDatabaseQuickPickItem);
jest.spyOn(window, "showInputBox").mockResolvedValue("owner1");
await commands.executeCommand(
await commandManager.execute(
"codeQLVariantAnalysisRepositories.addNewDatabase",
);
@@ -124,7 +122,7 @@ describe("Db panel UI commands", () => {
"tooltip",
);
await commands.executeCommand(
await commandManager.execute(
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
treeViewItem,
);

View File

@@ -1,14 +1,12 @@
import {
commands,
env,
extensions,
TextDocument,
TextEditor,
Uri,
window,
workspace,
} from "vscode";
import { CodeQLExtensionInterface } from "../../../../src/extension";
import { extLogger } from "../../../../src/common";
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
import * as ghActionsApiClient from "../../../../src/variant-analysis/gh-api/gh-actions-api-client";
@@ -20,7 +18,7 @@ import { Response } from "node-fetch";
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
import { CodeQLCliServer } from "../../../../src/cli";
import { storagePath } from "../../global.helper";
import { getActivatedExtension, storagePath } from "../../global.helper";
import { VariantAnalysisResultsManager } from "../../../../src/variant-analysis/variant-analysis-results-manager";
import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis";
import * as VariantAnalysisModule from "../../../../src/variant-analysis/shared/variant-analysis";
@@ -67,11 +65,7 @@ describe("Variant Analysis Manager", () => {
scannedRepos,
});
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
const extension = await getActivatedExtension();
const cli = mockedObject<CodeQLCliServer>({});
app = new ExtensionApp(extension.ctx);
const dbManager = new DbManager(app, new DbConfigStore(app));

View File

@@ -1,6 +1,3 @@
import { commands, extensions } from "vscode";
import { CodeQLExtensionInterface } from "../../../../src/extension";
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
import { VariantAnalysisMonitor } from "../../../../src/variant-analysis/variant-analysis-monitor";
import {
@@ -23,44 +20,37 @@ import {
processUpdatedVariantAnalysis,
} from "../../../../src/variant-analysis/variant-analysis-processor";
import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis";
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
import { testCredentialsWithStub } from "../../../factories/authentication";
import { createMockApp } from "../../../__mocks__/appMock";
import { createMockCommandManager } from "../../../__mocks__/commandsMock";
jest.setTimeout(60_000);
describe("Variant Analysis Monitor", () => {
let extension: CodeQLExtensionInterface | Record<string, never>;
let mockGetVariantAnalysis: jest.SpiedFunction<
typeof ghApiClient.getVariantAnalysis
>;
let variantAnalysisMonitor: VariantAnalysisMonitor;
let shouldCancelMonitor: jest.Mock<Promise<boolean>, [number]>;
let variantAnalysis: VariantAnalysis;
let variantAnalysisManager: VariantAnalysisManager;
let mockGetDownloadResult: jest.SpiedFunction<
typeof variantAnalysisManager.autoDownloadVariantAnalysisResult
>;
const onVariantAnalysisChangeSpy = jest.fn();
const mockEecuteCommand = jest.fn();
beforeEach(async () => {
variantAnalysis = createMockVariantAnalysis({});
shouldCancelMonitor = jest.fn();
extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
variantAnalysisMonitor = new VariantAnalysisMonitor(shouldCancelMonitor);
variantAnalysisMonitor = new VariantAnalysisMonitor(
createMockApp({
commands: createMockCommandManager({
executeCommand: mockEecuteCommand,
}),
}),
shouldCancelMonitor,
);
variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy);
variantAnalysisManager = extension.variantAnalysisManager;
mockGetDownloadResult = jest
.spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult")
.mockResolvedValue(undefined);
mockGetVariantAnalysis = jest
.spyOn(ghApiClient, "getVariantAnalysis")
.mockRejectedValue(new Error("Not mocked"));
@@ -71,10 +61,7 @@ describe("Variant Analysis Monitor", () => {
it("should return early if variant analysis should be cancelled", async () => {
shouldCancelMonitor.mockResolvedValue(true);
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled();
});
@@ -88,10 +75,7 @@ describe("Variant Analysis Monitor", () => {
});
it("should mark as failed and stop monitoring", async () => {
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1);
@@ -109,7 +93,6 @@ describe("Variant Analysis Monitor", () => {
describe("when the variant analysis is in progress", () => {
let mockApiResponse: VariantAnalysisApiResponse;
let scannedRepos: ApiVariantAnalysisScannedRepository[];
let succeededRepos: ApiVariantAnalysisScannedRepository[];
describe("when there are successfully scanned repos", () => {
beforeEach(async () => {
@@ -124,28 +107,18 @@ describe("Variant Analysis Monitor", () => {
]);
mockApiResponse = createMockApiResponse("succeeded", scannedRepos);
mockGetVariantAnalysis.mockResolvedValue(mockApiResponse);
succeededRepos = scannedRepos.filter(
(r) => r.analysis_status === "succeeded",
);
});
it("should trigger a download extension command for each repo", async () => {
const succeededRepos = scannedRepos.filter(
(r) => r.analysis_status === "succeeded",
);
const commandSpy = jest
.spyOn(commands, "executeCommand")
.mockResolvedValue(undefined);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
expect(commandSpy).toBeCalledTimes(succeededRepos.length);
expect(mockEecuteCommand).toBeCalledTimes(succeededRepos.length);
succeededRepos.forEach((succeededRepo, index) => {
expect(commandSpy).toHaveBeenNthCalledWith(
expect(mockEecuteCommand).toHaveBeenNthCalledWith(
index + 1,
"codeQL.autoDownloadVariantAnalysisResult",
processScannedRepository(succeededRepo),
@@ -153,23 +126,6 @@ describe("Variant Analysis Monitor", () => {
);
});
});
it("should download all available results", async () => {
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length);
succeededRepos.forEach((succeededRepo, index) => {
expect(mockGetDownloadResult).toHaveBeenNthCalledWith(
index + 1,
processScannedRepository(succeededRepo),
processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse),
);
});
});
});
describe("when there are only in progress repos", () => {
@@ -182,25 +138,9 @@ describe("Variant Analysis Monitor", () => {
});
it("should succeed and not download any repos via a command", async () => {
const commandSpy = jest
.spyOn(commands, "executeCommand")
.mockResolvedValue(undefined);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
expect(commandSpy).not.toHaveBeenCalled();
});
it("should not try to download any repos", async () => {
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
expect(mockGetDownloadResult).not.toBeCalled();
expect(mockEecuteCommand).not.toHaveBeenCalled();
});
});
@@ -249,17 +189,10 @@ describe("Variant Analysis Monitor", () => {
});
it("should trigger a download extension command for each repo", async () => {
const commandSpy = jest
.spyOn(commands, "executeCommand")
.mockResolvedValue(undefined);
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
expect(mockGetVariantAnalysis).toBeCalledTimes(4);
expect(commandSpy).toBeCalledTimes(5);
expect(mockEecuteCommand).toBeCalledTimes(5);
});
});
@@ -271,12 +204,9 @@ describe("Variant Analysis Monitor", () => {
});
it("should not try to download any repos", async () => {
await variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
testCredentialsWithStub(),
);
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis);
expect(mockGetDownloadResult).not.toBeCalled();
expect(mockEecuteCommand).not.toBeCalled();
});
});
});

View File

@@ -1,4 +1,10 @@
---
dependencies: {}
compiled: false
lockVersion: 1.0.0
dependencies:
codeql-javascript:
version: 0.5.1
codeql/regex:
version: 0.0.9
codeql/tutorial:
version: 0.0.6
compiled: false

View File

@@ -1,14 +1,19 @@
import { join } from "path";
import { extensions, CancellationToken, Uri, window } from "vscode";
import { CancellationToken, Uri, window } from "vscode";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { CodeQLCliServer } from "../../../src/cli";
import { DatabaseManager } from "../../../src/local-databases";
import {
importArchiveDatabase,
promptImportInternetDatabase,
} from "../../../src/databaseFetcher";
import { cleanDatabases, dbLoc, DB_URL, storagePath } from "../global.helper";
import {
cleanDatabases,
dbLoc,
DB_URL,
getActivatedExtension,
storagePath,
} from "../global.helper";
import { createMockCommandManager } from "../../__mocks__/commandsMock";
jest.setTimeout(60_000);
@@ -30,18 +35,8 @@ describe("DatabaseFetcher", () => {
jest.spyOn(window, "showErrorMessage").mockResolvedValue(undefined);
jest.spyOn(window, "showInformationMessage").mockResolvedValue(undefined);
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("databaseManager" in extension) {
databaseManager = extension.databaseManager;
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
const extension = await getActivatedExtension();
databaseManager = extension.databaseManager;
await cleanDatabases(databaseManager);
});

View File

@@ -1,9 +1,8 @@
import { join } from "path";
import { extensions } from "vscode";
import { CodeQLCliServer } from "../../../src/cli";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { tryGetQueryMetadata } from "../../../src/helpers";
import { getActivatedExtension } from "../global.helper";
// up to 3 minutes per test
jest.setTimeout(3 * 60 * 1000);
@@ -14,18 +13,8 @@ describe("helpers (with CLI)", () => {
let cli: CodeQLCliServer;
beforeEach(async () => {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("cliServer" in extension) {
cli = extension.cliServer;
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
const extension = await getActivatedExtension();
cli = extension.cliServer;
});
it("should get query metadata when available", async () => {

View File

@@ -7,12 +7,11 @@ import * as messages from "../../../src/pure/legacy-messages";
import * as qsClient from "../../../src/legacy-query-server/queryserver-client";
import * as cli from "../../../src/cli";
import { CellValue } from "../../../src/pure/bqrs-cli-types";
import { extensions } from "vscode";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { describeWithCodeQL } from "../cli";
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
import { extLogger, ProgressReporter } from "../../../src/common";
import { createMockApp } from "../../__mocks__/appMock";
import { getActivatedExtension } from "../global.helper";
const baseDir = join(__dirname, "../../../test/data");
@@ -112,41 +111,30 @@ describeWithCodeQL()("using the legacy query server", () => {
let cliServer: cli.CodeQLCliServer;
beforeAll(async () => {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("cliServer" in extension) {
cliServer = extension.cliServer;
cliServer.quiet = true;
const extension = await getActivatedExtension();
cliServer = extension.cliServer;
cliServer.quiet = true;
qs = new QueryServerClient(
createMockApp({}),
{
codeQlPath:
(await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) ||
"",
debug: false,
cacheSize: 0,
numThreads: 1,
saveCache: false,
timeoutSecs: 0,
},
cliServer,
{
contextStoragePath: tmpDir.name,
logger: extLogger,
},
(task) =>
task(nullProgressReporter, new CancellationTokenSource().token),
);
await qs.startQueryServer();
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
qs = new QueryServerClient(
createMockApp({}),
{
codeQlPath:
(await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) ||
"",
debug: false,
cacheSize: 0,
numThreads: 1,
saveCache: false,
timeoutSecs: 0,
},
cliServer,
{
contextStoragePath: tmpDir.name,
logger: extLogger,
},
(task) => task(nullProgressReporter, new CancellationTokenSource().token),
);
await qs.startQueryServer();
});
for (const queryTestCase of queryTestCases) {

View File

@@ -5,15 +5,19 @@ import * as messages from "../../../src/pure/new-messages";
import * as qsClient from "../../../src/query-server/queryserver-client";
import * as cli from "../../../src/cli";
import { CellValue } from "../../../src/pure/bqrs-cli-types";
import { extensions, Uri } from "vscode";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { Uri } from "vscode";
import { describeWithCodeQL } from "../cli";
import { QueryServerClient } from "../../../src/query-server/queryserver-client";
import { extLogger, ProgressReporter } from "../../../src/common";
import { QueryResultType } from "../../../src/pure/new-messages";
import { cleanDatabases, dbLoc, storagePath } from "../global.helper";
import {
cleanDatabases,
dbLoc,
getActivatedExtension,
storagePath,
} from "../global.helper";
import { importArchiveDatabase } from "../../../src/databaseFetcher";
import { createMockCommandManager } from "../../__mocks__/commandsMock";
import { createMockApp } from "../../__mocks__/appMock";
const baseDir = join(__dirname, "../../../test/data");
@@ -110,63 +114,54 @@ describeWithCodeQL()("using the new query server", () => {
let supportNewQueryServer = true;
beforeAll(async () => {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("cliServer" in extension && "databaseManager" in extension) {
cliServer = extension.cliServer;
const app = createMockApp({});
const extension = await getActivatedExtension();
cliServer = extension.cliServer;
cliServer.quiet = true;
if (!(await cliServer.cliConstraints.supportsNewQueryServerForTests())) {
supportNewQueryServer = false;
}
qs = new QueryServerClient(
{
codeQlPath:
(await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) ||
"",
debug: false,
cacheSize: 0,
numThreads: 1,
saveCache: false,
timeoutSecs: 0,
},
cliServer,
{
contextStoragePath: tmpDir.name,
logger: extLogger,
},
(task) =>
task(nullProgressReporter, new CancellationTokenSource().token),
);
await qs.startQueryServer();
// Unlike the old query sevre the new one wants a database and the empty direcrtory is not valid.
// Add a database, but make sure the database manager is empty first
await cleanDatabases(extension.databaseManager);
const uri = Uri.file(dbLoc);
const maybeDbItem = await importArchiveDatabase(
createMockCommandManager(),
uri.toString(true),
extension.databaseManager,
storagePath,
() => {
/**ignore progress */
},
token,
);
if (!maybeDbItem) {
throw new Error("Could not import database");
}
db = maybeDbItem.databaseUri.fsPath;
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
cliServer.quiet = true;
if (!(await cliServer.cliConstraints.supportsNewQueryServerForTests())) {
supportNewQueryServer = false;
}
qs = new QueryServerClient(
app,
{
codeQlPath:
(await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) ||
"",
debug: false,
cacheSize: 0,
numThreads: 1,
saveCache: false,
timeoutSecs: 0,
},
cliServer,
{
contextStoragePath: tmpDir.name,
logger: extLogger,
},
(task) => task(nullProgressReporter, new CancellationTokenSource().token),
);
await qs.startQueryServer();
// Unlike the old query sevre the new one wants a database and the empty direcrtory is not valid.
// Add a database, but make sure the database manager is empty first
await cleanDatabases(extension.databaseManager);
const uri = Uri.file(dbLoc);
const maybeDbItem = await importArchiveDatabase(
app.commands,
uri.toString(true),
extension.databaseManager,
storagePath,
() => {
/**ignore progress */
},
token,
);
if (!maybeDbItem) {
throw new Error("Could not import database");
}
db = maybeDbItem.databaseUri.fsPath;
});
for (const queryTestCase of queryTestCases) {

View File

@@ -1,8 +1,7 @@
import { extensions, window } from "vscode";
import { window } from "vscode";
import { join } from "path";
import { CodeQLCliServer } from "../../../src/cli";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { getErrorMessage } from "../../../src/pure/helpers-pure";
import * as helpers from "../../../src/helpers";
@@ -11,6 +10,7 @@ import {
handleInstallPackDependencies,
} from "../../../src/packaging";
import { mockedQuickPickItem } from "../utils/mocking.helpers";
import { getActivatedExtension } from "../global.helper";
// up to 3 minutes per test
jest.setTimeout(3 * 60 * 1000);
@@ -41,18 +41,8 @@ describe("Packaging commands", () => {
.spyOn(helpers, "showAndLogInformationMessage")
.mockResolvedValue(undefined);
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("cliServer" in extension) {
cli = extension.cliServer;
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
const extension = await getActivatedExtension();
cli = extension.cliServer;
});
it("should download all core query packs", async () => {

View File

@@ -1,10 +1,4 @@
import {
CancellationToken,
commands,
ExtensionContext,
extensions,
Uri,
} from "vscode";
import { CancellationToken, ExtensionContext, Uri } from "vscode";
import { join, dirname } from "path";
import {
pathExistsSync,
@@ -16,17 +10,22 @@ import {
import { load, dump } from "js-yaml";
import { DatabaseItem, DatabaseManager } from "../../../src/local-databases";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { cleanDatabases, dbLoc, storagePath } from "../global.helper";
import {
cleanDatabases,
dbLoc,
getActivatedExtension,
storagePath,
} from "../global.helper";
import { importArchiveDatabase } from "../../../src/databaseFetcher";
import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli";
import { describeWithCodeQL } from "../cli";
import { tmpDir } from "../../../src/helpers";
import { createInitialQueryInfo } from "../../../src/run-queries-shared";
import { QueryRunner } from "../../../src/queryRunner";
import { CompletedQueryInfo } from "../../../src/query-results";
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
import { createMockCommandManager } from "../../__mocks__/commandsMock";
import { LocalQueries } from "../../../src/local-queries";
import { QueryResultType } from "../../../src/pure/new-messages";
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
import { AllCommands, QueryServerCommands } from "../../../src/common/commands";
jest.setTimeout(20_000);
@@ -38,9 +37,13 @@ describeWithCodeQL()("Queries", () => {
let databaseManager: DatabaseManager;
let cli: CodeQLCliServer;
let qs: QueryRunner;
let localQueries: LocalQueries;
const progress = jest.fn();
let token: CancellationToken;
let ctx: ExtensionContext;
const appCommandManager = createVSCodeCommandManager<AllCommands>();
const queryServerCommandManager =
createVSCodeCommandManager<QueryServerCommands>();
let qlpackFile: string;
let qlpackLockFile: string;
@@ -48,32 +51,27 @@ describeWithCodeQL()("Queries", () => {
let qlFile: string;
beforeEach(async () => {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("databaseManager" in extension) {
databaseManager = extension.databaseManager;
cli = extension.cliServer;
qs = extension.qs;
cli.quiet = true;
ctx = extension.ctx;
qlpackFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.yml`;
qlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/codeql-pack.lock.yml`;
oldQlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.lock.yml`;
qlFile = `${ctx.storageUri?.fsPath}/quick-queries/quick-query.ql`;
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
const extension = await getActivatedExtension();
databaseManager = extension.databaseManager;
cli = extension.cliServer;
qs = extension.qs;
localQueries = extension.localQueries;
cli.quiet = true;
ctx = extension.ctx;
qlpackFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.yml`;
qlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/codeql-pack.lock.yml`;
oldQlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.lock.yml`;
qlFile = `${ctx.storageUri?.fsPath}/quick-queries/quick-query.ql`;
// Ensure we are starting from a clean slate.
safeDel(qlFile);
safeDel(qlpackFile);
token = {} as CancellationToken;
token = {
onCancellationRequested: (_) => {
void _;
},
} as CancellationToken;
// Add a database, but make sure the database manager is empty first
await cleanDatabases(databaseManager);
@@ -143,22 +141,21 @@ describeWithCodeQL()("Queries", () => {
}
async function runQueryWithExtensions() {
const result = new CompletedQueryInfo(
await qs.compileAndRunQueryAgainstDatabase(
dbItem,
await mockInitialQueryInfo(queryUsingExtensionPath),
join(tmpDir.name, "mock-storage-path"),
progress,
token,
),
const result = await localQueries.compileAndRunQueryInternal(
false,
Uri.file(queryUsingExtensionPath),
progress,
token,
dbItem,
undefined,
);
// Check that query was successful
expect(result.successful).toBe(true);
expect(result.resultType).toBe(QueryResultType.SUCCESS);
// Load query results
const chunk = await qs.cliServer.bqrsDecode(
result.getResultsPath(SELECT_QUERY_NAME, true),
result.outputDir.bqrsPath,
SELECT_QUERY_NAME,
{
// there should only be one result
@@ -174,35 +171,37 @@ describeWithCodeQL()("Queries", () => {
it("should run a query", async () => {
const queryPath = join(__dirname, "data", "simple-query.ql");
const result = qs.compileAndRunQueryAgainstDatabase(
dbItem,
await mockInitialQueryInfo(queryPath),
join(tmpDir.name, "mock-storage-path"),
const result = await localQueries.compileAndRunQueryInternal(
false,
Uri.file(queryPath),
progress,
token,
dbItem,
undefined,
);
// just check that the query was successful
expect((await result).successful).toBe(true);
expect(result.resultType).toBe(QueryResultType.SUCCESS);
});
// Asserts a fix for bug https://github.com/github/vscode-codeql/issues/733
it("should restart the database and run a query", async () => {
await commands.executeCommand("codeQL.restartQueryServer");
await appCommandManager.execute("codeQL.restartQueryServer");
const queryPath = join(__dirname, "data", "simple-query.ql");
const result = await qs.compileAndRunQueryAgainstDatabase(
dbItem,
await mockInitialQueryInfo(queryPath),
join(tmpDir.name, "mock-storage-path"),
const result = await localQueries.compileAndRunQueryInternal(
false,
Uri.file(queryPath),
progress,
token,
dbItem,
undefined,
);
expect(result.successful).toBe(true);
expect(result.resultType).toBe(QueryResultType.SUCCESS);
});
it("should create a quick query", async () => {
await commands.executeCommand("codeQL.quickQuery");
await queryServerCommandManager.execute("codeQL.quickQuery");
// should have created the quick query file and query pack file
expect(pathExistsSync(qlFile)).toBe(true);
@@ -235,7 +234,7 @@ describeWithCodeQL()("Queries", () => {
}),
);
writeFileSync(qlFile, "xxx");
await commands.executeCommand("codeQL.quickQuery");
await queryServerCommandManager.execute("codeQL.quickQuery");
// should not have created the quick query file because database schema hasn't changed
expect(readFileSync(qlFile, "utf8")).toBe("xxx");
@@ -248,15 +247,4 @@ describeWithCodeQL()("Queries", () => {
// ignore
}
}
async function mockInitialQueryInfo(queryPath: string) {
return await createInitialQueryInfo(
Uri.file(queryPath),
{
name: dbItem.name,
databaseUri: dbItem.databaseUri.toString(),
},
false,
);
}
});

View File

@@ -1,9 +1,8 @@
import { authentication, extensions, Uri } from "vscode";
import { authentication, Uri } from "vscode";
import { join } from "path";
import { SemVer } from "semver";
import { CodeQLCliServer, QueryInfoByLanguage } from "../../../src/cli";
import { CodeQLExtensionInterface } from "../../../src/extension";
import { itWithCodeQL } from "../cli";
import {
getOnDiskWorkspaceFolders,
@@ -13,6 +12,7 @@ import {
import { resolveQueries } from "../../../src/contextual/queryResolver";
import { KeyType } from "../../../src/contextual/keyType";
import { faker } from "@faker-js/faker";
import { getActivatedExtension } from "../global.helper";
jest.setTimeout(60_000);
@@ -24,19 +24,9 @@ describe("Use cli", () => {
let supportedLanguages: string[];
beforeEach(async () => {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
if ("cliServer" in extension) {
cli = extension.cliServer;
supportedLanguages = await cli.getSupportedLanguages();
} else {
throw new Error(
"Extension not initialized. Make sure cli is downloaded and installed properly.",
);
}
const extension = await getActivatedExtension();
cli = extension.cliServer;
supportedLanguages = await cli.getSupportedLanguages();
});
if (process.env.CLI_VERSION && process.env.CLI_VERSION !== "nightly") {

View File

@@ -1,7 +1,9 @@
import { commands, Selection, window, workspace } from "vscode";
import { Selection, window, workspace } from "vscode";
import { join, basename } from "path";
import { tmpDir } from "../../../src/helpers";
import { readFile, writeFile, ensureDir, copy } from "fs-extra";
import { createVSCodeCommandManager } from "../../../src/common/vscode/commands";
import { AllCommands } from "../../../src/common/commands";
jest.setTimeout(20_000);
@@ -9,6 +11,8 @@ jest.setTimeout(20_000);
* Integration tests for queries
*/
describe("SourceMap", () => {
const commandManager = createVSCodeCommandManager<AllCommands>();
it("should jump to QL code", async () => {
const root = workspace.workspaceFolders![0].uri.fsPath;
const srcFiles = {
@@ -32,7 +36,7 @@ describe("SourceMap", () => {
expect(summaryDocument.languageId).toBe("ql-summary");
const summaryEditor = await window.showTextDocument(summaryDocument);
summaryEditor.selection = new Selection(356, 10, 356, 10);
await commands.executeCommand("codeQL.gotoQL");
await commandManager.execute("codeQL.gotoQL");
const newEditor = window.activeTextEditor;
expect(newEditor).toBeDefined();

View File

@@ -1,11 +1,4 @@
import {
CancellationTokenSource,
commands,
extensions,
Uri,
window,
} from "vscode";
import { CodeQLExtensionInterface } from "../../../../src/extension";
import { CancellationTokenSource, commands, Uri, window } from "vscode";
import { extLogger } from "../../../../src/common";
import { setRemoteControllerRepo } from "../../../../src/config";
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
@@ -15,6 +8,7 @@ import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant
import { CliVersionConstraint, CodeQLCliServer } from "../../../../src/cli";
import {
fixWorkspaceReferences,
getActivatedExtension,
restoreWorkspaceReferences,
storagePath,
} from "../../global.helper";
@@ -48,11 +42,7 @@ describe("Variant Analysis Manager", () => {
cancellationTokenSource = new CancellationTokenSource();
const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
const extension = await getActivatedExtension();
cli = extension.cliServer;
const app = new ExtensionApp(extension.ctx);
const dbManager = new DbManager(app, new DbConfigStore(app));

View File

@@ -3,16 +3,17 @@ import { resolve } from "path";
import {
authentication,
commands,
extensions,
TextDocument,
window,
workspace,
} from "vscode";
import { CodeQLExtensionInterface } from "../../../../src/extension";
import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server";
import { mockedQuickPickItem } from "../../utils/mocking.helpers";
import { setRemoteControllerRepo } from "../../../../src/config";
import { getActivatedExtension } from "../../global.helper";
import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands";
import { AllCommands } from "../../../../src/common/commands";
jest.setTimeout(30_000);
@@ -30,6 +31,7 @@ async function showQlDocument(name: string): Promise<TextDocument> {
}
describe("Variant Analysis Submission Integration", () => {
const commandManager = createVSCodeCommandManager<AllCommands>();
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
let executeCommandSpy: jest.SpiedFunction<typeof commands.executeCommand>;
let showErrorMessageSpy: jest.SpiedFunction<typeof window.showErrorMessage>;
@@ -55,11 +57,7 @@ describe("Variant Analysis Submission Integration", () => {
.spyOn(window, "showErrorMessage")
.mockResolvedValue(undefined);
await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
"GitHub.vscode-codeql",
)!
.activate();
await getActivatedExtension();
});
describe("Successful scenario", () => {
@@ -73,7 +71,7 @@ describe("Variant Analysis Submission Integration", () => {
// Select target language for your query
quickPickSpy.mockResolvedValueOnce(mockedQuickPickItem("javascript"));
await commands.executeCommand("codeQL.runVariantAnalysis");
await commandManager.execute("codeQL.runVariantAnalysis");
expect(executeCommandSpy).toHaveBeenCalledWith(
"codeQL.openVariantAnalysisView",
@@ -90,7 +88,7 @@ describe("Variant Analysis Submission Integration", () => {
it("shows the error message", async () => {
await showQlDocument("query.ql");
await commands.executeCommand("codeQL.runVariantAnalysis");
await commandManager.execute("codeQL.runVariantAnalysis");
expect(showErrorMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(
@@ -112,7 +110,7 @@ describe("Variant Analysis Submission Integration", () => {
// Select target language for your query
quickPickSpy.mockResolvedValueOnce(mockedQuickPickItem("javascript"));
await commands.executeCommand("codeQL.runVariantAnalysis");
await commandManager.execute("codeQL.runVariantAnalysis");
expect(showErrorMessageSpy).toHaveBeenCalledWith(
expect.stringContaining(

View File

@@ -1,10 +1,12 @@
import { join } from "path";
import { load, dump } from "js-yaml";
import { realpathSync, readFileSync, writeFileSync } from "fs-extra";
import { commands } from "vscode";
import { CancellationToken, extensions } from "vscode";
import { DatabaseManager } from "../../src/local-databases";
import { CodeQLCliServer } from "../../src/cli";
import { removeWorkspaceRefs } from "../../src/variant-analysis/run-remote-query";
import { CodeQLExtensionInterface } from "../../src/extension";
import { ProgressCallback } from "../../src/progress";
// This file contains helpers shared between tests that work with an activated extension.
@@ -23,10 +25,23 @@ export function setStoragePath(path: string) {
storagePath = path;
}
export async function cleanDatabases(databaseManager: DatabaseManager) {
for (const item of databaseManager.databaseItems) {
await commands.executeCommand("codeQLDatabases.removeDatabase", item);
export async function getActivatedExtension(): Promise<CodeQLExtensionInterface> {
const extension = await extensions
.getExtension<CodeQLExtensionInterface | undefined>("GitHub.vscode-codeql")
?.activate();
if (extension === undefined) {
throw new Error(
"Unable to active CodeQL extension. Make sure cli is downloaded and installed properly.",
);
}
return extension;
}
export async function cleanDatabases(databaseManager: DatabaseManager) {
await databaseManager.removeAllDatabases(
{} as ProgressCallback,
{} as CancellationToken,
);
}
/**

View File

@@ -1,13 +1,19 @@
import { CUSTOM_CODEQL_PATH_SETTING } from "../../src/config";
import { ConfigurationTarget, env, extensions } from "vscode";
import { ConfigurationTarget, env } from "vscode";
import { beforeEachAction as testConfigBeforeEachAction } from "./test-config";
import * as tmp from "tmp";
import { realpathSync } from "fs-extra";
import { setStoragePath, storagePath } from "./global.helper";
import {
getActivatedExtension,
setStoragePath,
storagePath,
} from "./global.helper";
jest.retryTimes(3, {
logErrorsBeforeRetry: true,
});
if (process.env.CI) {
jest.retryTimes(3, {
logErrorsBeforeRetry: true,
});
}
// create an extension storage location
let removeStorage: tmp.DirResult["removeCallback"] | undefined;
@@ -33,7 +39,7 @@ export async function beforeAllAction() {
removeStorage = dir.removeCallback;
// Activate the extension
await extensions.getExtension("GitHub.vscode-codeql")?.activate();
await getActivatedExtension();
}
export async function beforeEachAction() {

View File

@@ -1,9 +1,11 @@
import { env } from "vscode";
import { beforeEachAction } from "./test-config";
jest.retryTimes(3, {
logErrorsBeforeRetry: true,
});
if (process.env.CI) {
jest.retryTimes(3, {
logErrorsBeforeRetry: true,
});
}
beforeEach(async () => {
jest.spyOn(env, "openExternal").mockResolvedValue(false);

View File

@@ -5,6 +5,7 @@ import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode";
import {
DatabaseContents,
DatabaseContentsWithDbScheme,
DatabaseEventKind,
DatabaseItemImpl,
DatabaseManager,
@@ -687,7 +688,7 @@ describe("local databases", () => {
resolveDatabaseContentsSpy = jest
.spyOn(DatabaseResolver, "resolveDatabaseContents")
.mockResolvedValue({} as DatabaseContents);
.mockResolvedValue({} as DatabaseContentsWithDbScheme);
addDatabaseSourceArchiveFolderSpy = jest.spyOn(
databaseManager,

View File

@@ -3,8 +3,9 @@ import { readFileSync } from "fs-extra";
import AstBuilder from "../../../../src/contextual/astBuilder";
import { CodeQLCliServer } from "../../../../src/cli";
import { Uri } from "vscode";
import { QueryWithResults } from "../../../../src/run-queries-shared";
import { QueryOutputDir } from "../../../../src/run-queries-shared";
import { mockDatabaseItem, mockedObject } from "../../utils/mocking.helpers";
import path from "path";
/**
*
@@ -52,11 +53,12 @@ describe("AstBuilder", () => {
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
const bqrsPath = path.normalize("/a/b/c/results.bqrs");
const options = { entities: ["id", "url", "string"] };
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "nodes", options);
expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "edges", options);
expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "nodes", options);
expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "edges", options);
expect(mockCli.bqrsDecode).toBeCalledWith(
"/a/b/c",
bqrsPath,
"graphProperties",
options,
);
@@ -137,13 +139,7 @@ describe("AstBuilder", () => {
function createAstBuilder() {
return new AstBuilder(
{
query: {
resultsPaths: {
resultsPath: "/a/b/c",
},
},
} as QueryWithResults,
new QueryOutputDir("/a/b/c"),
mockCli,
mockDatabaseItem({
resolveSourceFile: undefined,

View File

@@ -619,7 +619,7 @@ describe("prepareCodeTour", () => {
expect(executeCommand).toHaveBeenCalledWith(
"vscode.openFolder",
expect.objectContaining({
path: Uri.parse(tutorialWorkspacePath).fsPath,
path: expect.stringMatching(/tutorial.code-workspace$/),
}),
);
});

View File

@@ -1,6 +1,6 @@
import {
deserializeQueryHistory,
serializeQueryHistory,
readQueryHistoryFromFile,
writeQueryHistoryToFile,
} from "../../../../../src/query-history/store/query-history-store";
import { join } from "path";
import { writeFileSync, mkdirpSync, writeFile } from "fs-extra";
@@ -19,7 +19,7 @@ import { QueryHistoryInfo } from "../../../../../src/query-history/query-history
import { createMockVariantAnalysisHistoryItem } from "../../../../factories/query-history/variant-analysis-history-item";
import { nanoid } from "nanoid";
describe("serialize and deserialize", () => {
describe("write and read", () => {
let infoSuccessRaw: LocalQueryInfo;
let infoSuccessInterpreted: LocalQueryInfo;
let infoEarlyFailure: LocalQueryInfo;
@@ -93,12 +93,12 @@ describe("serialize and deserialize", () => {
];
});
it("should serialize and deserialize query history", async () => {
it("should write and read query history", async () => {
const allHistoryPath = join(tmpDir.name, "workspace-query-history.json");
// serialize and deserialize
await serializeQueryHistory(allHistory, allHistoryPath);
const allHistoryActual = await deserializeQueryHistory(allHistoryPath);
// write and read
await writeQueryHistoryToFile(allHistory, allHistoryPath);
const allHistoryActual = await readQueryHistoryFromFile(allHistoryPath);
// the dispose methods will be different. Ignore them.
allHistoryActual.forEach((info) => {
@@ -106,7 +106,7 @@ describe("serialize and deserialize", () => {
const completedQuery = info.completedQuery;
(completedQuery as any).dispose = undefined;
// these fields should be missing on the deserialized value
// these fields should be missing on the read value
// but they are undefined on the original value
if (!("logFileLocation" in completedQuery)) {
(completedQuery as any).logFileLocation = undefined;
@@ -181,7 +181,7 @@ describe("serialize and deserialize", () => {
"utf8",
);
const actual = await deserializeQueryHistory(path);
const actual = await readQueryHistoryFromFile(path);
expect(actual.length).toEqual(expectedHistory.length);
});
@@ -196,7 +196,7 @@ describe("serialize and deserialize", () => {
"utf8",
);
const allHistoryActual = await deserializeQueryHistory(badPath);
const allHistoryActual = await readQueryHistoryFromFile(badPath);
// version number is invalid. Should return an empty array.
expect(allHistoryActual).toEqual([]);
});

View File

@@ -13,7 +13,10 @@ import { tmpDir } from "../../../src/helpers";
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
import { CodeQLCliServer } from "../../../src/cli";
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
import { QueryInProgress } from "../../../src/legacy-query-server/run-queries";
import {
QueryInProgress,
compileQuery as compileQueryLegacy,
} from "../../../src/legacy-query-server/run-queries";
import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner";
import { DatabaseItem } from "../../../src/local-databases";
import { DeepPartial, mockedObject } from "../utils/mocking.helpers";
@@ -30,7 +33,6 @@ describe("run-queries", () => {
const saveDir = "query-save-dir";
const info = createMockQueryInfo(true, saveDir);
expect(info.compiledQueryPath).toBe(join(saveDir, "compiledQuery.qlo"));
expect(info.queryEvalInfo.dilPath).toBe(join(saveDir, "results.dil"));
expect(info.queryEvalInfo.resultsPaths.resultsPath).toBe(
join(saveDir, "results.bqrs"),
@@ -185,14 +187,15 @@ describe("run-queries", () => {
queryPath: "",
};
const results = await info.compile(
const results = await compileQueryLegacy(
qs as any,
mockQlProgram,
undefined,
info.queryEvalInfo,
mockProgress as any,
mockCancel as any,
qs.logger,
);
expect(results).toEqual([{ message: "err", severity: Severity.ERROR }]);
expect(qs.sendRequest).toHaveBeenCalledTimes(1);
@@ -214,7 +217,7 @@ describe("run-queries", () => {
timeoutSecs: 5,
},
queryToCheck: mockQlProgram,
resultPath: info.compiledQueryPath,
resultPath: info.queryEvalInfo.compileQueryPath,
target: { query: {} },
},
mockCancel,

View File

@@ -1,4 +1,3 @@
import * as fs from "fs-extra";
import { Uri, WorkspaceFolder } from "vscode";
import { QLTestAdapter } from "../../../src/test-adapter";
@@ -19,8 +18,6 @@ jest.mock("fs-extra", () => {
};
});
const mockedFsExtra = jest.mocked(fs);
describe("test-adapter", () => {
let adapter: QLTestAdapter;
let fakeDatabaseManager: DatabaseManager;
@@ -131,8 +128,6 @@ describe("test-adapter", () => {
});
it("should reregister testproj databases around test run", async () => {
mockedFsExtra.access.mockResolvedValue(undefined);
currentDatabaseItem = preTestDatabaseItem;
databaseItems = [preTestDatabaseItem];
await adapter.run(["/path/to/test/dir"]);