From c292f58e2033aaa26faf400b7e836d0c27877c79 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Wed, 25 Mar 2020 13:57:08 -0400 Subject: [PATCH 01/11] Factor out database qlpack inference. --- extensions/ql-vscode/src/helpers.ts | 54 +++++++++++++++++++++++++ extensions/ql-vscode/src/quick-query.ts | 42 +------------------ 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index c5bcaa8e917..4b65aeb0fdf 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -1,5 +1,9 @@ +import * as fs from 'fs-extra'; +import * as glob from 'glob-promise'; +import * as yaml from 'js-yaml'; import * as path from 'path'; import { CancellationToken, ExtensionContext, ProgressOptions, window as Window, workspace } from 'vscode'; +import { CodeQLCliServer } from './cli'; import { logger } from './logging'; import { QueryInfo } from './run-queries'; @@ -244,3 +248,53 @@ function createRateLimitedResult(): RateLimitedResult { kind: InvocationRateLimiterResultKind.RateLimited }; } + + +export type DatasetFolderInfo = { + dbscheme: string; + qlpack: string; +} + +export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise { + const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders()); + const packs: { packDir: string | undefined; packName: string }[] = + Object.entries(qlpacks).map(([packName, dirs]) => { + if (dirs.length < 1) { + logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`); + return { packName, packDir: undefined }; + } + if (dirs.length > 1) { + logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`); + } + return { + packName, + packDir: dirs[0] + } + }); + for (const { packDir, packName } of packs) { + if (packDir !== undefined) { + const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')); + if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) { + return packName; + } + } + } + throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`); +} + +export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFolder: string): Promise { + const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme')) + + if (dbschemes.length < 1) { + throw new Error(`Can't find dbscheme for current database in ${datasetFolder}`); + } + + dbschemes.sort(); + const dbscheme = dbschemes[0]; + if (dbschemes.length > 1) { + Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`); + } + + const qlpack = await getQlPackForDbscheme(cliServer, dbscheme); + return { dbscheme, qlpack }; +} diff --git a/extensions/ql-vscode/src/quick-query.ts b/extensions/ql-vscode/src/quick-query.ts index aa0b15313c9..204d19c4bb7 100644 --- a/extensions/ql-vscode/src/quick-query.ts +++ b/extensions/ql-vscode/src/quick-query.ts @@ -1,5 +1,4 @@ import * as fs from 'fs-extra'; -import * as glob from 'glob-promise'; import * as yaml from 'js-yaml'; import * as path from 'path'; import { ExtensionContext, window as Window, workspace, Uri } from 'vscode'; @@ -18,33 +17,6 @@ export function isQuickQueryPath(queryPath: string): boolean { return path.basename(queryPath) === QUICK_QUERY_QUERY_NAME; } -async function getQlPackFor(cliServer: CodeQLCliServer, dbschemePath: string): Promise { - const qlpacks = await cliServer.resolveQlpacks(helpers.getOnDiskWorkspaceFolders()); - const packs: { packDir: string | undefined; packName: string }[] = - Object.entries(qlpacks).map(([packName, dirs]) => { - if (dirs.length < 1) { - logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`); - return { packName, packDir: undefined }; - } - if (dirs.length > 1) { - logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`); - } - return { - packName, - packDir: dirs[0] - } - }); - for (const { packDir, packName } of packs) { - if (packDir !== undefined) { - const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')); - if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) { - return packName; - } - } - } - throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`); -} - /** * `getBaseText` heuristically returns an appropriate import statement * prelude based on the filename of the dbscheme file given. TODO: add @@ -128,19 +100,7 @@ export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQL } const datasetFolder = await dbItem.getDatasetFolder(cliServer); - const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme')) - - if (dbschemes.length < 1) { - throw new Error(`Can't find dbscheme for current database in ${datasetFolder}`); - } - - dbschemes.sort(); - const dbscheme = dbschemes[0]; - if (dbschemes.length > 1) { - Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`); - } - - const qlpack = await getQlPackFor(cliServer, dbscheme); + const { qlpack, dbscheme } = await helpers.resolveDatasetFolder(cliServer, datasetFolder); const quickQueryQlpackYaml: any = { name: "quick-query", version: "1.0.0", From 881c9095405bd32b674c6eed405ab4b560a7bf98 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 18 Feb 2020 09:53:46 -0500 Subject: [PATCH 02/11] Make rudimentary jump-to-definition work --- extensions/ql-vscode/src/cli.ts | 8 +- extensions/ql-vscode/src/databases.ts | 5 + extensions/ql-vscode/src/definitions.ts | 221 ++++++++++++++++++++++++ extensions/ql-vscode/src/extension.ts | 29 ++-- 4 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 extensions/ql-vscode/src/definitions.ts diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index f5d0bd4ded9..b7d474a88e2 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -10,10 +10,10 @@ import * as tk from 'tree-kill'; import * as util from 'util'; import { CancellationToken, Disposable } from 'vscode'; import { BQRSInfo, DecodedBqrsChunk } from "./bqrs-cli-types"; -import { DistributionProvider } from './distribution'; -import { assertNever } from './helpers-pure'; -import { QueryMetadata, SortDirection } from './interface-types'; -import { Logger, ProgressReporter } from './logging'; +import { DistributionProvider } from "./distribution"; +import { assertNever } from "./helpers-pure"; +import { QueryMetadata, SortDirection } from "./interface-types"; +import { Logger, ProgressReporter } from "./logging"; /** * The version of the SARIF format that we are using. diff --git a/extensions/ql-vscode/src/databases.ts b/extensions/ql-vscode/src/databases.ts index 2eaf9f3ee50..071f703684e 100644 --- a/extensions/ql-vscode/src/databases.ts +++ b/extensions/ql-vscode/src/databases.ts @@ -611,6 +611,11 @@ export class DatabaseManager extends DisposableObject { return this._databaseItems.find(item => item.databaseUri.toString(true) === uriString); } + public findDatabaseItemBySourceArchive(uri: vscode.Uri): DatabaseItem | undefined { + const uriString = uri.toString(true); + return this._databaseItems.find(item => item.sourceArchive && item.sourceArchive.toString(true) === uriString); + } + private async addDatabaseItem(item: DatabaseItemImpl) { this._databaseItems.push(item); this.updatePersistedDatabaseList(); diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts new file mode 100644 index 00000000000..1e4c764cf28 --- /dev/null +++ b/extensions/ql-vscode/src/definitions.ts @@ -0,0 +1,221 @@ +import * as vscode from "vscode" +import * as messages from "./messages" +import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries"; +import { QueryServerClient } from "./queryserver-client"; +import { DatabaseManager, DatabaseItem } from "./databases"; +import { CodeQLCliServer } from "./cli"; +import { getResultSetSchema, UrlValue, LineColumnLocation, EntityValue } from "./bqrs-cli-types"; +import { decodeSourceArchiveUri, zipArchiveScheme } from "./archive-filesystem-provider"; + +const TEMPLATE_NAME = "selectedSourceFile"; +const SELECT_QUERY_NAME = "#select"; + +enum KeyType { + DefinitionQuery, ReferenceQuery +} + +async function resolveQueries(keyType: KeyType): Promise { + switch (keyType) { + case KeyType.DefinitionQuery: return ["/home/jcreed/semmle/code/ql/cpp/ql/src/localDefinitions.ql"] + case KeyType.ReferenceQuery: return ["/home/jcreed/semmle/code/ql/cpp/ql/src/localReferences.ql"] + } +} + +export function createDefinitionsHandler(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): vscode.DefinitionProvider { + let fileCache = new CachedOperation(async (uriString: string) => { + const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); + const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); + + const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); + if (db) { + const links: vscode.DefinitionLink[] = [] + for (const query of await resolveQueries(KeyType.DefinitionQuery)) { + const templates: messages.TemplateDefinitions = { + [TEMPLATE_NAME]: { + values: { + tuples: [[{ + stringValue: uri.pathWithinSourceArchive + }]] + } + } + }; + const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates); + if (results.result.resultType == messages.QueryResultType.SUCCESS) { + links.push(...await getLinksFromResults(results, cli, db, (src, _dest) => src === uriString)); + } + } + return links; + } else { + return []; + } + }); + + return { + async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { + const fileLinks = await fileCache.get(document.uri.toString()); + let locLinks: vscode.LocationLink[] = []; + for (const link of fileLinks) { + if (link.originSelectionRange!.contains(position)) { + locLinks.push(link); + } + } + return locLinks; + } + + }; +} + +interface FullLocationLink extends vscode.LocationLink { + originUri: vscode.Uri; +} + +export function createReferencesHander(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): vscode.ReferenceProvider { + let fileCache = new CachedOperation(async (uriString: string) => { + const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); + const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); + + const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); + if (db) { + const links: FullLocationLink[] = [] + for (const query of await resolveQueries(KeyType.ReferenceQuery)) { + const templates: messages.TemplateDefinitions = { + [TEMPLATE_NAME]: { + values: { + tuples: [[{ + stringValue: uri.pathWithinSourceArchive + }]] + } + } + }; + const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates); + if (results.result.resultType == messages.QueryResultType.SUCCESS) { + links.push(...await getLinksFromResults(results, cli, db, (_src, dest) => dest === uriString)); + } + } + return links; + } else { + return []; + } + }) + + return { + async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { + const fileLinks = await fileCache.get(document.uri.toString()); + let locLinks: vscode.Location[] = []; + for (const link of fileLinks) { + if (link.targetRange!.contains(position)) { + locLinks.push({ range: link.originSelectionRange!, uri: link.originUri }); + } + } + return locLinks; + } + + }; +} + +interface FileRange { + file: vscode.Uri, + range: vscode.Range +} + +async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServer, db: DatabaseItem, filter: (srcFile: string, destFile: string) => boolean): Promise { + const localLinks: FullLocationLink[] = []; + const bqrsPath = results.query.resultsPaths.resultsPath; + const info = await cli.bqrsInfo(bqrsPath); + const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info); + if (selectInfo && selectInfo.columns.length == 3 + && selectInfo.columns[0].kind == "e" + && selectInfo.columns[1].kind == "e" + && selectInfo.columns[2].kind == "s") { + // TODO: Page this + const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME); + for (const tuple of allTuples.tuples) { + const src = tuple[0] as EntityValue; + const dest = tuple[1] as EntityValue; + const srcFile = src.url && fileRangeFromURI(src.url, db); + const destFile = dest.url && fileRangeFromURI(dest.url, db); + if (srcFile && destFile && filter(srcFile.file.toString(), destFile.file.toString())) { + localLinks.push({ targetRange: destFile.range, targetUri: destFile.file, originSelectionRange: srcFile.range, originUri: srcFile.file }); + } + } + } + return localLinks; +} + +function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): FileRange | undefined { + if (typeof uri === "string") { + return undefined; + } else if ('startOffset' in uri) { + return undefined; + } else { + const loc = uri as LineColumnLocation; + const range = new vscode.Range(Math.max(0, loc.startLine - 1), + Math.max(0, loc.startColumn - 1), + Math.max(0, loc.endLine - 1), + Math.max(0, loc.endColumn)); + try { + const parsed = vscode.Uri.parse(uri.uri, true); + if (parsed.scheme === "file") { + return { file: db.resolveSourceFile(parsed.fsPath), range }; + } + return undefined; + } catch (e) { + return undefined; + } + } +} + +const CACHE_SIZE = 100; +class CachedOperation { + private readonly operation: (t: string) => Promise; + private readonly cached: Map; + private readonly lru: string[]; + private readonly inProgressCallbacks: Map void, (reason?: any) => void][]>; + + constructor(operation: (t: string) => Promise) { + this.operation = operation; + this.lru = []; + this.inProgressCallbacks = new Map void, (reason?: any) => void][]>(); + this.cached = new Map(); + } + + async get(t: string): Promise { + // Try and retrieve from the cache + const fromCache = this.cached.get(t); + if (fromCache !== undefined) { + // Move to end of lru list + this.lru.push(this.lru.splice(this.lru.findIndex(v => v === t), 1)[0]) + return fromCache; + } + // Otherwise check if in progress + const inProgressCallback = this.inProgressCallbacks.get(t); + if (inProgressCallback !== undefined) { + // If so wait for it to resolve + return await new Promise((resolve, reject) => { + inProgressCallback.push([resolve, reject]); + }); + } + + // Otherwise compute the new value, but leave a callback to allow sharing work + const callbacks: [(u: U) => void, (reason?: any) => void][] = []; + this.inProgressCallbacks.set(t, callbacks); + try { + const result = await this.operation(t); + callbacks.forEach(f => f[0](result)); + this.inProgressCallbacks.delete(t); + if (this.lru.length > CACHE_SIZE) { + const toRemove = this.lru.shift()!; + this.cached.delete(toRemove); + } + this.lru.push(t); + this.cached.set(t, result); + return result; + } catch (e) { + // Rethrow error on all callbacks + callbacks.forEach(f => f[1](e)); + throw e; + } finally { + this.inProgressCallbacks.delete(t); + } + } +} diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 156fbd5b857..1ad3dfe0f06 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -1,25 +1,23 @@ -import { commands, Disposable, ExtensionContext, extensions, ProgressLocation, ProgressOptions, window as Window, Uri } from 'vscode'; +import { commands, Disposable, ExtensionContext, extensions, languages, ProgressLocation, ProgressOptions, Uri, window as Window } from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; +import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api'; import * as archiveFilesystemProvider from './archive-filesystem-provider'; -import { DistributionConfigListener, QueryServerConfigListener, QueryHistoryConfigListener } from './config'; +import { CodeQLCliServer } from './cli'; +import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener } from './config'; import { DatabaseManager } from './databases'; import { DatabaseUI } from './databases-ui'; -import { - DistributionUpdateCheckResultKind, DistributionManager, FindDistributionResult, FindDistributionResultKind, GithubApiError, - DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, GithubRateLimitedError -} from './distribution'; +import { createDefinitionsHandler, createReferencesHander } from './definitions'; +import { DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution'; import * as helpers from './helpers'; +import { assertNever } from './helpers-pure'; import { spawnIdeServer } from './ide-server'; import { InterfaceManager, WebviewReveal } from './interface'; import { ideServerLogger, logger, queryServerLogger } from './logging'; -import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries'; -import { CompletedQuery } from './query-results'; import { QueryHistoryManager } from './query-history'; +import { CompletedQuery } from './query-results'; import * as qsClient from './queryserver-client'; -import { CodeQLCliServer } from './cli'; -import { assertNever } from './helpers-pure'; import { displayQuickQuery } from './quick-query'; -import { TestHub, testExplorerExtensionId } from 'vscode-test-adapter-api'; +import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries'; import { QLTestAdapterFactory } from './test-adapter'; import { TestUIService } from './test-ui'; @@ -337,6 +335,15 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu })); ctx.subscriptions.push(client.start()); + + languages.registerDefinitionProvider( + { scheme: archiveFilesystemProvider.zipArchiveScheme }, + createDefinitionsHandler(cliServer, qs, dbm) + ); + languages.registerReferenceProvider( + { scheme: archiveFilesystemProvider.zipArchiveScheme }, + createReferencesHander(cliServer, qs, dbm) + ); } function initializeLogging(ctx: ExtensionContext): void { From a6cd08fb0b54aeca5aeab300fb8d410ca9c6fba0 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Wed, 25 Mar 2020 13:58:04 -0400 Subject: [PATCH 03/11] Add wrapper for 'resolve queries' cli command --- extensions/ql-vscode/src/cli.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index b7d474a88e2..1870aa004c1 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -614,6 +614,26 @@ export class CodeQLCliServer implements Disposable { "Resolving qlpack information", ); } + + /** + * Gets information about queries in a query suite. + * @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`. + * @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined, + * the default CLI search path is used. + * @returns A list of query files found + */ + resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise { + const args = ['--additional-packs', additionalPacks.join(path.delimiter)]; + if (searchPath !== undefined) { + args.push('--search-path', path.join(...searchPath)); + } + args.push(suite); + return this.runJsonCodeQlCliCommand( + ['resolve', 'queries'], + args, + "Resolving queries", + ); + } } /** From 5592a77963c3d7ce7ec2bbe55b2b1b0788aefc43 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Wed, 25 Mar 2020 14:09:29 -0400 Subject: [PATCH 04/11] Can autodetect language --- extensions/ql-vscode/src/definitions.ts | 60 +++++++++++++++++++------ extensions/ql-vscode/src/extension.ts | 4 +- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index 1e4c764cf28..9dc31e04d9f 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -1,11 +1,15 @@ -import * as vscode from "vscode" -import * as messages from "./messages" -import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries"; -import { QueryServerClient } from "./queryserver-client"; -import { DatabaseManager, DatabaseItem } from "./databases"; -import { CodeQLCliServer } from "./cli"; -import { getResultSetSchema, UrlValue, LineColumnLocation, EntityValue } from "./bqrs-cli-types"; +import * as fs from 'fs-extra'; +import * as yaml from 'js-yaml'; +import * as tmp from 'tmp'; +import * as vscode from "vscode"; import { decodeSourceArchiveUri, zipArchiveScheme } from "./archive-filesystem-provider"; +import { EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from "./bqrs-cli-types"; +import { CodeQLCliServer } from "./cli"; +import { DatabaseItem, DatabaseManager } from "./databases"; +import * as helpers from './helpers'; +import * as messages from "./messages"; +import { QueryServerClient } from "./queryserver-client"; +import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries"; const TEMPLATE_NAME = "selectedSourceFile"; const SELECT_QUERY_NAME = "#select"; @@ -14,22 +18,46 @@ enum KeyType { DefinitionQuery, ReferenceQuery } -async function resolveQueries(keyType: KeyType): Promise { +function tagOfKeyType(keyType: KeyType): string { switch (keyType) { - case KeyType.DefinitionQuery: return ["/home/jcreed/semmle/code/ql/cpp/ql/src/localDefinitions.ql"] - case KeyType.ReferenceQuery: return ["/home/jcreed/semmle/code/ql/cpp/ql/src/localReferences.ql"] + case KeyType.DefinitionQuery: return "local-definitions"; + case KeyType.ReferenceQuery: return "local-references"; } } -export function createDefinitionsHandler(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): vscode.DefinitionProvider { +async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise { + const suiteFile = tmp.fileSync({ postfix: '.qls' }).name; + const suiteYaml = { qlpack, include: { kind: 'definitions', 'tags contain': tagOfKeyType(keyType) } }; + await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8'); + + const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders()); + if (queries.length === 0) { + throw new Error("Couldn't find any queries for qlpack"); + } + return queries; +} + +async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise { + if (db.contents === undefined) + return undefined; + const datasetPath = db.contents.datasetUri.fsPath; + const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath); + return qlpack; +} + +export async function createDefinitionsHandler(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): Promise { let fileCache = new CachedOperation(async (uriString: string) => { const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); if (db) { + const qlpack = await qlpackOfDatabase(cli, db); + if (qlpack === undefined) { + throw new Error("Can't infer qlpack from database source archive"); + } const links: vscode.DefinitionLink[] = [] - for (const query of await resolveQueries(KeyType.DefinitionQuery)) { + for (const query of await resolveQueries(cli, qlpack, KeyType.DefinitionQuery)) { const templates: messages.TemplateDefinitions = { [TEMPLATE_NAME]: { values: { @@ -69,15 +97,19 @@ interface FullLocationLink extends vscode.LocationLink { originUri: vscode.Uri; } -export function createReferencesHander(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): vscode.ReferenceProvider { +export async function createReferencesHander(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): Promise { let fileCache = new CachedOperation(async (uriString: string) => { const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); if (db) { + const qlpack = await qlpackOfDatabase(cli, db); + if (qlpack === undefined) { + throw new Error("Can't infer qlpack from database source archive"); + } const links: FullLocationLink[] = [] - for (const query of await resolveQueries(KeyType.ReferenceQuery)) { + for (const query of await resolveQueries(cli, qlpack, KeyType.ReferenceQuery)) { const templates: messages.TemplateDefinitions = { [TEMPLATE_NAME]: { values: { diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 1ad3dfe0f06..fa46c508689 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -338,11 +338,11 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu languages.registerDefinitionProvider( { scheme: archiveFilesystemProvider.zipArchiveScheme }, - createDefinitionsHandler(cliServer, qs, dbm) + await createDefinitionsHandler(cliServer, qs, dbm) ); languages.registerReferenceProvider( { scheme: archiveFilesystemProvider.zipArchiveScheme }, - createReferencesHander(cliServer, qs, dbm) + await createReferencesHander(cliServer, qs, dbm) ); } From 28be98411dde641ed781c4e30dd521754580dceb Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Mon, 20 Apr 2020 10:20:39 -0400 Subject: [PATCH 05/11] Add constants for bqrs column kinds --- extensions/ql-vscode/src/bqrs-cli-types.ts | 23 ++++++++++++++++++++-- extensions/ql-vscode/src/definitions.ts | 8 ++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/bqrs-cli-types.ts b/extensions/ql-vscode/src/bqrs-cli-types.ts index 397937ca0d8..5d418dff98e 100644 --- a/extensions/ql-vscode/src/bqrs-cli-types.ts +++ b/extensions/ql-vscode/src/bqrs-cli-types.ts @@ -1,14 +1,33 @@ export const PAGE_SIZE = 1000; -export type ColumnKind = "f" | "i" | "s" | "b" | "d" | "e"; +/** + * The single-character codes used in the bqrs format for the the kind + * of a result column. This namespace is intentionally not an enum, see + * the "for the sake of extensibility" comment in messages.ts. + */ +export namespace ColumnKindCode { + export const FLOAT = "f"; + export const INTEGER = "i"; + export const STRING = "s"; + export const BOOLEAN = "b"; + export const DATE = "d"; + export const ENTITY = "e"; +} + +export type ColumnKind = + | typeof ColumnKindCode.FLOAT + | typeof ColumnKindCode.INTEGER + | typeof ColumnKindCode.STRING + | typeof ColumnKindCode.BOOLEAN + | typeof ColumnKindCode.DATE + | typeof ColumnKindCode.ENTITY; export interface Column { name?: string; kind: ColumnKind; } - export interface ResultSetSchema { name: string; rows: number; diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index 9dc31e04d9f..e292233c4c6 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -3,7 +3,7 @@ import * as yaml from 'js-yaml'; import * as tmp from 'tmp'; import * as vscode from "vscode"; import { decodeSourceArchiveUri, zipArchiveScheme } from "./archive-filesystem-provider"; -import { EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from "./bqrs-cli-types"; +import { EntityValue, getResultSetSchema, LineColumnLocation, UrlValue, ColumnKindCode } from "./bqrs-cli-types"; import { CodeQLCliServer } from "./cli"; import { DatabaseItem, DatabaseManager } from "./databases"; import * as helpers from './helpers'; @@ -156,9 +156,9 @@ async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServ const info = await cli.bqrsInfo(bqrsPath); const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info); if (selectInfo && selectInfo.columns.length == 3 - && selectInfo.columns[0].kind == "e" - && selectInfo.columns[1].kind == "e" - && selectInfo.columns[2].kind == "s") { + && selectInfo.columns[0].kind == ColumnKindCode.ENTITY + && selectInfo.columns[1].kind == ColumnKindCode.ENTITY + && selectInfo.columns[2].kind == ColumnKindCode.STRING) { // TODO: Page this const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME); for (const tuple of allTuples.tuples) { From 1c6b4a6d1e4cb8c192c74d0522a9772b48cef33b Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 21 Apr 2020 10:14:59 -0400 Subject: [PATCH 06/11] Put CachedOperation in helpers. --- extensions/ql-vscode/src/definitions.ts | 56 +----------------------- extensions/ql-vscode/src/helpers.ts | 57 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index e292233c4c6..ef47cd49229 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -7,6 +7,7 @@ import { EntityValue, getResultSetSchema, LineColumnLocation, UrlValue, ColumnKi import { CodeQLCliServer } from "./cli"; import { DatabaseItem, DatabaseManager } from "./databases"; import * as helpers from './helpers'; +import { CachedOperation } from './helpers'; import * as messages from "./messages"; import { QueryServerClient } from "./queryserver-client"; import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries"; @@ -196,58 +197,3 @@ function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): FileRange | undefine } } } - -const CACHE_SIZE = 100; -class CachedOperation { - private readonly operation: (t: string) => Promise; - private readonly cached: Map; - private readonly lru: string[]; - private readonly inProgressCallbacks: Map void, (reason?: any) => void][]>; - - constructor(operation: (t: string) => Promise) { - this.operation = operation; - this.lru = []; - this.inProgressCallbacks = new Map void, (reason?: any) => void][]>(); - this.cached = new Map(); - } - - async get(t: string): Promise { - // Try and retrieve from the cache - const fromCache = this.cached.get(t); - if (fromCache !== undefined) { - // Move to end of lru list - this.lru.push(this.lru.splice(this.lru.findIndex(v => v === t), 1)[0]) - return fromCache; - } - // Otherwise check if in progress - const inProgressCallback = this.inProgressCallbacks.get(t); - if (inProgressCallback !== undefined) { - // If so wait for it to resolve - return await new Promise((resolve, reject) => { - inProgressCallback.push([resolve, reject]); - }); - } - - // Otherwise compute the new value, but leave a callback to allow sharing work - const callbacks: [(u: U) => void, (reason?: any) => void][] = []; - this.inProgressCallbacks.set(t, callbacks); - try { - const result = await this.operation(t); - callbacks.forEach(f => f[0](result)); - this.inProgressCallbacks.delete(t); - if (this.lru.length > CACHE_SIZE) { - const toRemove = this.lru.shift()!; - this.cached.delete(toRemove); - } - this.lru.push(t); - this.cached.set(t, result); - return result; - } catch (e) { - // Rethrow error on all callbacks - callbacks.forEach(f => f[1](e)); - throw e; - } finally { - this.inProgressCallbacks.delete(t); - } - } -} diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 4b65aeb0fdf..96716904cb1 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -298,3 +298,60 @@ export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFo const qlpack = await getQlPackForDbscheme(cliServer, dbscheme); return { dbscheme, qlpack }; } + +/** + * A cached mapping from strings to value of type U. + */ +export class CachedOperation { + private readonly operation: (t: string) => Promise; + private readonly cached: Map; + private readonly lru: string[]; + private readonly inProgressCallbacks: Map void, (reason?: any) => void][]>; + + constructor(operation: (t: string) => Promise, private cacheSize = 100) { + this.operation = operation; + this.lru = []; + this.inProgressCallbacks = new Map void, (reason?: any) => void][]>(); + this.cached = new Map(); + } + + async get(t: string): Promise { + // Try and retrieve from the cache + const fromCache = this.cached.get(t); + if (fromCache !== undefined) { + // Move to end of lru list + this.lru.push(this.lru.splice(this.lru.findIndex(v => v === t), 1)[0]) + return fromCache; + } + // Otherwise check if in progress + const inProgressCallback = this.inProgressCallbacks.get(t); + if (inProgressCallback !== undefined) { + // If so wait for it to resolve + return await new Promise((resolve, reject) => { + inProgressCallback.push([resolve, reject]); + }); + } + + // Otherwise compute the new value, but leave a callback to allow sharing work + const callbacks: [(u: U) => void, (reason?: any) => void][] = []; + this.inProgressCallbacks.set(t, callbacks); + try { + const result = await this.operation(t); + callbacks.forEach(f => f[0](result)); + this.inProgressCallbacks.delete(t); + if (this.lru.length > this.cacheSize) { + const toRemove = this.lru.shift()!; + this.cached.delete(toRemove); + } + this.lru.push(t); + this.cached.set(t, result); + return result; + } catch (e) { + // Rethrow error on all callbacks + callbacks.forEach(f => f[1](e)); + throw e; + } finally { + this.inProgressCallbacks.delete(t); + } + } +} From 66665bf25eb16f75406f52778624f71316d35f36 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 21 Apr 2020 10:30:25 -0400 Subject: [PATCH 07/11] Refactor definitions/references finding --- extensions/ql-vscode/src/definitions.ts | 181 ++++++++++++------------ extensions/ql-vscode/src/extension.ts | 6 +- 2 files changed, 95 insertions(+), 92 deletions(-) diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index ef47cd49229..20e144fc3a9 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -12,6 +12,13 @@ import * as messages from "./messages"; import { QueryServerClient } from "./queryserver-client"; import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries"; +/** + * Run templated CodeQL queries to find definitions and references in + * source-language files. We may eventually want to find a way to + * generalize this to other custom queries, e.g. showing dataflow to + * or from a selected identifier. + */ + const TEMPLATE_NAME = "selectedSourceFile"; const SELECT_QUERY_NAME = "#select"; @@ -46,104 +53,62 @@ async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise return qlpack; } -export async function createDefinitionsHandler(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): Promise { - let fileCache = new CachedOperation(async (uriString: string) => { - const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); - const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); - - const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); - if (db) { - const qlpack = await qlpackOfDatabase(cli, db); - if (qlpack === undefined) { - throw new Error("Can't infer qlpack from database source archive"); - } - const links: vscode.DefinitionLink[] = [] - for (const query of await resolveQueries(cli, qlpack, KeyType.DefinitionQuery)) { - const templates: messages.TemplateDefinitions = { - [TEMPLATE_NAME]: { - values: { - tuples: [[{ - stringValue: uri.pathWithinSourceArchive - }]] - } - } - }; - const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates); - if (results.result.resultType == messages.QueryResultType.SUCCESS) { - links.push(...await getLinksFromResults(results, cli, db, (src, _dest) => src === uriString)); - } - } - return links; - } else { - return []; - } - }); - - return { - async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { - const fileLinks = await fileCache.get(document.uri.toString()); - let locLinks: vscode.LocationLink[] = []; - for (const link of fileLinks) { - if (link.originSelectionRange!.contains(position)) { - locLinks.push(link); - } - } - return locLinks; - } - - }; -} - interface FullLocationLink extends vscode.LocationLink { originUri: vscode.Uri; } -export async function createReferencesHander(cli: CodeQLCliServer, qs: QueryServerClient, dbm: DatabaseManager): Promise { - let fileCache = new CachedOperation(async (uriString: string) => { - const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); - const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); +export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider { + private cache: CachedOperation; - const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); - if (db) { - const qlpack = await qlpackOfDatabase(cli, db); - if (qlpack === undefined) { - throw new Error("Can't infer qlpack from database source archive"); - } - const links: FullLocationLink[] = [] - for (const query of await resolveQueries(cli, qlpack, KeyType.ReferenceQuery)) { - const templates: messages.TemplateDefinitions = { - [TEMPLATE_NAME]: { - values: { - tuples: [[{ - stringValue: uri.pathWithinSourceArchive - }]] - } - } - }; - const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates); - if (results.result.resultType == messages.QueryResultType.SUCCESS) { - links.push(...await getLinksFromResults(results, cli, db, (_src, dest) => dest === uriString)); - } + constructor( + private cli: CodeQLCliServer, + private qs: QueryServerClient, + private dbm: DatabaseManager, + ) { + this.cache = new CachedOperation(this.getDefinitions.bind(this)); + } + + async getDefinitions(uriString: string): Promise { + return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, (src, _dest) => src === uriString); + } + + async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { + const fileLinks = await this.cache.get(document.uri.toString()); + let locLinks: vscode.LocationLink[] = []; + for (const link of fileLinks) { + if (link.originSelectionRange!.contains(position)) { + locLinks.push(link); } - return links; - } else { - return []; } - }) - - return { - async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { - const fileLinks = await fileCache.get(document.uri.toString()); - let locLinks: vscode.Location[] = []; - for (const link of fileLinks) { - if (link.targetRange!.contains(position)) { - locLinks.push({ range: link.originSelectionRange!, uri: link.originUri }); - } + return locLinks; + } +} + +export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider { + private cache: CachedOperation; + + constructor( + private cli: CodeQLCliServer, + private qs: QueryServerClient, + private dbm: DatabaseManager, + ) { + this.cache = new CachedOperation(this.getReferences.bind(this)); + } + + async getReferences(uriString: string): Promise { + return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, (_src, dest) => dest === uriString); + } + + async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { + const fileLinks = await this.cache.get(document.uri.toString()); + let locLinks: vscode.Location[] = []; + for (const link of fileLinks) { + if (link.targetRange!.contains(position)) { + locLinks.push({ range: link.originSelectionRange!, uri: link.originUri }); } - return locLinks; } - - }; + return locLinks; + } } interface FileRange { @@ -175,6 +140,44 @@ async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServ return localLinks; } +async function getLinksForUriString( + cli: CodeQLCliServer, + qs: QueryServerClient, + dbm: DatabaseManager, + uriString: string, + filter: (src: string, dest: string) => boolean +) { + const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString)); + const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme }); + + const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri); + if (db) { + const qlpack = await qlpackOfDatabase(cli, db); + if (qlpack === undefined) { + throw new Error("Can't infer qlpack from database source archive"); + } + const links: FullLocationLink[] = [] + for (const query of await resolveQueries(cli, qlpack, KeyType.ReferenceQuery)) { + const templates: messages.TemplateDefinitions = { + [TEMPLATE_NAME]: { + values: { + tuples: [[{ + stringValue: uri.pathWithinSourceArchive + }]] + } + } + }; + const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates); + if (results.result.resultType == messages.QueryResultType.SUCCESS) { + links.push(...await getLinksFromResults(results, cli, db, filter)); + } + } + return links; + } else { + return []; + } +} + function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): FileRange | undefined { if (typeof uri === "string") { return undefined; diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index fa46c508689..ef411091cf9 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -6,7 +6,7 @@ import { CodeQLCliServer } from './cli'; import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener } from './config'; import { DatabaseManager } from './databases'; import { DatabaseUI } from './databases-ui'; -import { createDefinitionsHandler, createReferencesHander } from './definitions'; +import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions'; import { DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution'; import * as helpers from './helpers'; import { assertNever } from './helpers-pure'; @@ -338,11 +338,11 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu languages.registerDefinitionProvider( { scheme: archiveFilesystemProvider.zipArchiveScheme }, - await createDefinitionsHandler(cliServer, qs, dbm) + new TemplateQueryDefinitionProvider(cliServer, qs, dbm) ); languages.registerReferenceProvider( { scheme: archiveFilesystemProvider.zipArchiveScheme }, - await createReferencesHander(cliServer, qs, dbm) + new TemplateQueryReferenceProvider(cliServer, qs, dbm) ); } From 701804b6a48dcb980e6e9f104010f821ad030431 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 21 Apr 2020 11:03:20 -0400 Subject: [PATCH 08/11] Guard find-references with experimental setting --- extensions/ql-vscode/src/config.ts | 11 +++++++++++ extensions/ql-vscode/src/extension.ts | 20 +++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/extensions/ql-vscode/src/config.ts b/extensions/ql-vscode/src/config.ts index a1f5bb84d36..9d93e198ee7 100644 --- a/extensions/ql-vscode/src/config.ts +++ b/extensions/ql-vscode/src/config.ts @@ -39,6 +39,17 @@ class Setting { const ROOT_SETTING = new Setting('codeQL'); +// Enable experimental features + +/** + * This setting is deliberately not in package.json so that it does + * not appear in the settings ui in vscode itself. If users want to + * enable experimental features, they can add + * "codeQl.experimentalFeatures" directly in their vscode settings + * json file. + */ +export const EXPERIMENTAL_FEATURES_SETTING = new Setting('experimentalFeatures', ROOT_SETTING); + // Distribution configuration const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING); diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index ef411091cf9..7fcdf5ef3ae 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -3,7 +3,7 @@ import { LanguageClient } from 'vscode-languageclient'; import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api'; import * as archiveFilesystemProvider from './archive-filesystem-provider'; import { CodeQLCliServer } from './cli'; -import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener } from './config'; +import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener, EXPERIMENTAL_FEATURES_SETTING } from './config'; import { DatabaseManager } from './databases'; import { DatabaseUI } from './databases-ui'; import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions'; @@ -336,14 +336,16 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu ctx.subscriptions.push(client.start()); - languages.registerDefinitionProvider( - { scheme: archiveFilesystemProvider.zipArchiveScheme }, - new TemplateQueryDefinitionProvider(cliServer, qs, dbm) - ); - languages.registerReferenceProvider( - { scheme: archiveFilesystemProvider.zipArchiveScheme }, - new TemplateQueryReferenceProvider(cliServer, qs, dbm) - ); + if (EXPERIMENTAL_FEATURES_SETTING.getValue()) { + languages.registerDefinitionProvider( + { scheme: archiveFilesystemProvider.zipArchiveScheme }, + new TemplateQueryDefinitionProvider(cliServer, qs, dbm) + ); + languages.registerReferenceProvider( + { scheme: archiveFilesystemProvider.zipArchiveScheme }, + new TemplateQueryReferenceProvider(cliServer, qs, dbm) + ); + } } function initializeLogging(ctx: ExtensionContext): void { From 7df8905aa039ffb625616d79b1d868d5b17955df Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 21 Apr 2020 13:14:19 -0400 Subject: [PATCH 09/11] Scope tags for ide queries --- extensions/ql-vscode/src/definitions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index 20e144fc3a9..2a4a8333781 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -28,8 +28,8 @@ enum KeyType { function tagOfKeyType(keyType: KeyType): string { switch (keyType) { - case KeyType.DefinitionQuery: return "local-definitions"; - case KeyType.ReferenceQuery: return "local-references"; + case KeyType.DefinitionQuery: return "ide-contextual-queries/local-definitions"; + case KeyType.ReferenceQuery: return "ide-contextual-queries/local-references"; } } From ee591e802fe6f082d59945f6440ec0b55c59faee Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Tue, 21 Apr 2020 13:19:07 -0400 Subject: [PATCH 10/11] Fix lint violations --- extensions/ql-vscode/src/bqrs-cli-types.ts | 1 + extensions/ql-vscode/src/definitions.ts | 8 ++++---- extensions/ql-vscode/src/discovery.ts | 2 +- extensions/ql-vscode/src/logging.ts | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/extensions/ql-vscode/src/bqrs-cli-types.ts b/extensions/ql-vscode/src/bqrs-cli-types.ts index 5d418dff98e..9d45d133d5d 100644 --- a/extensions/ql-vscode/src/bqrs-cli-types.ts +++ b/extensions/ql-vscode/src/bqrs-cli-types.ts @@ -6,6 +6,7 @@ export const PAGE_SIZE = 1000; * of a result column. This namespace is intentionally not an enum, see * the "for the sake of extensibility" comment in messages.ts. */ +// eslint-disable-next-line @typescript-eslint/no-namespace export namespace ColumnKindCode { export const FLOAT = "f"; export const INTEGER = "i"; diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index 2a4a8333781..6346af5764f 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -74,7 +74,7 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { const fileLinks = await this.cache.get(document.uri.toString()); - let locLinks: vscode.LocationLink[] = []; + const locLinks: vscode.LocationLink[] = []; for (const link of fileLinks) { if (link.originSelectionRange!.contains(position)) { locLinks.push(link); @@ -101,7 +101,7 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise { const fileLinks = await this.cache.get(document.uri.toString()); - let locLinks: vscode.Location[] = []; + const locLinks: vscode.Location[] = []; for (const link of fileLinks) { if (link.targetRange!.contains(position)) { locLinks.push({ range: link.originSelectionRange!, uri: link.originUri }); @@ -112,8 +112,8 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider } interface FileRange { - file: vscode.Uri, - range: vscode.Range + file: vscode.Uri; + range: vscode.Range; } async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServer, db: DatabaseItem, filter: (srcFile: string, destFile: string) => boolean): Promise { diff --git a/extensions/ql-vscode/src/discovery.ts b/extensions/ql-vscode/src/discovery.ts index 40fb1f46066..3f4dff5953a 100644 --- a/extensions/ql-vscode/src/discovery.ts +++ b/extensions/ql-vscode/src/discovery.ts @@ -84,4 +84,4 @@ export abstract class Discovery extends DisposableObject { * @param results The discovery results returned by the `discover` function. */ protected abstract update(results: T): void; -} \ No newline at end of file +} diff --git a/extensions/ql-vscode/src/logging.ts b/extensions/ql-vscode/src/logging.ts index 9b925bb4571..d8eeb422338 100644 --- a/extensions/ql-vscode/src/logging.ts +++ b/extensions/ql-vscode/src/logging.ts @@ -60,7 +60,7 @@ export class OutputChannelLogger extends DisposableObject implements Logger { * function if you don't need to guarantee that the log writing is complete before * continuing. */ - async log(message: string, options = { } as LogOptions): Promise { + async log(message: string, options = {} as LogOptions): Promise { if (options.trailingNewline === undefined) { options.trailingNewline = true; } @@ -116,7 +116,7 @@ class AdditionalLogLocation extends Disposable { super(() => { /**/ }); } - async log(message: string, options = { } as LogOptions): Promise { + async log(message: string, options = {} as LogOptions): Promise { if (options.trailingNewline === undefined) { options.trailingNewline = true; } From e242a8fbebcae28b31a10d76518694473ca97c81 Mon Sep 17 00:00:00 2001 From: Jason Reed Date: Wed, 22 Apr 2020 14:09:20 -0400 Subject: [PATCH 11/11] Review comments --- extensions/ql-vscode/src/cli.ts | 3 ++- extensions/ql-vscode/src/definitions.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index 1870aa004c1..11a6126c5fd 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -617,10 +617,11 @@ export class CodeQLCliServer implements Disposable { /** * Gets information about queries in a query suite. + * @param suite The suite to resolve. * @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`. * @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined, * the default CLI search path is used. - * @returns A list of query files found + * @returns A list of query files found. */ resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise { const args = ['--additional-packs', additionalPacks.join(path.delimiter)]; diff --git a/extensions/ql-vscode/src/definitions.ts b/extensions/ql-vscode/src/definitions.ts index 6346af5764f..26221efd9bd 100644 --- a/extensions/ql-vscode/src/definitions.ts +++ b/extensions/ql-vscode/src/definitions.ts @@ -3,7 +3,7 @@ import * as yaml from 'js-yaml'; import * as tmp from 'tmp'; import * as vscode from "vscode"; import { decodeSourceArchiveUri, zipArchiveScheme } from "./archive-filesystem-provider"; -import { EntityValue, getResultSetSchema, LineColumnLocation, UrlValue, ColumnKindCode } from "./bqrs-cli-types"; +import { ColumnKindCode, EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from "./bqrs-cli-types"; import { CodeQLCliServer } from "./cli"; import { DatabaseItem, DatabaseManager } from "./databases"; import * as helpers from './helpers'; @@ -23,7 +23,8 @@ const TEMPLATE_NAME = "selectedSourceFile"; const SELECT_QUERY_NAME = "#select"; enum KeyType { - DefinitionQuery, ReferenceQuery + DefinitionQuery = 'DefinitionQuery', + ReferenceQuery = 'ReferenceQuery', } function tagOfKeyType(keyType: KeyType): string {