Merge branch 'main' into koesie10/react-18
This commit is contained in:
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -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 }}
|
||||
|
||||
47
.github/workflows/release.yml
vendored
47
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
1033
extensions/ql-vscode/package-lock.json
generated
1033
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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[]> {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
120
extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx
Normal file
120
extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -619,7 +619,7 @@ describe("prepareCodeTour", () => {
|
||||
expect(executeCommand).toHaveBeenCalledWith(
|
||||
"vscode.openFolder",
|
||||
expect.objectContaining({
|
||||
path: Uri.parse(tutorialWorkspacePath).fsPath,
|
||||
path: expect.stringMatching(/tutorial.code-workspace$/),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
Reference in New Issue
Block a user