{
  "$schema" : "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
  "version" : "2.1.0",
  "runs" : [ {
    "tool" : {
      "driver" : {
        "name" : "CodeQL",
        "organization" : "GitHub",
        "semanticVersion" : "2.8.1",
        "rules" : [ {
          "id" : "remote-query",
          "name" : "remote-query",
          "defaultConfiguration" : {
            "enabled" : true,
            "level" : "warning"
          },
          "properties" : {
            "id" : "remote-query",
            "kind" : "problem"
          }
        } ]
      }
    },
    "artifacts" : [ {
      "location" : {
        "uri" : "extensions/ql-vscode/src/cli.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 0
      },
      "contents" : {
        "text" : "import * as cpp from 'child-process-promise';\nimport * as child_process from 'child_process';\nimport * as path from 'path';\nimport * as sarif from 'sarif';\nimport { SemVer } from 'semver';\nimport { Readable } from 'stream';\nimport { StringDecoder } from 'string_decoder';\nimport * as tk from 'tree-kill';\nimport { promisify } from 'util';\nimport { CancellationToken, Disposable, Uri } from 'vscode';\n\nimport { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';\nimport { CliConfig } from './config';\nimport { DistributionProvider, FindDistributionResultKind } from './distribution';\nimport { assertNever } from './pure/helpers-pure';\nimport { QueryMetadata, SortDirection } from './pure/interface-types';\nimport { Logger, ProgressReporter } from './logging';\nimport { CompilationMessage } from './pure/messages';\nimport { sarifParser } from './sarif-parser';\nimport { dbSchemeToLanguage } from './helpers';\n\n/**\n * The version of the SARIF format that we are using.\n */\nconst SARIF_FORMAT = 'sarifv2.1.0';\n\n/**\n * The string used to specify CSV format.\n */\nconst CSV_FORMAT = 'csv';\n\n/**\n * Flags to pass to all cli commands.\n */\nconst LOGGING_FLAGS = ['-v', '--log-to-stderr'];\n\n/**\n * The expected output of `codeql resolve library-path`.\n */\nexport interface QuerySetup {\n  libraryPath: string[];\n  dbscheme: string;\n  relativeName?: string;\n  compilationCache?: string;\n}\n\n/**\n * The expected output of `codeql resolve queries --format bylanguage`.\n */\nexport interface QueryInfoByLanguage {\n  // Using `unknown` as a placeholder. For now, the value is only ever an empty object.\n  byLanguage: Record<string, Record<string, unknown>>;\n  noDeclaredLanguage: Record<string, unknown>;\n  multipleDeclaredLanguages: Record<string, unknown>;\n}\n\n/**\n * The expected output of `codeql resolve database`.\n */\nexport interface DbInfo {\n  sourceLocationPrefix: string;\n  columnKind: string;\n  unicodeNewlines: boolean;\n  sourceArchiveZip: string;\n  sourceArchiveRoot: string;\n  datasetFolder: string;\n  logsFolder: string;\n  languages: string[];\n}\n\n/**\n * The expected output of `codeql resolve upgrades`.\n */\nexport interface UpgradesInfo {\n  scripts: string[];\n  finalDbscheme: string;\n  matchesTarget?: boolean;\n}\n\n/**\n * The expected output of `codeql resolve qlpacks`.\n */\nexport type QlpacksInfo = { [name: string]: string[] };\n\n/**\n * The expected output of `codeql resolve languages`.\n */\nexport type LanguagesInfo = { [name: string]: string[] };\n\n/** Information about an ML model, as resolved by `codeql resolve ml-models`. */\nexport type MlModelInfo = {\n  checksum: string;\n  path: string;\n};\n\n/** The expected output of `codeql resolve ml-models`. */\nexport type MlModelsInfo = { models: MlModelInfo[] };\n\n/**\n * The expected output of `codeql resolve qlref`.\n */\nexport type QlrefInfo = { resolvedPath: string };\n\n// `codeql bqrs interpret` requires both of these to be present or\n// both absent.\nexport interface SourceInfo {\n  sourceArchive: string;\n  sourceLocationPrefix: string;\n}\n\n/**\n * The expected output of `codeql resolve tests`.\n */\nexport type ResolvedTests = string[];\n\n/**\n * Options for `codeql test run`.\n */\nexport interface TestRunOptions {\n  cancellationToken?: CancellationToken;\n  logger?: Logger;\n}\n\n/**\n * Event fired by `codeql test run`.\n */\nexport interface TestCompleted {\n  test: string;\n  pass: boolean;\n  messages: CompilationMessage[];\n  compilationMs: number;\n  evaluationMs: number;\n  expected: string;\n  diff: string[] | undefined;\n  failureDescription?: string;\n  failureStage?: string;\n}\n\n/**\n * Optional arguments for the `bqrsDecode` function\n */\ninterface BqrsDecodeOptions {\n  /** How many results to get. */\n  pageSize?: number;\n  /** The 0-based index of the first result to get. */\n  offset?: number;\n  /** The entity names to retrieve from the bqrs file. Default is url, string */\n  entities?: string[];\n}\n\n/**\n * This class manages a cli server started by `codeql execute cli-server` to\n * run commands without the overhead of starting a new java\n * virtual machine each time. This class also controls access to the server\n * by queueing the commands sent to it.\n */\nexport class CodeQLCliServer implements Disposable {\n\n\n  /** The process for the cli server, or undefined if one doesn't exist yet */\n  process?: child_process.ChildProcessWithoutNullStreams;\n  /** Queue of future commands*/\n  commandQueue: (() => void)[];\n  /** Whether a command is running */\n  commandInProcess: boolean;\n  /**  A buffer with a single null byte. */\n  nullBuffer: Buffer;\n\n  /** Version of current cli, lazily computed by the `getVersion()` method */\n  private _version: SemVer | undefined;\n\n  /**\n   * The languages supported by the current version of the CLI, computed by `getSupportedLanguages()`.\n   */\n  private _supportedLanguages: string[] | undefined;\n\n  /** Path to current codeQL executable, or undefined if not running yet. */\n  codeQlPath: string | undefined;\n\n  cliConstraints = new CliVersionConstraint(this);\n\n  /**\n   * When set to true, ignore some modal popups and assume user has clicked \"yes\".\n   */\n  public quiet = false;\n\n  constructor(\n    private distributionProvider: DistributionProvider,\n    private cliConfig: CliConfig,\n    private logger: Logger\n  ) {\n    this.commandQueue = [];\n    this.commandInProcess = false;\n    this.nullBuffer = Buffer.alloc(1);\n    if (this.distributionProvider.onDidChangeDistribution) {\n      this.distributionProvider.onDidChangeDistribution(() => {\n        this.restartCliServer();\n        this._version = undefined;\n        this._supportedLanguages = undefined;\n      });\n    }\n    if (this.cliConfig.onDidChangeConfiguration) {\n      this.cliConfig.onDidChangeConfiguration(() => {\n        this.restartCliServer();\n        this._version = undefined;\n        this._supportedLanguages = undefined;\n      });\n    }\n  }\n\n  dispose(): void {\n    this.killProcessIfRunning();\n  }\n\n  killProcessIfRunning(): void {\n    if (this.process) {\n      // Tell the Java CLI server process to shut down.\n      void this.logger.log('Sending shutdown request');\n      try {\n        this.process.stdin.write(JSON.stringify(['shutdown']), 'utf8');\n        this.process.stdin.write(this.nullBuffer);\n        void this.logger.log('Sent shutdown request');\n      } catch (e) {\n        // We are probably fine here, the process has already closed stdin.\n        void this.logger.log(`Shutdown request failed: process stdin may have already closed. The error was ${e}`);\n        void this.logger.log('Stopping the process anyway.');\n      }\n      // Close the stdin and stdout streams.\n      // This is important on Windows where the child process may not die cleanly.\n      this.process.stdin.end();\n      this.process.kill();\n      this.process.stdout.destroy();\n      this.process.stderr.destroy();\n      this.process = undefined;\n\n    }\n  }\n\n  /**\n   * Restart the server when the current command terminates\n   */\n  private restartCliServer(): void {\n    const callback = (): void => {\n      try {\n        this.killProcessIfRunning();\n      } finally {\n        this.runNext();\n      }\n    };\n\n    // If the server is not running a command run this immediately\n    // otherwise add to the front of the queue (as we want to run this after the next command()).\n    if (this.commandInProcess) {\n      this.commandQueue.unshift(callback);\n    } else {\n      callback();\n    }\n\n  }\n\n  /**\n   * Get the path to the CodeQL CLI distribution, or throw an exception if not found.\n   */\n  private async getCodeQlPath(): Promise<string> {\n    const codeqlPath = await this.distributionProvider.getCodeQlPathWithoutVersionCheck();\n    if (!codeqlPath) {\n      throw new Error('Failed to find CodeQL distribution.');\n    }\n    return codeqlPath;\n  }\n\n  /**\n   * Launch the cli server\n   */\n  private async launchProcess(): Promise<child_process.ChildProcessWithoutNullStreams> {\n    const codeQlPath = await this.getCodeQlPath();\n    const args = [];\n    if (shouldDebugCliServer()) {\n      args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y');\n    }\n\n    return await spawnServer(\n      codeQlPath,\n      'CodeQL CLI Server',\n      ['execute', 'cli-server'],\n      args,\n      this.logger,\n      _data => { /**/ }\n    );\n  }\n\n  private async runCodeQlCliInternal(command: string[], commandArgs: string[], description: string): Promise<string> {\n    const stderrBuffers: Buffer[] = [];\n    if (this.commandInProcess) {\n      throw new Error('runCodeQlCliInternal called while cli was running');\n    }\n    this.commandInProcess = true;\n    try {\n      //Launch the process if it doesn't exist\n      if (!this.process) {\n        this.process = await this.launchProcess();\n      }\n      // Grab the process so that typescript know that it is always defined.\n      const process = this.process;\n      // The array of fragments of stdout\n      const stdoutBuffers: Buffer[] = [];\n\n      // Compute the full args array\n      const args = command.concat(LOGGING_FLAGS).concat(commandArgs);\n      const argsString = args.join(' ');\n      void this.logger.log(`${description} using CodeQL CLI: ${argsString}...`);\n      try {\n        await new Promise<void>((resolve, reject) => {\n          // Start listening to stdout\n          process.stdout.addListener('data', (newData: Buffer) => {\n            stdoutBuffers.push(newData);\n            // If the buffer ends in '0' then exit.\n            // We don't have to check the middle as no output will be written after the null until\n            // the next command starts\n            if (newData.length > 0 && newData.readUInt8(newData.length - 1) === 0) {\n              resolve();\n            }\n          });\n          // Listen to stderr\n          process.stderr.addListener('data', (newData: Buffer) => {\n            stderrBuffers.push(newData);\n          });\n          // Listen for process exit.\n          process.addListener('close', (code) => reject(code));\n          // Write the command followed by a null terminator.\n          process.stdin.write(JSON.stringify(args), 'utf8');\n          process.stdin.write(this.nullBuffer);\n        });\n        // Join all the data together\n        const fullBuffer = Buffer.concat(stdoutBuffers);\n        // Make sure we remove the terminator;\n        const data = fullBuffer.toString('utf8', 0, fullBuffer.length - 1);\n        void this.logger.log('CLI command succeeded.');\n        return data;\n      } catch (err) {\n        // Kill the process if it isn't already dead.\n        this.killProcessIfRunning();\n        // Report the error (if there is a stderr then use that otherwise just report the error cod or nodejs error)\n        const newError =\n          stderrBuffers.length == 0\n            ? new Error(`${description} failed: ${err}`)\n            : new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString('utf8')}`);\n        newError.stack += (err.stack || '');\n        throw newError;\n      } finally {\n        void this.logger.log(Buffer.concat(stderrBuffers).toString('utf8'));\n        // Remove the listeners we set up.\n        process.stdout.removeAllListeners('data');\n        process.stderr.removeAllListeners('data');\n        process.removeAllListeners('close');\n      }\n    } finally {\n      this.commandInProcess = false;\n      // start running the next command immediately\n      this.runNext();\n    }\n  }\n\n  /**\n   * Run the next command in the queue\n   */\n  private runNext(): void {\n    const callback = this.commandQueue.shift();\n    if (callback) {\n      callback();\n    }\n  }\n\n  /**\n   * Runs an asynchronous CodeQL CLI command without invoking the CLI server, returning any events\n   * fired by the command as an asynchronous generator.\n   *\n   * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n   * @param commandArgs The arguments to pass to the `codeql` command.\n   * @param cancellationToken CancellationToken to terminate the test process.\n   * @param logger Logger to write text output from the command.\n   * @returns The sequence of async events produced by the command.\n   */\n  private async* runAsyncCodeQlCliCommandInternal(\n    command: string[],\n    commandArgs: string[],\n    cancellationToken?: CancellationToken,\n    logger?: Logger\n  ): AsyncGenerator<string, void, unknown> {\n    // Add format argument first, in case commandArgs contains positional parameters.\n    const args = [\n      ...command,\n      '--format', 'jsonz',\n      ...commandArgs\n    ];\n\n    // Spawn the CodeQL process\n    const codeqlPath = await this.getCodeQlPath();\n    const childPromise = cpp.spawn(codeqlPath, args);\n    const child = childPromise.childProcess;\n\n    let cancellationRegistration: Disposable | undefined = undefined;\n    try {\n      if (cancellationToken !== undefined) {\n        cancellationRegistration = cancellationToken.onCancellationRequested(_e => {\n          tk(child.pid);\n        });\n      }\n      if (logger !== undefined) {\n        // The human-readable output goes to stderr.\n        void logStream(child.stderr!, logger);\n      }\n\n      for await (const event of await splitStreamAtSeparators(child.stdout!, ['\\0'])) {\n        yield event;\n      }\n\n      await childPromise;\n    }\n    finally {\n      if (cancellationRegistration !== undefined) {\n        cancellationRegistration.dispose();\n      }\n    }\n  }\n\n  /**\n   * Runs an asynchronous CodeQL CLI command without invoking the CLI server, returning any events\n   * fired by the command as an asynchronous generator.\n   *\n   * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n   * @param commandArgs The arguments to pass to the `codeql` command.\n   * @param description Description of the action being run, to be shown in log and error messages.\n   * @param cancellationToken CancellationToken to terminate the test process.\n   * @param logger Logger to write text output from the command.\n   * @returns The sequence of async events produced by the command.\n   */\n  public async* runAsyncCodeQlCliCommand<EventType>(\n    command: string[],\n    commandArgs: string[],\n    description: string,\n    cancellationToken?: CancellationToken,\n    logger?: Logger\n  ): AsyncGenerator<EventType, void, unknown> {\n    for await (const event of await this.runAsyncCodeQlCliCommandInternal(command, commandArgs,\n      cancellationToken, logger)) {\n      try {\n        yield JSON.parse(event) as EventType;\n      } catch (err) {\n        throw new Error(`Parsing output of ${description} failed: ${err.stderr || err}`);\n      }\n    }\n  }\n\n  /**\n   * Runs a CodeQL CLI command on the server, returning the output as a string.\n   * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n   * @param commandArgs The arguments to pass to the `codeql` command.\n   * @param description Description of the action being run, to be shown in log and error messages.\n   * @param progressReporter Used to output progress messages, e.g. to the status bar.\n   * @returns The contents of the command's stdout, if the command succeeded.\n   */\n  runCodeQlCliCommand(command: string[], commandArgs: string[], description: string, progressReporter?: ProgressReporter): Promise<string> {\n    if (progressReporter) {\n      progressReporter.report({ message: description });\n    }\n\n    return new Promise((resolve, reject) => {\n      // Construct the command that actually does the work\n      const callback = (): void => {\n        try {\n          this.runCodeQlCliInternal(command, commandArgs, description).then(resolve, reject);\n        } catch (err) {\n          reject(err);\n        }\n      };\n      // If the server is not running a command, then run the given command immediately,\n      // otherwise add to the queue\n      if (this.commandInProcess) {\n        this.commandQueue.push(callback);\n      } else {\n        callback();\n      }\n    });\n  }\n\n  /**\n   * Runs a CodeQL CLI command, returning the output as JSON.\n   * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n   * @param commandArgs The arguments to pass to the `codeql` command.\n   * @param description Description of the action being run, to be shown in log and error messages.\n   * @param addFormat Whether or not to add commandline arguments to specify the format as JSON.\n   * @param progressReporter Used to output progress messages, e.g. to the status bar.\n   * @returns The contents of the command's stdout, if the command succeeded.\n   */\n  async runJsonCodeQlCliCommand<OutputType>(command: string[], commandArgs: string[], description: string, addFormat = true, progressReporter?: ProgressReporter): Promise<OutputType> {\n    let args: string[] = [];\n    if (addFormat) // Add format argument first, in case commandArgs contains positional parameters.\n      args = args.concat(['--format', 'json']);\n    args = args.concat(commandArgs);\n    const result = await this.runCodeQlCliCommand(command, args, description, progressReporter);\n    try {\n      return JSON.parse(result) as OutputType;\n    } catch (err) {\n      throw new Error(`Parsing output of ${description} failed: ${err.stderr || err}`);\n    }\n  }\n\n  /**\n   * Resolve the library path and dbscheme for a query.\n   * @param workspaces The current open workspaces\n   * @param queryPath The path to the query\n   */\n  async resolveLibraryPath(workspaces: string[], queryPath: string): Promise<QuerySetup> {\n    const subcommandArgs = [\n      '--query', queryPath,\n      ...this.getAdditionalPacksArg(workspaces)\n    ];\n    return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');\n  }\n\n  /**\n   * Resolves the language for a query.\n   * @param queryUri The URI of the query\n   */\n  async resolveQueryByLanguage(workspaces: string[], queryUri: Uri): Promise<QueryInfoByLanguage> {\n    const subcommandArgs = [\n      '--format', 'bylanguage',\n      queryUri.fsPath,\n      ...this.getAdditionalPacksArg(workspaces)\n    ];\n    return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));\n  }\n\n  /**\n   * Finds all available QL tests in a given directory.\n   * @param testPath Root of directory tree to search for tests.\n   * @returns The list of tests that were found.\n   */\n  public async resolveTests(testPath: string): Promise<ResolvedTests> {\n    const subcommandArgs = [\n      testPath\n    ];\n    return await this.runJsonCodeQlCliCommand<ResolvedTests>(\n      ['resolve', 'tests', '--strict-test-discovery'],\n      subcommandArgs,\n      'Resolving tests'\n    );\n  }\n\n  public async resolveQlref(qlref: string): Promise<QlrefInfo> {\n    const subcommandArgs = [\n      qlref\n    ];\n    return await this.runJsonCodeQlCliCommand<QlrefInfo>(\n      ['resolve', 'qlref'],\n      subcommandArgs,\n      'Resolving qlref',\n      false\n    );\n  }\n\n  /**\n   * Issues an internal clear-cache command to the cli server. This\n   * command is used to clear the qlpack cache of the server.\n   *\n   * This cache is generally cleared every 1s. This method is used\n   * to force an early clearing of the cache.\n   */\n  public async clearCache(): Promise<void> {\n    await this.runCodeQlCliCommand(['clear-cache'], [], 'Clearing qlpack cache');\n  }\n\n  /**\n   * Runs QL tests.\n   * @param testPaths Full paths of the tests to run.\n   * @param workspaces Workspace paths to use as search paths for QL packs.\n   * @param options Additional options.\n   */\n  public async* runTests(\n    testPaths: string[], workspaces: string[], options: TestRunOptions\n  ): AsyncGenerator<TestCompleted, void, unknown> {\n\n    const subcommandArgs = this.cliConfig.additionalTestArguments.concat([\n      ...this.getAdditionalPacksArg(workspaces),\n      '--threads',\n      this.cliConfig.numberTestThreads.toString(),\n      ...testPaths\n    ]);\n\n    for await (const event of await this.runAsyncCodeQlCliCommand<TestCompleted>(['test', 'run'],\n      subcommandArgs, 'Run CodeQL Tests', options.cancellationToken, options.logger)) {\n      yield event;\n    }\n  }\n\n  /**\n   * Gets the metadata for a query.\n   * @param queryPath The path to the query.\n   */\n  async resolveMetadata(queryPath: string): Promise<QueryMetadata> {\n    return await this.runJsonCodeQlCliCommand<QueryMetadata>(['resolve', 'metadata'], [queryPath], 'Resolving query metadata');\n  }\n\n  /** Resolves the ML models that should be available when evaluating a query. */\n  async resolveMlModels(additionalPacks: string[]): Promise<MlModelsInfo> {\n    return await this.runJsonCodeQlCliCommand<MlModelsInfo>(\n      ['resolve', 'ml-models'],\n      this.getAdditionalPacksArg(additionalPacks),\n      'Resolving ML models',\n      false\n    );\n  }\n\n  /**\n   * Gets the RAM setting for the query server.\n   * @param queryMemoryMb The maximum amount of RAM to use, in MB.\n   * Leave `undefined` for CodeQL to choose a limit based on the available system memory.\n   * @param progressReporter The progress reporter to send progress information to.\n   * @returns String arguments that can be passed to the CodeQL query server,\n   * indicating how to split the given RAM limit between heap and off-heap memory.\n   */\n  async resolveRam(queryMemoryMb: number | undefined, progressReporter?: ProgressReporter): Promise<string[]> {\n    const args: string[] = [];\n    if (queryMemoryMb !== undefined) {\n      args.push('--ram', queryMemoryMb.toString());\n    }\n    return await this.runJsonCodeQlCliCommand<string[]>(['resolve', 'ram'], args, 'Resolving RAM settings', true, progressReporter);\n  }\n  /**\n   * Gets the headers (and optionally pagination info) of a bqrs.\n   * @param bqrsPath The path to the bqrs.\n   * @param pageSize The page size to precompute offsets into the binary file for.\n   */\n  async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BQRSInfo> {\n    const subcommandArgs = (\n      pageSize ? ['--paginate-rows', pageSize.toString()] : []\n    ).concat(\n      bqrsPath\n    );\n    return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');\n  }\n\n  async databaseUnbundle(archivePath: string, target: string, name?: string): Promise<string> {\n    const subcommandArgs = [];\n    if (target) subcommandArgs.push('--target', target);\n    if (name) subcommandArgs.push('--name', name);\n    subcommandArgs.push(archivePath);\n\n    return await this.runCodeQlCliCommand(['database', 'unbundle'], subcommandArgs, `Extracting ${archivePath} to directory ${target}`);\n  }\n\n  /**\n   * Uses a .qhelp file to generate Query Help documentation in a specified format.\n   * @param pathToQhelp The path to the .qhelp file\n   * @param format The format in which the query help should be generated {@link https://codeql.github.com/docs/codeql-cli/manual/generate-query-help/#cmdoption-codeql-generate-query-help-format}\n   * @param outputDirectory The output directory for the generated file\n   */\n  async generateQueryHelp(pathToQhelp: string, outputDirectory?: string): Promise<string> {\n    const subcommandArgs = ['--format=markdown'];\n    if (outputDirectory) subcommandArgs.push('--output', outputDirectory);\n    subcommandArgs.push(pathToQhelp);\n\n    return await this.runCodeQlCliCommand(['generate', 'query-help'], subcommandArgs, `Generating qhelp in markdown format at ${outputDirectory}`);\n  }\n\n  /**\n  * Gets the results from a bqrs.\n  * @param bqrsPath The path to the bqrs.\n  * @param resultSet The result set to get.\n  * @param options Optional BqrsDecodeOptions arguments\n  */\n  async bqrsDecode(\n    bqrsPath: string,\n    resultSet: string,\n    { pageSize, offset, entities = ['url', 'string'] }: BqrsDecodeOptions = {}\n  ): Promise<DecodedBqrsChunk> {\n\n    const subcommandArgs = [\n      `--entities=${entities.join(',')}`,\n      '--result-set', resultSet,\n    ].concat(\n      pageSize ? ['--rows', pageSize.toString()] : []\n    ).concat(\n      offset ? ['--start-at', offset.toString()] : []\n    ).concat([bqrsPath]);\n    return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(['bqrs', 'decode'], subcommandArgs, 'Reading bqrs data');\n  }\n\n  async runInterpretCommand(format: string, metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {\n    const args = [\n      '--output', interpretedResultsPath,\n      '--format', format,\n      // Forward all of the query metadata.\n      ...Object.entries(metadata).map(([key, value]) => `-t=${key}=${value}`)\n    ];\n    if (format == SARIF_FORMAT) {\n      // TODO: This flag means that we don't group interpreted results\n      // by primary location. We may want to revisit whether we call\n      // interpretation with and without this flag, or do some\n      // grouping client-side.\n      args.push('--no-group-results');\n    }\n    if (sourceInfo !== undefined) {\n      args.push(\n        '--source-archive', sourceInfo.sourceArchive,\n        '--source-location-prefix', sourceInfo.sourceLocationPrefix\n      );\n    }\n\n    args.push(\n      '--threads',\n      this.cliConfig.numberThreads.toString(),\n    );\n\n    args.push(\n      '--max-paths',\n      this.cliConfig.maxPaths.toString(),\n    );\n\n    args.push(resultsPath);\n    await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');\n  }\n\n  async interpretBqrs(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {\n    await this.runInterpretCommand(SARIF_FORMAT, metadata, resultsPath, interpretedResultsPath, sourceInfo);\n    return await sarifParser(interpretedResultsPath);\n  }\n\n  async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise<void> {\n    await this.runInterpretCommand(CSV_FORMAT, metadata, resultsPath, csvPath, sourceInfo);\n  }\n\n  async sortBqrs(resultsPath: string, sortedResultsPath: string, resultSet: string, sortKeys: number[], sortDirections: SortDirection[]): Promise<void> {\n    const sortDirectionStrings = sortDirections.map(direction => {\n      switch (direction) {\n        case SortDirection.asc:\n          return 'asc';\n        case SortDirection.desc:\n          return 'desc';\n        default:\n          return assertNever(direction);\n      }\n    });\n\n    await this.runCodeQlCliCommand(['bqrs', 'decode'],\n      [\n        '--format=bqrs',\n        `--result-set=${resultSet}`,\n        `--output=${sortedResultsPath}`,\n        `--sort-key=${sortKeys.join(',')}`,\n        `--sort-direction=${sortDirectionStrings.join(',')}`,\n        resultsPath\n      ],\n      'Sorting query results');\n  }\n\n\n  /**\n   * Returns the `DbInfo` for a database.\n   * @param databasePath Path to the CodeQL database to obtain information from.\n   */\n  resolveDatabase(databasePath: string): Promise<DbInfo> {\n    return this.runJsonCodeQlCliCommand(['resolve', 'database'], [databasePath],\n      'Resolving database');\n  }\n\n  /**\n   * Gets information necessary for upgrading a database.\n   * @param dbScheme the path to the dbscheme of the database to be upgraded.\n   * @param searchPath A list of directories to search for upgrade scripts.\n   * @param allowDowngradesIfPossible Whether we should try and include downgrades of we can.\n   * @param targetDbScheme The dbscheme to try to upgrade to.\n   * @returns A list of database upgrade script directories\n   */\n  async resolveUpgrades(dbScheme: string, searchPath: string[], allowDowngradesIfPossible: boolean, targetDbScheme?: string): Promise<UpgradesInfo> {\n    const args = [...this.getAdditionalPacksArg(searchPath), '--dbscheme', dbScheme];\n    if (targetDbScheme) {\n      args.push('--target-dbscheme', targetDbScheme);\n      if (allowDowngradesIfPossible && await this.cliConstraints.supportsDowngrades()) {\n        args.push('--allow-downgrades');\n      }\n    }\n    return await this.runJsonCodeQlCliCommand<UpgradesInfo>(\n      ['resolve', 'upgrades'],\n      args,\n      'Resolving database upgrade scripts',\n    );\n  }\n\n  /**\n   * Gets information about available qlpacks\n   * @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`.\n   * @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined,\n   *   the default CLI search path is used.\n   * @returns A dictionary mapping qlpack name to the directory it comes from\n   */\n  resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {\n    const args = this.getAdditionalPacksArg(additionalPacks);\n    if (searchPath?.length) {\n      args.push('--search-path', path.join(...searchPath));\n    }\n\n    return this.runJsonCodeQlCliCommand<QlpacksInfo>(\n      ['resolve', 'qlpacks'],\n      args,\n      'Resolving qlpack information',\n    );\n  }\n\n  /**\n   * Gets information about the available languages.\n   * @returns A dictionary mapping language name to the directory it comes from\n   */\n  async resolveLanguages(): Promise<LanguagesInfo> {\n    return await this.runJsonCodeQlCliCommand<LanguagesInfo>(['resolve', 'languages'], [], 'Resolving languages');\n  }\n\n  /**\n   * Gets the list of available languages. Refines the result of `resolveLanguages()`, by excluding\n   * extra things like \"xml\" and \"properties\".\n   *\n   * @returns An array of languages that are supported by the current version of the CodeQL CLI.\n   */\n  public async getSupportedLanguages(): Promise<string[]> {\n    if (!this._supportedLanguages) {\n      // Get the intersection of resolveLanguages with the list of hardcoded languages in dbSchemeToLanguage.\n      const resolvedLanguages = Object.keys(await this.resolveLanguages());\n      const hardcodedLanguages = Object.values(dbSchemeToLanguage);\n\n      this._supportedLanguages = resolvedLanguages.filter(lang => hardcodedLanguages.includes(lang));\n    }\n    return this._supportedLanguages;\n  }\n\n  /**\n   * Gets information about queries in a query suite.\n   * @param suite The suite to resolve.\n   * @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`.\n   * @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined,\n   *   the default CLI search path is used.\n   * @returns A list of query files found.\n   */\n  async resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {\n    const args = this.getAdditionalPacksArg(additionalPacks);\n    if (searchPath !== undefined) {\n      args.push('--search-path', path.join(...searchPath));\n    }\n    if (await this.cliConstraints.supportsAllowLibraryPacksInResolveQueries()) {\n      // All of our usage of `codeql resolve queries` needs to handle library packs.\n      args.push('--allow-library-packs');\n    }\n    args.push(suite);\n    return this.runJsonCodeQlCliCommand<string[]>(\n      ['resolve', 'queries'],\n      args,\n      'Resolving queries',\n    );\n  }\n\n  /**\n   * Downloads a specified pack.\n   * @param packs The `<package-scope/name[@version]>` of the packs to download.\n   */\n  async packDownload(packs: string[]) {\n    return this.runJsonCodeQlCliCommand(['pack', 'download'], packs, 'Downloading packs');\n  }\n\n  async packInstall(dir: string) {\n    return this.runJsonCodeQlCliCommand(['pack', 'install'], [dir], 'Installing pack dependencies');\n  }\n\n  async packBundle(dir: string, workspaceFolders: string[], outputPath: string, precompile = true): Promise<void> {\n    const args = [\n      '-o',\n      outputPath,\n      dir,\n      ...this.getAdditionalPacksArg(workspaceFolders)\n    ];\n    if (!precompile && await this.cliConstraints.supportsNoPrecompile()) {\n      args.push('--no-precompile');\n    }\n\n    return this.runJsonCodeQlCliCommand(['pack', 'bundle'], args, 'Bundling pack');\n  }\n\n  async packPacklist(dir: string, includeQueries: boolean): Promise<string[]> {\n    const args = includeQueries ? [dir] : ['--no-include-queries', dir];\n    // since 2.7.1, packlist returns an object with a \"paths\" property that is a list of packs.\n    // previous versions return a list of packs.\n    const results: { paths: string[] } | string[] = await this.runJsonCodeQlCliCommand(['pack', 'packlist'], args, 'Generating the pack list');\n\n    // Once we no longer need to support 2.7.0 or earlier, we can remove this and assume all versions return an object.\n    if ('paths' in results) {\n      return results.paths;\n    } else {\n      return results;\n    }\n  }\n\n  async generateDil(qloFile: string, outFile: string): Promise<void> {\n    const extraArgs = await this.cliConstraints.supportsDecompileDil()\n      ? ['--kind', 'dil', '-o', outFile, qloFile]\n      : ['-o', outFile, qloFile];\n    await this.runCodeQlCliCommand(\n      ['query', 'decompile'],\n      extraArgs,\n      'Generating DIL',\n    );\n  }\n\n  public async getVersion() {\n    if (!this._version) {\n      this._version = await this.refreshVersion();\n    }\n    return this._version;\n  }\n\n  private async refreshVersion() {\n    const distribution = await this.distributionProvider.getDistribution();\n    switch (distribution.kind) {\n      case FindDistributionResultKind.CompatibleDistribution:\n      // eslint-disable-next-line no-fallthrough\n      case FindDistributionResultKind.IncompatibleDistribution:\n        return distribution.version;\n\n      default:\n        // We should not get here because if no distributions are available, then\n        // the cli class is never instantiated.\n        throw new Error('No distribution found');\n    }\n  }\n\n  private getAdditionalPacksArg(paths: string[]): string[] {\n    return paths.length\n      ? ['--additional-packs', paths.join(path.delimiter)]\n      : [];\n  }\n}\n\n/**\n * Spawns a child server process using the CodeQL CLI\n * and attaches listeners to it.\n *\n * @param config The configuration containing the path to the CLI.\n * @param name Name of the server being started, to be shown in log and error messages.\n * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n * @param commandArgs The arguments to pass to the `codeql` command.\n * @param logger Logger to write startup messages.\n * @param stderrListener Listener for log messages from the server's stderr stream.\n * @param stdoutListener Optional listener for messages from the server's stdout stream.\n * @param progressReporter Used to output progress messages, e.g. to the status bar.\n * @returns The started child process.\n */\nexport function spawnServer(\n  codeqlPath: string,\n  name: string,\n  command: string[],\n  commandArgs: string[],\n  logger: Logger,\n  stderrListener: (data: any) => void,\n  stdoutListener?: (data: any) => void,\n  progressReporter?: ProgressReporter\n): child_process.ChildProcessWithoutNullStreams {\n  // Enable verbose logging.\n  const args = command.concat(commandArgs).concat(LOGGING_FLAGS);\n\n  // Start the server process.\n  const base = codeqlPath;\n  const argsString = args.join(' ');\n  if (progressReporter !== undefined) {\n    progressReporter.report({ message: `Starting ${name}` });\n  }\n  void logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`);\n  const child = child_process.spawn(base, args);\n  if (!child || !child.pid) {\n    throw new Error(`Failed to start ${name} using command ${base} ${argsString}.`);\n  }\n\n  // Set up event listeners.\n  child.on('close', (code) => logger.log(`Child process exited with code ${code}`));\n  child.stderr!.on('data', stderrListener);\n  if (stdoutListener !== undefined) {\n    child.stdout!.on('data', stdoutListener);\n  }\n\n  if (progressReporter !== undefined) {\n    progressReporter.report({ message: `Started ${name}` });\n  }\n  void logger.log(`${name} started on PID: ${child.pid}`);\n  return child;\n}\n\n/**\n * Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string.\n * @param codeQlPath The path to the CLI.\n * @param command The `codeql` command to be run, provided as an array of command/subcommand names.\n * @param commandArgs The arguments to pass to the `codeql` command.\n * @param description Description of the action being run, to be shown in log and error messages.\n * @param logger Logger to write command log messages, e.g. to an output channel.\n * @param progressReporter Used to output progress messages, e.g. to the status bar.\n * @returns The contents of the command's stdout, if the command succeeded.\n */\nexport async function runCodeQlCliCommand(\n  codeQlPath: string,\n  command: string[],\n  commandArgs: string[],\n  description: string,\n  logger: Logger,\n  progressReporter?: ProgressReporter\n): Promise<string> {\n  // Add logging arguments first, in case commandArgs contains positional parameters.\n  const args = command.concat(LOGGING_FLAGS).concat(commandArgs);\n  const argsString = args.join(' ');\n  try {\n    if (progressReporter !== undefined) {\n      progressReporter.report({ message: description });\n    }\n    void logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`);\n    const result = await promisify(child_process.execFile)(codeQlPath, args);\n    void logger.log(result.stderr);\n    void logger.log('CLI command succeeded.');\n    return result.stdout;\n  } catch (err) {\n    throw new Error(`${description} failed: ${err.stderr || err}`);\n  }\n}\n\n/**\n * Buffer to hold state used when splitting a text stream into lines.\n */\nclass SplitBuffer {\n  private readonly decoder = new StringDecoder('utf8');\n  private readonly maxSeparatorLength: number;\n  private buffer = '';\n  private searchIndex = 0;\n\n  constructor(private readonly separators: readonly string[]) {\n    this.maxSeparatorLength = separators.map(s => s.length).reduce((a, b) => Math.max(a, b), 0);\n  }\n\n  /**\n   * Append new text data to the buffer.\n   * @param chunk The chunk of data to append.\n   */\n  public addChunk(chunk: Buffer): void {\n    this.buffer += this.decoder.write(chunk);\n  }\n\n  /**\n   * Signal that the end of the input stream has been reached.\n   */\n  public end(): void {\n    this.buffer += this.decoder.end();\n    this.buffer += this.separators[0];  // Append a separator to the end to ensure the last line is returned.\n  }\n\n  /**\n   * A version of startsWith that isn't overriden by a broken version of ms-python.\n   *\n   * The definition comes from\n   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith\n   * which is CC0/public domain\n   *\n   * See https://github.com/github/vscode-codeql/issues/802 for more context as to why we need it.\n   */\n  private static startsWith(s: string, searchString: string, position: number): boolean {\n    const pos = position > 0 ? position | 0 : 0;\n    return s.substring(pos, pos + searchString.length) === searchString;\n  }\n\n  /**\n   * Extract the next full line from the buffer, if one is available.\n   * @returns The text of the next available full line (without the separator), or `undefined` if no\n   * line is available.\n   */\n  public getNextLine(): string | undefined {\n    while (this.searchIndex <= (this.buffer.length - this.maxSeparatorLength)) {\n      for (const separator of this.separators) {\n        if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {\n          const line = this.buffer.substr(0, this.searchIndex);\n          this.buffer = this.buffer.substr(this.searchIndex + separator.length);\n          this.searchIndex = 0;\n          return line;\n        }\n      }\n      this.searchIndex++;\n    }\n\n    return undefined;\n  }\n}\n\n/**\n * Splits a text stream into lines based on a list of valid line separators.\n * @param stream The text stream to split. This stream will be fully consumed.\n * @param separators The list of strings that act as line separators.\n * @returns A sequence of lines (not including separators).\n */\nasync function* splitStreamAtSeparators(\n  stream: Readable, separators: string[]\n): AsyncGenerator<string, void, unknown> {\n\n  const buffer = new SplitBuffer(separators);\n  for await (const chunk of stream) {\n    buffer.addChunk(chunk);\n    let line: string | undefined;\n    do {\n      line = buffer.getNextLine();\n      if (line !== undefined) {\n        yield line;\n      }\n    } while (line !== undefined);\n  }\n  buffer.end();\n  let line: string | undefined;\n  do {\n    line = buffer.getNextLine();\n    if (line !== undefined) {\n      yield line;\n    }\n  } while (line !== undefined);\n}\n\n/**\n *  Standard line endings for splitting human-readable text.\n */\nconst lineEndings = ['\\r\\n', '\\r', '\\n'];\n\n/**\n * Log a text stream to a `Logger` interface.\n * @param stream The stream to log.\n * @param logger The logger that will consume the stream output.\n */\nasync function logStream(stream: Readable, logger: Logger): Promise<void> {\n  for await (const line of await splitStreamAtSeparators(stream, lineEndings)) {\n    // Await the result of log here in order to ensure the logs are written in the correct order.\n    await logger.log(line);\n  }\n}\n\n\nexport function shouldDebugIdeServer() {\n  return 'IDE_SERVER_JAVA_DEBUG' in process.env\n    && process.env.IDE_SERVER_JAVA_DEBUG !== '0'\n    && process.env.IDE_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';\n}\n\nexport function shouldDebugQueryServer() {\n  return 'QUERY_SERVER_JAVA_DEBUG' in process.env\n    && process.env.QUERY_SERVER_JAVA_DEBUG !== '0'\n    && process.env.QUERY_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';\n}\n\nexport function shouldDebugCliServer() {\n  return 'CLI_SERVER_JAVA_DEBUG' in process.env\n    && process.env.CLI_SERVER_JAVA_DEBUG !== '0'\n    && process.env.CLI_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';\n}\n\nexport class CliVersionConstraint {\n\n  /**\n   * CLI version where --kind=DIL was introduced\n   */\n  public static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0');\n\n  /**\n   * CLI version where languages are exposed during a `codeql resolve database` command.\n   */\n  public static CLI_VERSION_WITH_LANGUAGE = new SemVer('2.4.1');\n\n  /**\n   * CLI version where `codeql resolve upgrades` supports\n   * the `--allow-downgrades` flag\n   */\n  public static CLI_VERSION_WITH_DOWNGRADES = new SemVer('2.4.4');\n\n  /**\n   * CLI version where the `codeql resolve qlref` command is available.\n   */\n  public static CLI_VERSION_WITH_RESOLVE_QLREF = new SemVer('2.5.1');\n\n  /**\n   * CLI version where database registration was introduced\n  */\n  public static CLI_VERSION_WITH_DB_REGISTRATION = new SemVer('2.4.1');\n\n  /**\n   * CLI version where the `--allow-library-packs` option to `codeql resolve queries` was\n   * introduced.\n   */\n  public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');\n\n  /**\n   * CLI version where the `database unbundle` subcommand was introduced.\n   */\n  public static CLI_VERSION_WITH_DATABASE_UNBUNDLE = new SemVer('2.6.0');\n\n  /**\n   * CLI version where the `--no-precompile` option for pack creation was introduced.\n   */\n  public static CLI_VERSION_WITH_NO_PRECOMPILE = new SemVer('2.7.1');\n\n  /**\n   * CLI version where remote queries are supported.\n   */\n  public static CLI_VERSION_REMOTE_QUERIES = new SemVer('2.6.3');\n\n  /**\n   * CLI version where the `resolve ml-models` subcommand was introduced.\n   */\n  public static CLI_VERSION_WITH_RESOLVE_ML_MODELS = new SemVer('2.7.3');\n\n  /**\n   * CLI version where the `--old-eval-stats` option to the query server was introduced.\n   */\n  public static CLI_VERSION_WITH_OLD_EVAL_STATS = new SemVer('2.7.4');\n\n  /**\n   * CLI version where packaging was introduced.\n   */\n  public static CLI_VERSION_WITH_PACKAGING = new SemVer('2.6.0');\n\n  constructor(private readonly cli: CodeQLCliServer) {\n    /**/\n  }\n\n  private async isVersionAtLeast(v: SemVer) {\n    return (await this.cli.getVersion()).compare(v) >= 0;\n  }\n\n  public async supportsDecompileDil() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DECOMPILE_KIND_DIL);\n  }\n\n  public async supportsLanguageName() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_LANGUAGE);\n  }\n\n  public async supportsDowngrades() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DOWNGRADES);\n  }\n\n  public async supportsResolveQlref() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF);\n  }\n\n  public async supportsAllowLibraryPacksInResolveQueries() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES);\n  }\n\n  async supportsDatabaseRegistration() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);\n  }\n\n  async supportsDatabaseUnbundle() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DATABASE_UNBUNDLE);\n  }\n\n  async supportsNoPrecompile() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_NO_PRECOMPILE);\n  }\n\n  async supportsRemoteQueries() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES);\n  }\n\n  async supportsResolveMlModels() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_ML_MODELS);\n  }\n\n  async supportsOldEvalStats() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_OLD_EVAL_STATS);\n  }\n\n  async supportsPackaging() {\n    return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_PACKAGING);\n  }\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/extension.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 1
      },
      "contents" : {
        "text" : "import {\n  CancellationToken,\n  CancellationTokenSource,\n  commands,\n  Disposable,\n  ExtensionContext,\n  extensions,\n  languages,\n  ProgressLocation,\n  ProgressOptions,\n  Uri,\n  window as Window,\n  env,\n  window,\n  QuickPickItem,\n  Range,\n  workspace,\n  ProviderResult\n} from 'vscode';\nimport { LanguageClient } from 'vscode-languageclient';\nimport * as os from 'os';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport * as tmp from 'tmp-promise';\nimport { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';\n\nimport { AstViewer } from './astViewer';\nimport * as archiveFilesystemProvider from './archive-filesystem-provider';\nimport QuickEvalCodeLensProvider from './quickEvalCodeLensProvider';\nimport { CodeQLCliServer, CliVersionConstraint } from './cli';\nimport {\n  CliConfigListener,\n  DistributionConfigListener,\n  isCanary,\n  MAX_QUERIES,\n  QueryHistoryConfigListener,\n  QueryServerConfigListener\n} from './config';\nimport * as languageSupport from './languageSupport';\nimport { DatabaseItem, DatabaseManager } from './databases';\nimport { DatabaseUI } from './databases-ui';\nimport {\n  TemplateQueryDefinitionProvider,\n  TemplateQueryReferenceProvider,\n  TemplatePrintAstProvider\n} from './contextual/templateProvider';\nimport {\n  DEFAULT_DISTRIBUTION_VERSION_RANGE,\n  DistributionKind,\n  DistributionManager,\n  DistributionUpdateCheckResultKind,\n  FindDistributionResult,\n  FindDistributionResultKind,\n  GithubApiError,\n  GithubRateLimitedError\n} from './distribution';\nimport {\n  findLanguage,\n  tmpDirDisposal,\n  showBinaryChoiceDialog,\n  showAndLogErrorMessage,\n  showAndLogWarningMessage,\n  showAndLogInformationMessage,\n  showInformationMessageWithAction,\n  tmpDir\n} from './helpers';\nimport { assertNever } from './pure/helpers-pure';\nimport { spawnIdeServer } from './ide-server';\nimport { InterfaceManager } from './interface';\nimport { WebviewReveal } from './interface-utils';\nimport { ideServerLogger, logger, queryServerLogger } from './logging';\nimport { QueryHistoryManager } from './query-history';\nimport { CompletedLocalQueryInfo, LocalQueryInfo } from './query-results';\nimport * as qsClient from './queryserver-client';\nimport { displayQuickQuery } from './quick-query';\nimport { compileAndRunQueryAgainstDatabase, createInitialQueryInfo } from './run-queries';\nimport { QLTestAdapterFactory } from './test-adapter';\nimport { TestUIService } from './test-ui';\nimport { CompareInterfaceManager } from './compare/compare-interface';\nimport { gatherQlFiles } from './pure/files';\nimport { initializeTelemetry } from './telemetry';\nimport {\n  commandRunner,\n  commandRunnerWithProgress,\n  ProgressCallback,\n  withProgress,\n  ProgressUpdate\n} from './commandRunner';\nimport { CodeQlStatusBarHandler } from './status-bar';\n\nimport { Credentials } from './authentication';\nimport { RemoteQueriesManager } from './remote-queries/remote-queries-manager';\nimport { RemoteQueryResult } from './remote-queries/remote-query-result';\nimport { URLSearchParams } from 'url';\nimport { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';\nimport * as sampleData from './remote-queries/sample-data';\nimport { handleDownloadPacks, handleInstallPackDependencies } from './packaging';\nimport { AnalysesResultsManager } from './remote-queries/analyses-results-manager';\nimport { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';\n\n/**\n * extension.ts\n * ------------\n *\n * A vscode extension for CodeQL query development.\n */\n\n/**\n * Holds when we have proceeded past the initial phase of extension activation in which\n * we are trying to ensure that a valid CodeQL distribution exists, and we're actually setting\n * up the bulk of the extension.\n */\nlet beganMainExtensionActivation = false;\n\n/**\n * A list of vscode-registered-command disposables that contain\n * temporary stub handlers for commands that exist package.json (hence\n * are already connected to onscreen ui elements) but which will not\n * have any useful effect if we haven't located a CodeQL distribution.\n */\nconst errorStubs: Disposable[] = [];\n\n/**\n * Holds when we are installing or checking for updates to the distribution.\n */\nlet isInstallingOrUpdatingDistribution = false;\n\nconst extensionId = 'GitHub.vscode-codeql';\nconst extension = extensions.getExtension(extensionId);\n\n/**\n * If the user tries to execute vscode commands after extension activation is failed, give\n * a sensible error message.\n *\n * @param excludedCommands List of commands for which we should not register error stubs.\n */\nfunction registerErrorStubs(excludedCommands: string[], stubGenerator: (command: string) => () => Promise<void>): void {\n  // Remove existing stubs\n  errorStubs.forEach(stub => stub.dispose());\n\n  if (extension === undefined) {\n    throw new Error(`Can't find extension ${extensionId}`);\n  }\n\n  const stubbedCommands: string[]\n    = extension.packageJSON.contributes.commands.map((entry: { command: string }) => entry.command);\n\n  stubbedCommands.forEach(command => {\n    if (excludedCommands.indexOf(command) === -1) {\n      errorStubs.push(commandRunner(command, stubGenerator(command)));\n    }\n  });\n}\n\n/**\n * The publicly available interface for this extension. This is to\n * be used in our tests.\n */\nexport interface CodeQLExtensionInterface {\n  readonly ctx: ExtensionContext;\n  readonly cliServer: CodeQLCliServer;\n  readonly qs: qsClient.QueryServerClient;\n  readonly distributionManager: DistributionManager;\n  readonly databaseManager: DatabaseManager;\n  readonly databaseUI: DatabaseUI;\n  readonly dispose: () => void;\n}\n\n/**\n * Returns the CodeQLExtensionInterface, or an empty object if the interface is not\n * available after activation is complete. This will happen if there is no cli\n * installed when the extension starts. Downloading and installing the cli\n * will happen at a later time.\n *\n * @param ctx The extension context\n *\n * @returns CodeQLExtensionInterface\n */\nexport async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionInterface | Record<string, never>> {\n\n  void logger.log(`Starting ${extensionId} extension`);\n  if (extension === undefined) {\n    throw new Error(`Can't find extension ${extensionId}`);\n  }\n\n  const distributionConfigListener = new DistributionConfigListener();\n  await initializeLogging(ctx);\n  await initializeTelemetry(extension, ctx);\n  languageSupport.install();\n\n  const codelensProvider = new QuickEvalCodeLensProvider();\n  languages.registerCodeLensProvider({ scheme: 'file', language: 'ql' }, codelensProvider);\n\n  ctx.subscriptions.push(distributionConfigListener);\n  const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;\n  const distributionManager = new DistributionManager(distributionConfigListener, codeQlVersionRange, ctx);\n\n  const shouldUpdateOnNextActivationKey = 'shouldUpdateOnNextActivation';\n\n  registerErrorStubs([checkForUpdatesCommand], command => (async () => {\n    void showAndLogErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`);\n  }));\n\n  interface DistributionUpdateConfig {\n    isUserInitiated: boolean;\n    shouldDisplayMessageWhenNoUpdates: boolean;\n    allowAutoUpdating: boolean;\n  }\n\n  async function installOrUpdateDistributionWithProgressTitle(progressTitle: string, config: DistributionUpdateConfig): Promise<void> {\n    const minSecondsSinceLastUpdateCheck = config.isUserInitiated ? 0 : 86400;\n    const noUpdatesLoggingFunc = config.shouldDisplayMessageWhenNoUpdates ?\n      showAndLogInformationMessage : async (message: string) => void logger.log(message);\n    const result = await distributionManager.checkForUpdatesToExtensionManagedDistribution(minSecondsSinceLastUpdateCheck);\n\n    // We do want to auto update if there is no distribution at all\n    const allowAutoUpdating = config.allowAutoUpdating || !await distributionManager.hasDistribution();\n\n    switch (result.kind) {\n      case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult:\n        void logger.log('Didn\\'t perform CodeQL CLI update check since a check was already performed within the previous ' +\n          `${minSecondsSinceLastUpdateCheck} seconds.`);\n        break;\n      case DistributionUpdateCheckResultKind.AlreadyUpToDate:\n        await noUpdatesLoggingFunc('CodeQL CLI already up to date.');\n        break;\n      case DistributionUpdateCheckResultKind.InvalidLocation:\n        await noUpdatesLoggingFunc('CodeQL CLI is installed externally so could not be updated.');\n        break;\n      case DistributionUpdateCheckResultKind.UpdateAvailable:\n        if (beganMainExtensionActivation || !allowAutoUpdating) {\n          const updateAvailableMessage = `Version \"${result.updatedRelease.name}\" of the CodeQL CLI is now available. ` +\n            'Do you wish to upgrade?';\n          await ctx.globalState.update(shouldUpdateOnNextActivationKey, true);\n          if (await showInformationMessageWithAction(updateAvailableMessage, 'Restart and Upgrade')) {\n            await commands.executeCommand('workbench.action.reloadWindow');\n          }\n        } else {\n          const progressOptions: ProgressOptions = {\n            title: progressTitle,\n            location: ProgressLocation.Notification,\n          };\n\n          await withProgress(progressOptions, progress =>\n            distributionManager.installExtensionManagedDistributionRelease(result.updatedRelease, progress));\n\n          await ctx.globalState.update(shouldUpdateOnNextActivationKey, false);\n          void showAndLogInformationMessage(`CodeQL CLI updated to version \"${result.updatedRelease.name}\".`);\n        }\n        break;\n      default:\n        assertNever(result);\n    }\n  }\n\n  async function installOrUpdateDistribution(config: DistributionUpdateConfig): Promise<void> {\n    if (isInstallingOrUpdatingDistribution) {\n      throw new Error('Already installing or updating CodeQL CLI');\n    }\n    isInstallingOrUpdatingDistribution = true;\n    const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined;\n    const willUpdateCodeQl = ctx.globalState.get(shouldUpdateOnNextActivationKey);\n    const messageText = willUpdateCodeQl\n      ? 'Updating CodeQL CLI'\n      : codeQlInstalled\n        ? 'Checking for updates to CodeQL CLI'\n        : 'Installing CodeQL CLI';\n\n    try {\n      await installOrUpdateDistributionWithProgressTitle(messageText, config);\n    } catch (e) {\n      // Don't rethrow the exception, because if the config is changed, we want to be able to retry installing\n      // or updating the distribution.\n      const alertFunction = (codeQlInstalled && !config.isUserInitiated) ?\n        showAndLogWarningMessage : showAndLogErrorMessage;\n      const taskDescription = (willUpdateCodeQl ? 'update' :\n        codeQlInstalled ? 'check for updates to' : 'install') + ' CodeQL CLI';\n\n      if (e instanceof GithubRateLimitedError) {\n        void alertFunction(`Rate limited while trying to ${taskDescription}. Please try again after ` +\n          `your rate limit window resets at ${e.rateLimitResetDate.toLocaleString(env.language)}.`);\n      } else if (e instanceof GithubApiError) {\n        void alertFunction(`Encountered GitHub API error while trying to ${taskDescription}. ` + e);\n      }\n      void alertFunction(`Unable to ${taskDescription}. ` + e);\n    } finally {\n      isInstallingOrUpdatingDistribution = false;\n    }\n  }\n\n  async function getDistributionDisplayingDistributionWarnings(): Promise<FindDistributionResult> {\n    const result = await distributionManager.getDistribution();\n    switch (result.kind) {\n      case FindDistributionResultKind.CompatibleDistribution:\n        void logger.log(`Found compatible version of CodeQL CLI (version ${result.version.raw})`);\n        break;\n      case FindDistributionResultKind.IncompatibleDistribution: {\n        const fixGuidanceMessage = (() => {\n          switch (result.distribution.kind) {\n            case DistributionKind.ExtensionManaged:\n              return 'Please update the CodeQL CLI by running the \"CodeQL: Check for CLI Updates\" command.';\n            case DistributionKind.CustomPathConfig:\n              return `Please update the \\\"CodeQL CLI Executable Path\\\" setting to point to a CLI in the version range ${codeQlVersionRange}.`;\n            case DistributionKind.PathEnvironmentVariable:\n              return `Please update the CodeQL CLI on your PATH to a version compatible with ${codeQlVersionRange}, or ` +\n                `set the \\\"CodeQL CLI Executable Path\\\" setting to the path of a CLI version compatible with ${codeQlVersionRange}.`;\n          }\n        })();\n\n        void showAndLogWarningMessage(\n          `The current version of the CodeQL CLI (${result.version.raw}) ` +\n          `is incompatible with this extension. ${fixGuidanceMessage}`\n        );\n        break;\n      }\n      case FindDistributionResultKind.UnknownCompatibilityDistribution:\n        void showAndLogWarningMessage(\n          'Compatibility with the configured CodeQL CLI could not be determined. ' +\n          'You may experience problems using the extension.'\n        );\n        break;\n      case FindDistributionResultKind.NoDistribution:\n        void showAndLogErrorMessage('The CodeQL CLI could not be found.');\n        break;\n      default:\n        assertNever(result);\n    }\n    return result;\n  }\n\n  async function installOrUpdateThenTryActivate(\n    config: DistributionUpdateConfig\n  ): Promise<CodeQLExtensionInterface | Record<string, never>> {\n\n    await installOrUpdateDistribution(config);\n\n    // Display the warnings even if the extension has already activated.\n    const distributionResult = await getDistributionDisplayingDistributionWarnings();\n    let extensionInterface: CodeQLExtensionInterface | Record<string, never> = {};\n    if (!beganMainExtensionActivation && distributionResult.kind !== FindDistributionResultKind.NoDistribution) {\n      extensionInterface = await activateWithInstalledDistribution(\n        ctx,\n        distributionManager,\n        distributionConfigListener\n      );\n\n    } else if (distributionResult.kind === FindDistributionResultKind.NoDistribution) {\n      registerErrorStubs([checkForUpdatesCommand], command => async () => {\n        const installActionName = 'Install CodeQL CLI';\n        const chosenAction = await void showAndLogErrorMessage(`Can't execute ${command}: missing CodeQL CLI.`, {\n          items: [installActionName]\n        });\n        if (chosenAction === installActionName) {\n          await installOrUpdateThenTryActivate({\n            isUserInitiated: true,\n            shouldDisplayMessageWhenNoUpdates: false,\n            allowAutoUpdating: true\n          });\n        }\n      });\n    }\n    return extensionInterface;\n  }\n\n  ctx.subscriptions.push(distributionConfigListener.onDidChangeConfiguration(() => installOrUpdateThenTryActivate({\n    isUserInitiated: true,\n    shouldDisplayMessageWhenNoUpdates: false,\n    allowAutoUpdating: true\n  })));\n  ctx.subscriptions.push(commandRunner(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({\n    isUserInitiated: true,\n    shouldDisplayMessageWhenNoUpdates: true,\n    allowAutoUpdating: true\n  })));\n\n  return await installOrUpdateThenTryActivate({\n    isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey),\n    shouldDisplayMessageWhenNoUpdates: false,\n\n    // only auto update on startup if the user has previously requested an update\n    // otherwise, ask user to accept the update\n    allowAutoUpdating: !!ctx.globalState.get(shouldUpdateOnNextActivationKey)\n  });\n}\n\nasync function activateWithInstalledDistribution(\n  ctx: ExtensionContext,\n  distributionManager: DistributionManager,\n  distributionConfigListener: DistributionConfigListener\n): Promise<CodeQLExtensionInterface> {\n  beganMainExtensionActivation = true;\n  // Remove any error stubs command handlers left over from first part\n  // of activation.\n  errorStubs.forEach((stub) => stub.dispose());\n\n  void logger.log('Initializing configuration listener...');\n  const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(\n    distributionManager\n  );\n  ctx.subscriptions.push(qlConfigurationListener);\n\n  void logger.log('Initializing CodeQL cli server...');\n  const cliServer = new CodeQLCliServer(\n    distributionManager,\n    new CliConfigListener(),\n    logger\n  );\n  ctx.subscriptions.push(cliServer);\n\n  const statusBar = new CodeQlStatusBarHandler(cliServer, distributionConfigListener);\n  ctx.subscriptions.push(statusBar);\n\n  void logger.log('Initializing query server client.');\n  const qs = new qsClient.QueryServerClient(\n    qlConfigurationListener,\n    cliServer,\n    {\n      logger: queryServerLogger,\n      contextStoragePath: getContextStoragePath(ctx),\n    },\n    (task) =>\n      Window.withProgress(\n        { title: 'CodeQL query server', location: ProgressLocation.Window },\n        task\n      )\n  );\n  ctx.subscriptions.push(qs);\n  await qs.startQueryServer();\n\n  void logger.log('Initializing database manager.');\n  const dbm = new DatabaseManager(ctx, qs, cliServer, logger);\n  ctx.subscriptions.push(dbm);\n  void logger.log('Initializing database panel.');\n  const databaseUI = new DatabaseUI(\n    dbm,\n    qs,\n    getContextStoragePath(ctx),\n    ctx.extensionPath\n  );\n  databaseUI.init();\n  ctx.subscriptions.push(databaseUI);\n\n  void logger.log('Initializing query history manager.');\n  const queryHistoryConfigurationListener = new QueryHistoryConfigListener();\n  ctx.subscriptions.push(queryHistoryConfigurationListener);\n  const showResults = async (item: CompletedLocalQueryInfo) =>\n    showResultsForCompletedQuery(item, WebviewReveal.Forced);\n  const queryStorageDir = path.join(ctx.globalStorageUri.fsPath, 'queries');\n  await fs.ensureDir(queryStorageDir);\n\n  void logger.log('Initializing query history.');\n  const qhm = new QueryHistoryManager(\n    qs,\n    dbm,\n    queryStorageDir,\n    ctx,\n    queryHistoryConfigurationListener,\n    async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>\n      showResultsForComparison(from, to),\n  );\n\n  qhm.onWillOpenQueryItem(async item => {\n    if (item.t === 'local' && item.completed) {\n      await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.Forced);\n    }\n  });\n\n  ctx.subscriptions.push(qhm);\n  void logger.log('Initializing results panel interface.');\n  const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);\n  ctx.subscriptions.push(intm);\n\n  void logger.log('Initializing compare panel interface.');\n  const cmpm = new CompareInterfaceManager(\n    ctx,\n    dbm,\n    cliServer,\n    queryServerLogger,\n    showResults\n  );\n  ctx.subscriptions.push(cmpm);\n\n  void logger.log('Initializing source archive filesystem provider.');\n  archiveFilesystemProvider.activate(ctx);\n\n  async function showResultsForComparison(\n    from: CompletedLocalQueryInfo,\n    to: CompletedLocalQueryInfo\n  ): Promise<void> {\n    try {\n      await cmpm.showResults(from, to);\n    } catch (e) {\n      void showAndLogErrorMessage(e.message);\n    }\n  }\n\n  async function showResultsForCompletedQuery(\n    query: CompletedLocalQueryInfo,\n    forceReveal: WebviewReveal\n  ): Promise<void> {\n    await intm.showResults(query, forceReveal, false);\n  }\n\n  async function compileAndRunQuery(\n    quickEval: boolean,\n    selectedQuery: Uri | undefined,\n    progress: ProgressCallback,\n    token: CancellationToken,\n    databaseItem: DatabaseItem | undefined,\n    range?: Range\n  ): Promise<void> {\n    if (qs !== undefined) {\n      // If no databaseItem is specified, use the database currently selected in the Databases UI\n      databaseItem = databaseItem || await databaseUI.getDatabaseItem(progress, token);\n      if (databaseItem === undefined) {\n        throw new Error('Can\\'t run query without a selected database');\n      }\n      const databaseInfo = {\n        name: databaseItem.name,\n        databaseUri: databaseItem.databaseUri.toString(),\n      };\n\n      // handle cancellation from the history view.\n      const source = new CancellationTokenSource();\n      token.onCancellationRequested(() => source.cancel());\n\n      const initialInfo = await createInitialQueryInfo(selectedQuery, databaseInfo, quickEval, range);\n      const item = new LocalQueryInfo(initialInfo, queryHistoryConfigurationListener, source);\n      qhm.addQuery(item);\n      try {\n        const completedQueryInfo = await compileAndRunQueryAgainstDatabase(\n          cliServer,\n          qs,\n          databaseItem,\n          initialInfo,\n          queryStorageDir,\n          progress,\n          source.token,\n        );\n        item.completeThisQuery(completedQueryInfo);\n        await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.NotForced);\n        // Note we must update the query history view after showing results as the\n        // display and sorting might depend on the number of results\n      } catch (e) {\n        e.message = `Error running query: ${e.message}`;\n        item.failureReason = e.message;\n        throw e;\n      } finally {\n        await qhm.refreshTreeView();\n        source.dispose();\n      }\n    }\n  }\n\n  const qhelpTmpDir = tmp.dirSync({ prefix: 'qhelp_', keep: false, unsafeCleanup: true });\n  ctx.subscriptions.push({ dispose: qhelpTmpDir.removeCallback });\n\n  async function previewQueryHelp(\n    selectedQuery: Uri\n  ): Promise<void> {\n    // selectedQuery is unpopulated when executing through the command palette\n    const pathToQhelp = selectedQuery ? selectedQuery.fsPath : window.activeTextEditor?.document.uri.fsPath;\n    if (pathToQhelp) {\n      // Create temporary directory\n      const relativePathToMd = path.basename(pathToQhelp, '.qhelp') + '.md';\n      const absolutePathToMd = path.join(qhelpTmpDir.name, relativePathToMd);\n      const uri = Uri.file(absolutePathToMd);\n      try {\n        await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd);\n        await commands.executeCommand('markdown.showPreviewToSide', uri);\n      } catch (err) {\n        const errorMessage = err.message.includes('Generating qhelp in markdown') ? (\n          `Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`\n        ) : `Could not open a preview of the generated file (${absolutePathToMd}).`;\n        void showAndLogErrorMessage(errorMessage, { fullMessage: `${errorMessage}\\n${err}` });\n      }\n    }\n\n  }\n\n  async function openReferencedFile(\n    selectedQuery: Uri\n  ): Promise<void> {\n    // If no file is selected, the path of the file in the editor is selected\n    const path = selectedQuery?.fsPath || window.activeTextEditor?.document.uri.fsPath;\n    if (qs !== undefined && path) {\n      if (await cliServer.cliConstraints.supportsResolveQlref()) {\n        const resolved = await cliServer.resolveQlref(path);\n        const uri = Uri.file(resolved.resolvedPath);\n        await window.showTextDocument(uri, { preview: false });\n      } else {\n        void showAndLogErrorMessage(\n          'Jumping from a .qlref file to the .ql file it references is not '\n          + 'supported with the CLI version you are running.\\n'\n          + `Please upgrade your CLI to version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF\n          } or later to use this feature.`);\n      }\n    }\n  }\n\n  ctx.subscriptions.push(tmpDirDisposal);\n\n  void logger.log('Initializing CodeQL language server.');\n  const client = new LanguageClient(\n    'CodeQL Language Server',\n    () => spawnIdeServer(qlConfigurationListener),\n    {\n      documentSelector: [\n        { language: 'ql', scheme: 'file' },\n        { language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' },\n      ],\n      synchronize: {\n        configurationSection: 'codeQL',\n      },\n      // Ensure that language server exceptions are logged to the same channel as its output.\n      outputChannel: ideServerLogger.outputChannel,\n    },\n    true\n  );\n\n  void logger.log('Initializing QLTest interface.');\n  const testExplorerExtension = extensions.getExtension<TestHub>(\n    testExplorerExtensionId\n  );\n  if (testExplorerExtension) {\n    const testHub = testExplorerExtension.exports;\n    const testAdapterFactory = new QLTestAdapterFactory(testHub, cliServer, dbm);\n    ctx.subscriptions.push(testAdapterFactory);\n\n    const testUIService = new TestUIService(testHub);\n    ctx.subscriptions.push(testUIService);\n  }\n\n  void logger.log('Registering top-level command palette commands.');\n  ctx.subscriptions.push(\n    commandRunnerWithProgress(\n      'codeQL.runQuery',\n      async (\n        progress: ProgressCallback,\n        token: CancellationToken,\n        uri: Uri | undefined\n      ) => await compileAndRunQuery(false, uri, progress, token, undefined),\n      {\n        title: 'Running query',\n        cancellable: true\n      },\n\n      // Open the query server logger on error since that's usually where the interesting errors appear.\n      queryServerLogger\n    )\n  );\n  interface DatabaseQuickPickItem extends QuickPickItem {\n    databaseItem: DatabaseItem;\n  }\n  ctx.subscriptions.push(\n    commandRunnerWithProgress(\n      'codeQL.runQueryOnMultipleDatabases',\n      async (\n        progress: ProgressCallback,\n        token: CancellationToken,\n        uri: Uri | undefined\n      ) => {\n        let filteredDBs = dbm.databaseItems;\n        if (filteredDBs.length === 0) {\n          void showAndLogErrorMessage('No databases found. Please add a suitable database to your workspace.');\n          return;\n        }\n        // If possible, only show databases with the right language (otherwise show all databases).\n        const queryLanguage = await findLanguage(cliServer, uri);\n        if (queryLanguage) {\n          filteredDBs = dbm.databaseItems.filter(db => db.language === queryLanguage);\n          if (filteredDBs.length === 0) {\n            void showAndLogErrorMessage(`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`);\n            return;\n          }\n        }\n        const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(dbItem => (\n          {\n            databaseItem: dbItem,\n            label: dbItem.name,\n            description: dbItem.language,\n          }\n        ));\n        /**\n         * Databases that were selected in the quick pick menu.\n         */\n        const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(\n          quickPickItems,\n          { canPickMany: true, ignoreFocusOut: true }\n        );\n        if (quickpick !== undefined) {\n          // Collect all skipped databases and display them at the end (instead of popping up individual errors)\n          const skippedDatabases = [];\n          const errors = [];\n          for (const item of quickpick) {\n            try {\n              await compileAndRunQuery(false, uri, progress, token, item.databaseItem);\n            } catch (error) {\n              skippedDatabases.push(item.label);\n              errors.push(error.message);\n            }\n          }\n          if (skippedDatabases.length > 0) {\n            void logger.log(`Errors:\\n${errors.join('\\n')}`);\n            void showAndLogWarningMessage(\n              `The following databases were skipped:\\n${skippedDatabases.join('\\n')}.\\nFor details about the errors, see the logs.`\n            );\n          }\n        } else {\n          void showAndLogErrorMessage('No databases selected.');\n        }\n      },\n      {\n        title: 'Running query on selected databases',\n        cancellable: true\n      }\n    )\n  );\n  ctx.subscriptions.push(\n    commandRunnerWithProgress(\n      'codeQL.runQueries',\n      async (\n        progress: ProgressCallback,\n        token: CancellationToken,\n        _: Uri | undefined,\n        multi: Uri[]\n      ) => {\n        const maxQueryCount = MAX_QUERIES.getValue() as number;\n        const [files, dirFound] = await gatherQlFiles(multi.map(uri => uri.fsPath));\n        if (files.length > maxQueryCount) {\n          throw new Error(`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`);\n        }\n        // warn user and display selected files when a directory is selected because some ql\n        // files may be hidden from the user.\n        if (dirFound) {\n          const fileString = files.map(file => path.basename(file)).join(', ');\n          const res = await showBinaryChoiceDialog(\n            `You are about to run ${files.length} queries: ${fileString} Do you want to continue?`\n          );\n          if (!res) {\n            return;\n          }\n        }\n        const queryUris = files.map(path => Uri.parse(`file:${path}`, true));\n\n        // Use a wrapped progress so that messages appear with the queries remaining in it.\n        let queriesRemaining = queryUris.length;\n        function wrappedProgress(update: ProgressUpdate) {\n          const message = queriesRemaining > 1\n            ? `${queriesRemaining} remaining. ${update.message}`\n            : update.message;\n          progress({\n            ...update,\n            message\n          });\n        }\n\n        if (queryUris.length > 1) {\n          // Try to upgrade the current database before running any queries\n          // so that the user isn't confronted with multiple upgrade\n          // requests for each query to run.\n          // Only do it if running multiple queries since this check is\n          // performed on each query run anyway.\n          await databaseUI.tryUpgradeCurrentDatabase(progress, token);\n        }\n\n        wrappedProgress({\n          maxStep: queryUris.length,\n          step: queryUris.length - queriesRemaining,\n          message: ''\n        });\n\n        await Promise.all(queryUris.map(async uri =>\n          compileAndRunQuery(false, uri, wrappedProgress, token, undefined)\n            .then(() => queriesRemaining--)\n        ));\n      },\n      {\n        title: 'Running queries',\n        cancellable: true\n      },\n\n      // Open the query server logger on error since that's usually where the interesting errors appear.\n      queryServerLogger\n    )\n  );\n  ctx.subscriptions.push(\n    commandRunnerWithProgress(\n      'codeQL.quickEval',\n      async (\n        progress: ProgressCallback,\n        token: CancellationToken,\n        uri: Uri | undefined\n      ) => await compileAndRunQuery(true, uri, progress, token, undefined),\n      {\n        title: 'Running query',\n        cancellable: true\n      },\n      // Open the query server logger on error since that's usually where the interesting errors appear.\n      queryServerLogger\n    )\n  );\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress(\n      'codeQL.codeLensQuickEval',\n      async (\n        progress: ProgressCallback,\n        token: CancellationToken,\n        uri: Uri,\n        range: Range\n      ) => await compileAndRunQuery(true, uri, progress, token, undefined, range),\n      {\n        title: 'Running query',\n        cancellable: true\n      },\n\n      // Open the query server logger on error since that's usually where the interesting errors appear.\n      queryServerLogger\n    )\n  );\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.quickQuery', async (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) =>\n      displayQuickQuery(ctx, cliServer, databaseUI, progress, token),\n      {\n        title: 'Run Quick Query'\n      },\n\n      // Open the query server logger on error since that's usually where the interesting errors appear.\n      queryServerLogger\n    )\n  );\n\n  void logger.log('Initializing remote queries interface.');\n  const rqm = new RemoteQueriesManager(ctx, cliServer, qhm, queryStorageDir, logger);\n  ctx.subscriptions.push(rqm);\n\n  // wait until after the remote queries manager is initialized to read the query history\n  // since the rqm is notified of queries being added.\n  await qhm.readQueryHistory();\n\n\n  registerRemoteQueryTextProvider();\n\n  // The \"runRemoteQuery\" command is internal-only.\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.runRemoteQuery', async (\n      progress: ProgressCallback,\n      token: CancellationToken,\n      uri: Uri | undefined\n    ) => {\n      if (isCanary()) {\n        progress({\n          maxStep: 5,\n          step: 0,\n          message: 'Getting credentials'\n        });\n        await rqm.runRemoteQuery(\n          uri || window.activeTextEditor?.document.uri,\n          progress,\n          token\n        );\n      } else {\n        throw new Error('Remote queries require the CodeQL Canary version to run.');\n      }\n    }, {\n      title: 'Run Remote Query',\n      cancellable: true\n    })\n  );\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.monitorRemoteQuery', async (\n      queryItem: RemoteQueryHistoryItem,\n      token: CancellationToken) => {\n      await rqm.monitorRemoteQuery(queryItem, token);\n    }));\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.autoDownloadRemoteQueryResults', async (\n      queryResult: RemoteQueryResult,\n      token: CancellationToken) => {\n      await rqm.autoDownloadRemoteQueryResults(queryResult, token);\n    }));\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.showFakeRemoteQueryResults', async () => {\n      const analysisResultsManager = new AnalysesResultsManager(ctx, queryStorageDir, logger);\n      const rqim = new RemoteQueriesInterfaceManager(ctx, logger, analysisResultsManager);\n      await rqim.showResults(sampleData.sampleRemoteQuery, sampleData.sampleRemoteQueryResult);\n\n      await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage1);\n      await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage2);\n      await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage3);\n    }));\n\n  ctx.subscriptions.push(\n    commandRunner(\n      'codeQL.openReferencedFile',\n      openReferencedFile\n    )\n  );\n\n  ctx.subscriptions.push(\n    commandRunner(\n      'codeQL.previewQueryHelp',\n      previewQueryHelp\n    )\n  );\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.restartQueryServer', async (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) => {\n      await qs.restartQueryServer(progress, token);\n      void showAndLogInformationMessage('CodeQL Query Server restarted.', {\n        outputLogger: queryServerLogger,\n      });\n    }, {\n      title: 'Restarting Query Server'\n    })\n  );\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.chooseDatabaseFolder', (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) =>\n      databaseUI.handleChooseDatabaseFolder(progress, token), {\n      title: 'Choose a Database from a Folder'\n    })\n  );\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.chooseDatabaseArchive', (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) =>\n      databaseUI.handleChooseDatabaseArchive(progress, token), {\n      title: 'Choose a Database from an Archive'\n    })\n  );\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.chooseDatabaseLgtm', (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) =>\n      databaseUI.handleChooseDatabaseLgtm(progress, token),\n      {\n        title: 'Adding database from LGTM',\n      })\n  );\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.chooseDatabaseInternet', (\n      progress: ProgressCallback,\n      token: CancellationToken\n    ) =>\n      databaseUI.handleChooseDatabaseInternet(progress, token),\n\n      {\n        title: 'Adding database from URL',\n      })\n  );\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.openDocumentation', async () =>\n      env.openExternal(Uri.parse('https://codeql.github.com/docs/'))));\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.copyVersion', async () => {\n      const text = `CodeQL extension version: ${extension?.packageJSON.version} \\nCodeQL CLI version: ${await getCliVersion()} \\nPlatform: ${os.platform()} ${os.arch()}`;\n      await env.clipboard.writeText(text);\n      void showAndLogInformationMessage(text);\n    }));\n\n  const getCliVersion = async () => {\n    try {\n      return await cliServer.getVersion();\n    } catch {\n      return '<missing>';\n    }\n  };\n\n  // The \"authenticateToGitHub\" command is internal-only.\n  ctx.subscriptions.push(\n    commandRunner('codeQL.authenticateToGitHub', async () => {\n      if (isCanary()) {\n        /**\n         * Credentials for authenticating to GitHub.\n         * These are used when making API calls.\n         */\n        const credentials = await Credentials.initialize(ctx);\n        const octokit = await credentials.getOctokit();\n        const userInfo = await octokit.users.getAuthenticated();\n        void showAndLogInformationMessage(`Authenticated to GitHub as user: ${userInfo.data.login}`);\n      }\n    }));\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.installPackDependencies', async (\n      progress: ProgressCallback\n    ) =>\n      await handleInstallPackDependencies(cliServer, progress),\n      {\n        title: 'Installing pack dependencies',\n      }\n    ));\n\n  ctx.subscriptions.push(\n    commandRunnerWithProgress('codeQL.downloadPacks', async (\n      progress: ProgressCallback\n    ) =>\n      await handleDownloadPacks(cliServer, progress),\n      {\n        title: 'Downloading packs',\n      }\n    ));\n\n  ctx.subscriptions.push(\n    commandRunner('codeQL.showLogs', async () => {\n      logger.show();\n    })\n  );\n\n  void logger.log('Starting language server.');\n  ctx.subscriptions.push(client.start());\n\n  // Jump-to-definition and find-references\n  void logger.log('Registering jump-to-definition handlers.');\n\n  // Store contextual queries in a temporary folder so that they are removed\n  // when the application closes. There is no need for the user to interact with them.\n  const contextualQueryStorageDir = path.join(tmpDir.name, 'contextual-query-storage');\n  await fs.ensureDir(contextualQueryStorageDir);\n  languages.registerDefinitionProvider(\n    { scheme: archiveFilesystemProvider.zipArchiveScheme },\n    new TemplateQueryDefinitionProvider(cliServer, qs, dbm, contextualQueryStorageDir)\n  );\n\n  languages.registerReferenceProvider(\n    { scheme: archiveFilesystemProvider.zipArchiveScheme },\n    new TemplateQueryReferenceProvider(cliServer, qs, dbm, contextualQueryStorageDir)\n  );\n\n  const astViewer = new AstViewer();\n  const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm, contextualQueryStorageDir);\n\n  ctx.subscriptions.push(astViewer);\n  ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (\n    progress: ProgressCallback,\n    token: CancellationToken,\n    selectedFile: Uri\n  ) => {\n    const ast = await templateProvider.provideAst(\n      progress,\n      token,\n      selectedFile ?? window.activeTextEditor?.document.uri,\n    );\n    if (ast) {\n      astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);\n    }\n  }, {\n    cancellable: true,\n    title: 'Calculate AST'\n  }));\n\n  await commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');\n\n  void logger.log('Successfully finished extension initialization.');\n\n  return {\n    ctx,\n    cliServer,\n    qs,\n    distributionManager,\n    databaseManager: dbm,\n    databaseUI,\n    dispose: () => {\n      ctx.subscriptions.forEach(d => d.dispose());\n    }\n  };\n}\n\nfunction getContextStoragePath(ctx: ExtensionContext) {\n  return ctx.storageUri?.fsPath || ctx.globalStorageUri.fsPath;\n}\n\nasync function initializeLogging(ctx: ExtensionContext): Promise<void> {\n  const storagePath = getContextStoragePath(ctx);\n  await logger.setLogStoragePath(storagePath, false);\n  await ideServerLogger.setLogStoragePath(storagePath, false);\n  ctx.subscriptions.push(logger);\n  ctx.subscriptions.push(queryServerLogger);\n  ctx.subscriptions.push(ideServerLogger);\n}\n\nconst checkForUpdatesCommand = 'codeQL.checkForUpdatesToCLI';\n\n/**\n * This text provider lets us open readonly files in the editor.\n *\n * TODO: Consolidate this with the 'codeql' text provider in query-history.ts.\n */\nfunction registerRemoteQueryTextProvider() {\n  workspace.registerTextDocumentContentProvider('remote-query', {\n    provideTextDocumentContent(\n      uri: Uri\n    ): ProviderResult<string> {\n      const params = new URLSearchParams(uri.query);\n\n      return params.get('queryText');\n    },\n  });\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/logging.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 2
      },
      "contents" : {
        "text" : "import { window as Window, OutputChannel, Progress, Disposable } from 'vscode';\nimport { DisposableObject } from './pure/disposable-object';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\n\ninterface LogOptions {\n  /** If false, don't output a trailing newline for the log entry. Default true. */\n  trailingNewline?: boolean;\n\n  /** If specified, add this log entry to the log file at the specified location. */\n  additionalLogLocation?: string;\n}\n\nexport interface Logger {\n  /** Writes the given log message, optionally followed by a newline. */\n  log(message: string, options?: LogOptions): Promise<void>;\n  /**\n   * Reveal this channel in the UI.\n   *\n   * @param preserveFocus When `true` the channel will not take focus.\n   */\n  show(preserveFocus?: boolean): void;\n\n  /**\n   * Remove the log at the specified location\n   * @param location log to remove\n   */\n  removeAdditionalLogLocation(location: string | undefined): void;\n\n  /**\n   * The base location where all side log files are stored.\n   */\n  getBaseLocation(): string | undefined;\n\n  /**\n   * Sets the location where logs are stored.\n   * @param storagePath The path where logs are stored.\n   * @param isCustomLogDirectory Whether the logs are stored in a custom, user-specified directory.\n   */\n  setLogStoragePath(storagePath: string, isCustomLogDirectory: boolean): Promise<void>;\n}\n\nexport type ProgressReporter = Progress<{ message: string }>;\n\n/** A logger that writes messages to an output channel in the Output tab. */\nexport class OutputChannelLogger extends DisposableObject implements Logger {\n  public readonly outputChannel: OutputChannel;\n  private readonly additionalLocations = new Map<string, AdditionalLogLocation>();\n  private additionalLogLocationPath: string | undefined;\n  isCustomLogDirectory: boolean;\n\n  constructor(private title: string) {\n    super();\n    this.outputChannel = Window.createOutputChannel(title);\n    this.push(this.outputChannel);\n    this.isCustomLogDirectory = false;\n  }\n\n  async setLogStoragePath(storagePath: string, isCustomLogDirectory: boolean): Promise<void> {\n    this.additionalLogLocationPath = path.join(storagePath, this.title);\n\n    this.isCustomLogDirectory = isCustomLogDirectory;\n\n    if (!this.isCustomLogDirectory) {\n      // clear out any old state from previous runs\n      await fs.remove(this.additionalLogLocationPath);\n    }\n  }\n\n  /**\n   * This function is asynchronous and will only resolve once the message is written\n   * to the side log (if required). It is not necessary to await the results of this\n   * function if you don't need to guarantee that the log writing is complete before\n   * continuing.\n   */\n  async log(message: string, options = {} as LogOptions): Promise<void> {\n    try {\n      if (options.trailingNewline === undefined) {\n        options.trailingNewline = true;\n      }\n      if (options.trailingNewline) {\n        this.outputChannel.appendLine(message);\n      } else {\n        this.outputChannel.append(message);\n      }\n\n      if (this.additionalLogLocationPath && options.additionalLogLocation) {\n        const logPath = path.join(this.additionalLogLocationPath, options.additionalLogLocation);\n        let additional = this.additionalLocations.get(logPath);\n        if (!additional) {\n          const msg = `| Log being saved to ${logPath} |`;\n          const separator = new Array(msg.length).fill('-').join('');\n          this.outputChannel.appendLine(separator);\n          this.outputChannel.appendLine(msg);\n          this.outputChannel.appendLine(separator);\n          additional = new AdditionalLogLocation(logPath, !this.isCustomLogDirectory);\n          this.additionalLocations.set(logPath, additional);\n          this.track(additional);\n        }\n\n        await additional.log(message, options);\n      }\n    } catch (e) {\n      if (e instanceof Error && e.message === 'Channel has been closed') {\n        // Output channel is closed logging to console instead\n        console.log('Output channel is closed logging to console instead:', message);\n      } else {\n        throw e;\n      }\n    }\n  }\n\n  show(preserveFocus?: boolean): void {\n    this.outputChannel.show(preserveFocus);\n  }\n\n  removeAdditionalLogLocation(location: string | undefined): void {\n    if (this.additionalLogLocationPath && location) {\n      const logPath = location.startsWith(this.additionalLogLocationPath)\n        ? location\n        : path.join(this.additionalLogLocationPath, location);\n      const additional = this.additionalLocations.get(logPath);\n      if (additional) {\n        this.disposeAndStopTracking(additional);\n        this.additionalLocations.delete(logPath);\n      }\n    }\n  }\n\n  getBaseLocation() {\n    return this.additionalLogLocationPath;\n  }\n}\n\nclass AdditionalLogLocation extends Disposable {\n  constructor(private location: string, private shouldDeleteLogs: boolean) {\n    super(() => { /**/ });\n  }\n\n  async log(message: string, options = {} as LogOptions): Promise<void> {\n    if (options.trailingNewline === undefined) {\n      options.trailingNewline = true;\n    }\n    await fs.ensureFile(this.location);\n\n    await fs.appendFile(this.location, message + (options.trailingNewline ? '\\n' : ''), {\n      encoding: 'utf8'\n    });\n  }\n\n  async dispose(): Promise<void> {\n    if (this.shouldDeleteLogs) {\n      await fs.remove(this.location);\n    }\n  }\n}\n\n/** The global logger for the extension. */\nexport const logger = new OutputChannelLogger('CodeQL Extension Log');\n\n/** The logger for messages from the query server. */\nexport const queryServerLogger = new OutputChannelLogger('CodeQL Query Server');\n\n/** The logger for messages from the language server. */\nexport const ideServerLogger = new OutputChannelLogger(\n  'CodeQL Language Server'\n);\n\n/** The logger for messages from tests. */\nexport const testLogger = new OutputChannelLogger('CodeQL Tests');\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/pure/disposable-object.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 3
      },
      "contents" : {
        "text" : "\n// Avoid explicitly referencing Disposable type in vscode.\n// This file cannot have dependencies on the vscode API.\ninterface Disposable {\n  dispose(): any;\n}\n\nexport type DisposeHandler = (disposable: Disposable) => void;\n\n/**\n * Base class to make it easier to implement a `Disposable` that owns other disposable object.\n */\nexport abstract class DisposableObject implements Disposable {\n  private disposables: Disposable[] = [];\n  private tracked?: Set<Disposable> = undefined;\n\n  /**\n   * Adds `obj` to a list of objects to dispose when `this` is disposed. Objects added by `push` are\n   * disposed in reverse order of being added.\n   * @param obj The object to take ownership of.\n   */\n  protected push<T extends Disposable>(obj: T): T {\n    if (obj !== undefined) {\n      this.disposables.push(obj);\n    }\n    return obj;\n  }\n\n  /**\n   * Adds `obj` to a set of objects to dispose when `this` is disposed. Objects added by\n   * `track` are disposed in an unspecified order.\n   * @param obj The object to track.\n   */\n  protected track<T extends Disposable>(obj: T): T {\n    if (obj !== undefined) {\n      if (this.tracked === undefined) {\n        this.tracked = new Set<Disposable>();\n      }\n      this.tracked.add(obj);\n    }\n    return obj;\n  }\n\n  /**\n   * Removes `obj`, which must have been previously added by `track`, from the set of objects to\n   * dispose when `this` is disposed. `obj` itself is disposed.\n   * @param obj The object to stop tracking.\n   */\n  protected disposeAndStopTracking(obj: Disposable): void {\n    if (obj && this.tracked) {\n      this.tracked.delete(obj);\n      obj.dispose();\n    }\n  }\n\n  /**\n   * Dispose this object and all contained objects\n   *\n   * @param disposeHandler An optional dispose handler that gets\n   *      passed each element to dispose. The dispose handler\n   *      can choose how (and if) to dispose the object. The\n   *      primary usage is for tests that should not dispose\n   *      all items of a disposable.\n   */\n  public dispose(disposeHandler?: DisposeHandler) {\n    if (this.tracked !== undefined) {\n      for (const trackedObject of this.tracked.values()) {\n        if (disposeHandler) {\n          disposeHandler(trackedObject);\n        } else {\n          trackedObject.dispose();\n        }\n      }\n      this.tracked = undefined;\n    }\n    while (this.disposables.length > 0) {\n      const disposable = this.disposables.pop()!;\n      if (disposeHandler) {\n        disposeHandler(disposable);\n      } else {\n        disposable.dispose();\n      }\n    }\n  }\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/query-results.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 4
      },
      "contents" : {
        "text" : "import { CancellationTokenSource, env } from 'vscode';\n\nimport { QueryWithResults, QueryEvaluationInfo } from './run-queries';\nimport * as messages from './pure/messages';\nimport * as cli from './cli';\nimport * as sarif from 'sarif';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport {\n  RawResultsSortState,\n  SortedResultSetInfo,\n  QueryMetadata,\n  InterpretedResultsSortState,\n  ResultsPaths\n} from './pure/interface-types';\nimport { QueryHistoryConfig } from './config';\nimport { DatabaseInfo } from './pure/interface-types';\nimport { QueryStatus } from './query-status';\nimport { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';\n\n/**\n * query-results.ts\n * ----------------\n *\n * A collection of classes and functions that collectively\n * manage query results.\n */\n\n/**\n * A description of the information about a query\n * that is available before results are populated.\n */\nexport interface InitialQueryInfo {\n  userSpecifiedLabel?: string; // if missing, use a default label\n  readonly queryText: string; // text of the selected file, or the selected text when doing quick eval\n  readonly isQuickQuery: boolean;\n  readonly isQuickEval: boolean;\n  readonly quickEvalPosition?: messages.Position;\n  readonly queryPath: string;\n  readonly databaseInfo: DatabaseInfo\n  readonly start: Date;\n  readonly id: string; // unique id for this query.\n}\n\nexport class CompletedQueryInfo implements QueryWithResults {\n  readonly query: QueryEvaluationInfo;\n  readonly result: messages.EvaluationResult;\n  readonly logFileLocation?: string;\n  resultCount: number;\n\n  /**\n   * This dispose method is called when the query is removed from the history view.\n   */\n  dispose: () => void;\n\n  /**\n   * Map from result set name to SortedResultSetInfo.\n   */\n  sortedResultsInfo: Record<string, SortedResultSetInfo>;\n\n  /**\n   * How we're currently sorting alerts. This is not mere interface\n   * state due to truncation; on re-sort, we want to read in the file\n   * again, sort it, and only ship off a reasonable number of results\n   * to the webview. Undefined means to use whatever order is in the\n   * sarif file.\n   */\n  interpretedResultsSortState: InterpretedResultsSortState | undefined;\n\n  /**\n   * Note that in the {@link FullQueryInfo.slurp} method, we create a CompletedQueryInfo instance\n   * by explicitly setting the prototype in order to avoid calling this constructor.\n   */\n  constructor(\n    evaluation: QueryWithResults,\n  ) {\n    this.query = evaluation.query;\n    this.result = evaluation.result;\n    this.logFileLocation = evaluation.logFileLocation;\n\n    // Use the dispose method from the evaluation.\n    // The dispose will clean up any additional log locations that this\n    // query may have created.\n    this.dispose = evaluation.dispose;\n\n    this.sortedResultsInfo = {};\n    this.resultCount = 0;\n  }\n\n  setResultCount(value: number) {\n    this.resultCount = value;\n  }\n\n  get statusString(): string {\n    switch (this.result.resultType) {\n      case messages.QueryResultType.CANCELLATION:\n        return `cancelled after ${Math.round(this.result.evaluationTime / 1000)} seconds`;\n      case messages.QueryResultType.OOM:\n        return 'out of memory';\n      case messages.QueryResultType.SUCCESS:\n        return `finished in ${Math.round(this.result.evaluationTime / 1000)} seconds`;\n      case messages.QueryResultType.TIMEOUT:\n        return `timed out after ${Math.round(this.result.evaluationTime / 1000)} seconds`;\n      case messages.QueryResultType.OTHER_ERROR:\n      default:\n        return this.result.message ? `failed: ${this.result.message}` : 'failed';\n    }\n  }\n\n  getResultsPath(selectedTable: string, useSorted = true): string {\n    if (!useSorted) {\n      return this.query.resultsPaths.resultsPath;\n    }\n    return this.sortedResultsInfo[selectedTable]?.resultsPath\n      || this.query.resultsPaths.resultsPath;\n  }\n\n  get didRunSuccessfully(): boolean {\n    return this.result.resultType === messages.QueryResultType.SUCCESS;\n  }\n\n  async updateSortState(\n    server: cli.CodeQLCliServer,\n    resultSetName: string,\n    sortState?: RawResultsSortState\n  ): Promise<void> {\n    if (sortState === undefined) {\n      delete this.sortedResultsInfo[resultSetName];\n      return;\n    }\n\n    const sortedResultSetInfo: SortedResultSetInfo = {\n      resultsPath: this.query.getSortedResultSetPath(resultSetName),\n      sortState\n    };\n\n    await server.sortBqrs(\n      this.query.resultsPaths.resultsPath,\n      sortedResultSetInfo.resultsPath,\n      resultSetName,\n      [sortState.columnIndex],\n      [sortState.sortDirection]\n    );\n    this.sortedResultsInfo[resultSetName] = sortedResultSetInfo;\n  }\n\n  async updateInterpretedSortState(sortState?: InterpretedResultsSortState): Promise<void> {\n    this.interpretedResultsSortState = sortState;\n  }\n}\n\n\n/**\n * Call cli command to interpret results.\n */\nexport async function interpretResults(\n  server: cli.CodeQLCliServer,\n  metadata: QueryMetadata | undefined,\n  resultsPaths: ResultsPaths,\n  sourceInfo?: cli.SourceInfo\n): Promise<sarif.Log> {\n  const { resultsPath, interpretedResultsPath } = resultsPaths;\n  if (await fs.pathExists(interpretedResultsPath)) {\n    return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8'));\n  }\n  return await server.interpretBqrs(ensureMetadataIsComplete(metadata), resultsPath, interpretedResultsPath, sourceInfo);\n}\n\nexport function ensureMetadataIsComplete(metadata: QueryMetadata | undefined) {\n  if (metadata === undefined) {\n    throw new Error('Can\\'t interpret results without query metadata');\n  }\n  if (metadata.kind === undefined) {\n    throw new Error('Can\\'t interpret results without query metadata including kind');\n  }\n  if (metadata.id === undefined) {\n    // Interpretation per se doesn't really require an id, but the\n    // SARIF format does, so in the absence of one, we use a dummy id.\n    metadata.id = 'dummy-id';\n  }\n  return metadata;\n}\n\n\n/**\n * Used in Interface and Compare-Interface for queries that we know have been complated.\n */\nexport type CompletedLocalQueryInfo = LocalQueryInfo & {\n  completedQuery: CompletedQueryInfo\n};\n\nexport type QueryHistoryInfo = LocalQueryInfo | RemoteQueryHistoryItem;\n\nexport class LocalQueryInfo {\n  readonly t = 'local';\n\n  public failureReason: string | undefined;\n  public completedQuery: CompletedQueryInfo | undefined;\n  private config: QueryHistoryConfig | undefined;\n\n  /**\n   * Note that in the {@link slurpQueryHistory} method, we create a FullQueryInfo instance\n   * by explicitly setting the prototype in order to avoid calling this constructor.\n   */\n  constructor(\n    public readonly initialInfo: InitialQueryInfo,\n    config: QueryHistoryConfig,\n    private cancellationSource?: CancellationTokenSource // used to cancel in progress queries\n  ) {\n    this.setConfig(config);\n  }\n\n  cancel() {\n    this.cancellationSource?.cancel();\n    // query is no longer in progress, can delete the cancellation token source\n    this.cancellationSource?.dispose();\n    delete this.cancellationSource;\n  }\n\n  get startTime() {\n    return this.initialInfo.start.toLocaleString(env.language);\n  }\n\n  interpolate(template: string): string {\n    const { resultCount = 0, statusString = 'in progress' } = this.completedQuery || {};\n    const replacements: { [k: string]: string } = {\n      t: this.startTime,\n      q: this.getQueryName(),\n      d: this.initialInfo.databaseInfo.name,\n      r: resultCount.toString(),\n      s: statusString,\n      f: this.getQueryFileName(),\n      '%': '%',\n    };\n    return template.replace(/%(.)/g, (match, key) => {\n      const replacement = replacements[key];\n      return replacement !== undefined ? replacement : match;\n    });\n  }\n\n  /**\n   * Returns a label for this query that includes interpolated values.\n   */\n  get label(): string {\n    return this.interpolate(\n      this.initialInfo.userSpecifiedLabel ?? this.config?.format ?? ''\n    );\n  }\n\n  /**\n   * Avoids getting the default label for the query.\n   * If there is a custom label for this query, interpolate and use that.\n   * Otherwise, use the name of the query.\n   *\n   * @returns the name of the query, unless there is a custom label for this query.\n   */\n  getShortLabel(): string {\n    return this.initialInfo.userSpecifiedLabel\n      ? this.interpolate(this.initialInfo.userSpecifiedLabel)\n      : this.getQueryName();\n  }\n\n  /**\n   * The query's file name, unless it is a quick eval.\n   * Queries run through quick evaluation are not usually the entire query file.\n   * Label them differently and include the line numbers.\n   */\n  getQueryFileName() {\n    if (this.initialInfo.quickEvalPosition) {\n      const { line, endLine, fileName } = this.initialInfo.quickEvalPosition;\n      const lineInfo = line === endLine ? `${line}` : `${line}-${endLine}`;\n      return `${path.basename(fileName)}:${lineInfo}`;\n    }\n    return path.basename(this.initialInfo.queryPath);\n  }\n\n  /**\n   * Three cases:\n   *\n   * - If this is a completed query, use the query name from the query metadata.\n   * - If this is a quick eval, return the query name with a prefix\n   * - Otherwise, return the query file name.\n   */\n  getQueryName() {\n    if (this.initialInfo.quickEvalPosition) {\n      return 'Quick evaluation of ' + this.getQueryFileName();\n    } else if (this.completedQuery?.query.metadata?.name) {\n      return this.completedQuery?.query.metadata?.name;\n    } else {\n      return this.getQueryFileName();\n    }\n  }\n\n  get completed(): boolean {\n    return !!this.completedQuery;\n  }\n\n  completeThisQuery(info: QueryWithResults) {\n    this.completedQuery = new CompletedQueryInfo(info);\n\n    // dispose of the cancellation token source and also ensure the source is not serialized as JSON\n    this.cancellationSource?.dispose();\n    delete this.cancellationSource;\n  }\n\n  /**\n   * If there is a failure reason, then this query has failed.\n   * If there is no completed query, then this query is still running.\n   * If there is a completed query, then check if didRunSuccessfully.\n   * If true, then this query has completed successfully, otherwise it has failed.\n   */\n  get status(): QueryStatus {\n    if (this.failureReason) {\n      return QueryStatus.Failed;\n    } else if (!this.completedQuery) {\n      return QueryStatus.InProgress;\n    } else if (this.completedQuery.didRunSuccessfully) {\n      return QueryStatus.Completed;\n    } else {\n      return QueryStatus.Failed;\n    }\n  }\n\n  /**\n   * The `config` property must not be serialized since it contains a listerner\n   * for global configuration changes. Instead, It should be set when the query\n   * is deserialized.\n   *\n   * @param config the global query history config object\n   */\n  setConfig(config: QueryHistoryConfig) {\n    // avoid serializing config property\n    Object.defineProperty(this, 'config', {\n      enumerable: false,\n      writable: false,\n      configurable: true,\n      value: config\n    });\n  }\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/queryserver-client.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 5
      },
      "contents" : {
        "text" : "import * as cp from 'child_process';\nimport * as path from 'path';\nimport { DisposableObject } from './pure/disposable-object';\nimport { Disposable, CancellationToken, commands } from 'vscode';\nimport { createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc';\nimport * as cli from './cli';\nimport { QueryServerConfig } from './config';\nimport { Logger, ProgressReporter } from './logging';\nimport { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './pure/messages';\nimport * as messages from './pure/messages';\nimport { ProgressCallback, ProgressTask } from './commandRunner';\nimport * as fs from 'fs-extra';\nimport * as helpers from './helpers';\n\ntype ServerOpts = {\n  logger: Logger;\n  contextStoragePath: string;\n}\n\n/** A running query server process and its associated message connection. */\nclass ServerProcess implements Disposable {\n  child: cp.ChildProcess;\n  connection: MessageConnection;\n  logger: Logger;\n\n  constructor(child: cp.ChildProcess, connection: MessageConnection, logger: Logger) {\n    this.child = child;\n    this.connection = connection;\n    this.logger = logger;\n  }\n\n  dispose(): void {\n    void this.logger.log('Stopping query server...');\n    this.connection.dispose();\n    this.child.stdin!.end();\n    this.child.stderr!.destroy();\n    // TODO kill the process if it doesn't terminate after a certain time limit.\n\n    // On Windows, we usually have to terminate the process before closing its stdout.\n    this.child.stdout!.destroy();\n    void this.logger.log('Stopped query server.');\n  }\n}\n\ntype WithProgressReporting = (task: (progress: ProgressReporter, token: CancellationToken) => Thenable<void>) => Thenable<void>;\n\n/**\n * Client that manages a query server process.\n * The server process is started upon initialization and tracked during its lifetime.\n * The server process is disposed when the client is disposed, or if the client asks\n * to restart it (which disposes the existing process and starts a new one).\n */\nexport class QueryServerClient extends DisposableObject {\n\n  serverProcess?: ServerProcess;\n  evaluationResultCallbacks: { [key: number]: (res: EvaluationResult) => void };\n  progressCallbacks: { [key: number]: ((res: ProgressMessage) => void) | undefined };\n  nextCallback: number;\n  nextProgress: number;\n  withProgressReporting: WithProgressReporting;\n\n  private readonly queryServerStartListeners = [] as ProgressTask<void>[];\n\n  // Can't use standard vscode EventEmitter here since they do not cause the calling\n  // function to fail if one of the event handlers fail. This is something that\n  // we need here.\n  readonly onDidStartQueryServer = (e: ProgressTask<void>) => {\n    this.queryServerStartListeners.push(e);\n  }\n\n  public activeQueryName: string | undefined;\n\n  constructor(\n    readonly config: QueryServerConfig,\n    readonly cliServer: cli.CodeQLCliServer,\n    readonly opts: ServerOpts,\n    withProgressReporting: WithProgressReporting\n  ) {\n    super();\n    // When the query server configuration changes, restart the query server.\n    if (config.onDidChangeConfiguration !== undefined) {\n      this.push(config.onDidChangeConfiguration(() =>\n        commands.executeCommand('codeQL.restartQueryServer')));\n    }\n    this.withProgressReporting = withProgressReporting;\n    this.nextCallback = 0;\n    this.nextProgress = 0;\n    this.progressCallbacks = {};\n    this.evaluationResultCallbacks = {};\n  }\n\n  async initLogger() {\n    let storagePath = this.opts.contextStoragePath;\n    let isCustomLogDirectory = false;\n    if (this.config.customLogDirectory) {\n      try {\n        if (!(await fs.pathExists(this.config.customLogDirectory))) {\n          await fs.mkdir(this.config.customLogDirectory);\n        }\n        void this.logger.log(`Saving query server logs to user-specified directory: ${this.config.customLogDirectory}.`);\n        storagePath = this.config.customLogDirectory;\n        isCustomLogDirectory = true;\n      } catch (e) {\n        void helpers.showAndLogErrorMessage(`${this.config.customLogDirectory} is not a valid directory. Logs will be stored in a temporary workspace directory instead.`);\n      }\n    }\n\n    await this.logger.setLogStoragePath(storagePath, isCustomLogDirectory);\n\n  }\n\n  get logger(): Logger {\n    return this.opts.logger;\n  }\n\n  /** Stops the query server by disposing of the current server process. */\n  private stopQueryServer(): void {\n    if (this.serverProcess !== undefined) {\n      this.disposeAndStopTracking(this.serverProcess);\n    } else {\n      void this.logger.log('No server process to be stopped.');\n    }\n  }\n\n  /** Restarts the query server by disposing of the current server process and then starting a new one. */\n  async restartQueryServer(\n    progress: ProgressCallback,\n    token: CancellationToken\n  ): Promise<void> {\n    this.stopQueryServer();\n    await this.startQueryServer();\n\n    // Ensure we await all responses from event handlers so that\n    // errors can be properly reported to the user.\n    await Promise.all(this.queryServerStartListeners.map(handler => handler(\n      progress,\n      token\n    )));\n  }\n\n  showLog(): void {\n    this.logger.show();\n  }\n\n  /** Starts a new query server process, sending progress messages to the status bar. */\n  async startQueryServer(): Promise<void> {\n    // Use an arrow function to preserve the value of `this`.\n    return this.withProgressReporting((progress, _) => this.startQueryServerImpl(progress));\n  }\n\n  /** Starts a new query server process, sending progress messages to the given reporter. */\n  private async startQueryServerImpl(progressReporter: ProgressReporter): Promise<void> {\n    await this.initLogger();\n    const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);\n    const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);\n\n    if (this.config.saveCache) {\n      args.push('--save-cache');\n    }\n\n    if (this.config.cacheSize > 0) {\n      args.push('--max-disk-cache');\n      args.push(this.config.cacheSize.toString());\n    }\n\n    if (await this.cliServer.cliConstraints.supportsDatabaseRegistration()) {\n      args.push('--require-db-registration');\n    }\n\n    if (await this.cliServer.cliConstraints.supportsOldEvalStats()) {\n      args.push('--old-eval-stats');\n    }\n\n    if (this.config.debug) {\n      args.push('--debug', '--tuple-counting');\n    }\n\n    if (cli.shouldDebugQueryServer()) {\n      args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=y,suspend=n,quiet=y');\n    }\n\n    const child = cli.spawnServer(\n      this.config.codeQlPath,\n      'CodeQL query server',\n      ['execute', 'query-server'],\n      args,\n      this.logger,\n      data => this.logger.log(data.toString(), {\n        trailingNewline: false,\n        additionalLogLocation: this.activeQueryName\n      }),\n      undefined, // no listener for stdout\n      progressReporter\n    );\n    progressReporter.report({ message: 'Connecting to CodeQL query server' });\n    const connection = createMessageConnection(child.stdout, child.stdin);\n    connection.onRequest(completeQuery, res => {\n      if (!(res.runId in this.evaluationResultCallbacks)) {\n        void this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);\n      } else {\n        const baseLocation = this.logger.getBaseLocation();\n        if (baseLocation && this.activeQueryName) {\n          res.logFileLocation = path.join(baseLocation, this.activeQueryName);\n        }\n        this.evaluationResultCallbacks[res.runId](res);\n      }\n      return {};\n    });\n    connection.onNotification(progress, res => {\n      const callback = this.progressCallbacks[res.id];\n      if (callback) {\n        callback(res);\n      }\n    });\n    this.serverProcess = new ServerProcess(child, connection, this.logger);\n    // Ensure the server process is disposed together with this client.\n    this.track(this.serverProcess);\n    connection.listen();\n    progressReporter.report({ message: 'Connected to CodeQL query server' });\n    this.nextCallback = 0;\n    this.nextProgress = 0;\n    this.progressCallbacks = {};\n    this.evaluationResultCallbacks = {};\n  }\n\n  registerCallback(callback: (res: EvaluationResult) => void): number {\n    const id = this.nextCallback++;\n    this.evaluationResultCallbacks[id] = callback;\n    return id;\n  }\n\n  unRegisterCallback(id: number): void {\n    delete this.evaluationResultCallbacks[id];\n  }\n\n  get serverProcessPid(): number {\n    return this.serverProcess!.child.pid;\n  }\n\n  async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {\n    const id = this.nextProgress++;\n    this.progressCallbacks[id] = progress;\n\n    this.updateActiveQuery(type.method, parameter);\n    try {\n      if (this.serverProcess === undefined) {\n        throw new Error('No query server process found.');\n      }\n      return await this.serverProcess.connection.sendRequest(type, { body: parameter, progressId: id }, token);\n    } finally {\n      delete this.progressCallbacks[id];\n    }\n  }\n\n  /**\n   * Updates the active query every time there is a new request to compile.\n   * The active query is used to specify the side log.\n   *\n   * This isn't ideal because in situations where there are queries running\n   * in parallel, each query's log messages are interleaved. Fixing this\n   * properly will require a change in the query server.\n   */\n  private updateActiveQuery(method: string, parameter: any): void {\n    if (method === messages.compileQuery.method) {\n      const queryPath = parameter?.queryToCheck?.queryPath || 'unknown';\n      this.activeQueryName = `query-${path.basename(queryPath)}-${this.nextProgress}.log`;\n    }\n  }\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/telemetry.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 6
      },
      "contents" : {
        "text" : "import { ConfigurationTarget, Extension, ExtensionContext, ConfigurationChangeEvent } from 'vscode';\nimport TelemetryReporter from 'vscode-extension-telemetry';\nimport { ConfigListener, CANARY_FEATURES, ENABLE_TELEMETRY, GLOBAL_ENABLE_TELEMETRY, LOG_TELEMETRY } from './config';\nimport * as appInsights from 'applicationinsights';\nimport { logger } from './logging';\nimport { UserCancellationException } from './commandRunner';\nimport { showBinaryChoiceWithUrlDialog } from './helpers';\n\n// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.\nconst key = 'REPLACE-APP-INSIGHTS-KEY';\n\nexport enum CommandCompletion {\n  Success = 'Success',\n  Failed = 'Failed',\n  Cancelled = 'Cancelled'\n}\n\n// Avoid sending the following data to App insights since we don't need it.\nconst tagsToRemove = [\n  'ai.application.ver',\n  'ai.device.id',\n  'ai.cloud.roleInstance',\n  'ai.cloud.role',\n  'ai.device.id',\n  'ai.device.osArchitecture',\n  'ai.device.osPlatform',\n  'ai.device.osVersion',\n  'ai.internal.sdkVersion',\n  'ai.session.id'\n];\n\nconst baseDataPropertiesToRemove = [\n  'common.os',\n  'common.platformversion',\n  'common.remotename',\n  'common.uikind',\n  'common.vscodesessionid'\n];\n\nexport class TelemetryListener extends ConfigListener {\n\n  static relevantSettings = [ENABLE_TELEMETRY, CANARY_FEATURES];\n\n  private reporter?: TelemetryReporter;\n\n  constructor(\n    private readonly id: string,\n    private readonly version: string,\n    private readonly key: string,\n    private readonly ctx: ExtensionContext\n  ) {\n    super();\n  }\n\n  /**\n   * This function handles changes to relevant configuration elements. There are 2 configuration\n   * ids that this function cares about:\n   *\n   *     * `codeQL.telemetry.enableTelemetry`: If this one has changed, then we need to re-initialize\n   *        the reporter and the reporter may wind up being removed.\n   *     * `codeQL.canary`: A change here could possibly re-trigger a dialog popup.\n   *\n   * Note that the global telemetry setting also gate-keeps whether or not to send telemetry events\n   * to Application Insights. However, this gatekeeping happens inside of the vscode-extension-telemetry\n   * package. So, this does not need to be handled here.\n   *\n   * @param e the configuration change event\n   */\n  async handleDidChangeConfiguration(e: ConfigurationChangeEvent): Promise<void> {\n    if (\n      e.affectsConfiguration('codeQL.telemetry.enableTelemetry') ||\n      e.affectsConfiguration('telemetry.enableTelemetry')\n    ) {\n      await this.initialize();\n    }\n\n    // Re-request telemetry so that users can see the dialog again.\n    // Re-request if codeQL.canary is being set to `true` and telemetry\n    // is not currently enabled.\n    if (\n      e.affectsConfiguration('codeQL.canary') &&\n      CANARY_FEATURES.getValue() &&\n      !ENABLE_TELEMETRY.getValue()\n    ) {\n      await Promise.all([\n        this.setTelemetryRequested(false),\n        this.requestTelemetryPermission()\n      ]);\n    }\n  }\n\n  async initialize() {\n    await this.requestTelemetryPermission();\n\n    this.disposeReporter();\n\n    if (ENABLE_TELEMETRY.getValue<boolean>()) {\n      this.createReporter();\n    }\n  }\n\n  private createReporter() {\n    this.reporter = new TelemetryReporter(\n      this.id,\n      this.version,\n      this.key,\n      /* anonymize stack traces */ true\n    );\n    this.push(this.reporter);\n\n    const client = (this.reporter as any).appInsightsClient as appInsights.TelemetryClient;\n    if (client) {\n      // add a telemetry processor to delete unwanted properties\n      client.addTelemetryProcessor((envelope: any) => {\n        tagsToRemove.forEach(tag => delete envelope.tags[tag]);\n        const baseDataProperties = (envelope.data as any)?.baseData?.properties;\n        if (baseDataProperties) {\n          baseDataPropertiesToRemove.forEach(prop => delete baseDataProperties[prop]);\n        }\n\n        if (LOG_TELEMETRY.getValue<boolean>()) {\n          void logger.log(`Telemetry: ${JSON.stringify(envelope)}`);\n        }\n        return true;\n      });\n    }\n  }\n\n  dispose() {\n    super.dispose();\n    void this.reporter?.dispose();\n  }\n\n  sendCommandUsage(name: string, executionTime: number, error?: Error) {\n    if (!this.reporter) {\n      return;\n    }\n    const status = !error\n      ? CommandCompletion.Success\n      : error instanceof UserCancellationException\n        ? CommandCompletion.Cancelled\n        : CommandCompletion.Failed;\n\n    const isCanary = (!!CANARY_FEATURES.getValue<boolean>()).toString();\n\n    this.reporter.sendTelemetryEvent(\n      'command-usage',\n      {\n        name,\n        status,\n        isCanary\n      },\n      { executionTime }\n    );\n  }\n\n  /**\n   * Displays a popup asking the user if they want to enable telemetry\n   * for this extension.\n   */\n  async requestTelemetryPermission() {\n    if (!this.wasTelemetryRequested()) {\n      // if global telemetry is disabled, avoid showing the dialog or making any changes\n      let result = undefined;\n      if (GLOBAL_ENABLE_TELEMETRY.getValue()) {\n        // Extension won't start until this completes.\n        result = await showBinaryChoiceWithUrlDialog(\n          'Does the CodeQL Extension by GitHub have your permission to collect usage data and metrics to help us improve CodeQL for VSCode?',\n          'https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code'\n        );\n      }\n      if (result !== undefined) {\n        await Promise.all([\n          this.setTelemetryRequested(true),\n          ENABLE_TELEMETRY.updateValue<boolean>(result, ConfigurationTarget.Global),\n        ]);\n      }\n    }\n  }\n\n  /**\n   * Exposed for testing\n   */\n  get _reporter() {\n    return this.reporter;\n  }\n\n  private disposeReporter() {\n    if (this.reporter) {\n      void this.reporter.dispose();\n      this.reporter = undefined;\n    }\n  }\n\n  private wasTelemetryRequested(): boolean {\n    return !!this.ctx.globalState.get<boolean>('telemetry-request-viewed');\n  }\n\n  private async setTelemetryRequested(newValue: boolean): Promise<void> {\n    await this.ctx.globalState.update('telemetry-request-viewed', newValue);\n  }\n}\n\n\n/**\n * The global Telemetry instance\n */\nexport let telemetryListener: TelemetryListener;\n\nexport async function initializeTelemetry(extension: Extension<any>, ctx: ExtensionContext): Promise<void> {\n  telemetryListener = new TelemetryListener(extension.id, extension.packageJSON.version, key, ctx);\n  // do not await initialization, since doing so will sometimes cause a modal popup.\n  // this is a particular problem during integration tests, which will hang if a modal popup is displayed.\n  void telemetryListener.initialize();\n  ctx.subscriptions.push(telemetryListener);\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/test-ui.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 7
      },
      "contents" : {
        "text" : "import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { Uri, TextDocumentShowOptions, commands, window } from 'vscode';\nimport {\n  TestHub,\n  TestController,\n  TestAdapter,\n  TestRunStartedEvent,\n  TestRunFinishedEvent,\n  TestEvent,\n  TestSuiteEvent\n} from 'vscode-test-adapter-api';\n\nimport { showAndLogWarningMessage } from './helpers';\nimport { TestTreeNode } from './test-tree-node';\nimport { DisposableObject } from './pure/disposable-object';\nimport { UIService } from './vscode-utils/ui-service';\nimport { QLTestAdapter, getExpectedFile, getActualFile } from './test-adapter';\nimport { logger } from './logging';\n\ntype VSCodeTestEvent = TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent;\n\n/**\n * Test event listener. Currently unused, but left in to keep the plumbing hooked up for future use.\n */\nclass QLTestListener extends DisposableObject {\n  constructor(adapter: TestAdapter) {\n    super();\n\n    this.push(adapter.testStates(this.onTestStatesEvent, this));\n  }\n\n  private onTestStatesEvent(_e: VSCodeTestEvent): void {\n    /**/\n  }\n}\n\n/**\n * Service that implements all UI and commands for QL tests.\n */\nexport class TestUIService extends UIService implements TestController {\n  private readonly listeners: Map<TestAdapter, QLTestListener> = new Map();\n\n  constructor(private readonly testHub: TestHub) {\n    super();\n\n    void logger.log('Registering CodeQL test panel commands.');\n    this.registerCommand('codeQLTests.showOutputDifferences', this.showOutputDifferences);\n    this.registerCommand('codeQLTests.acceptOutput', this.acceptOutput);\n\n    testHub.registerTestController(this);\n  }\n\n  public dispose(): void {\n    this.testHub.unregisterTestController(this);\n\n    super.dispose();\n  }\n\n  public registerTestAdapter(adapter: TestAdapter): void {\n    this.listeners.set(adapter, new QLTestListener(adapter));\n  }\n\n  public unregisterTestAdapter(adapter: TestAdapter): void {\n    if (adapter instanceof QLTestAdapter) {\n      this.listeners.delete(adapter);\n    }\n  }\n\n  private async acceptOutput(node: TestTreeNode): Promise<void> {\n    const testId = node.info.id;\n    const stat = await fs.lstat(testId);\n    if (stat.isFile()) {\n      const expectedPath = getExpectedFile(testId);\n      const actualPath = getActualFile(testId);\n      await fs.copy(actualPath, expectedPath, { overwrite: true });\n    }\n  }\n\n  private async showOutputDifferences(node: TestTreeNode): Promise<void> {\n    const testId = node.info.id;\n    const stat = await fs.lstat(testId);\n    if (stat.isFile()) {\n      const expectedPath = getExpectedFile(testId);\n      const expectedUri = Uri.file(expectedPath);\n      const actualPath = getActualFile(testId);\n      const options: TextDocumentShowOptions = {\n        preserveFocus: true,\n        preview: true\n      };\n\n      if (!await fs.pathExists(expectedPath)) {\n        void showAndLogWarningMessage(`'${path.basename(expectedPath)}' does not exist. Creating an empty file.`);\n        await fs.createFile(expectedPath);\n      }\n\n      if (await fs.pathExists(actualPath)) {\n        const actualUri = Uri.file(actualPath);\n        await commands.executeCommand<void>('vscode.diff', expectedUri, actualUri,\n          `Expected vs. Actual for ${path.basename(testId)}`, options);\n      } else {\n        await window.showTextDocument(expectedUri, options);\n      }\n    }\n  }\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/run-queries.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 8
      },
      "contents" : {
        "text" : "import * as crypto from 'crypto';\nimport * as fs from 'fs-extra';\nimport * as tmp from 'tmp-promise';\nimport * as path from 'path';\nimport { nanoid } from 'nanoid';\nimport {\n  CancellationToken,\n  ConfigurationTarget,\n  Range,\n  TextDocument,\n  TextEditor,\n  Uri,\n  window\n} from 'vscode';\nimport { ErrorCodes, ResponseError } from 'vscode-languageclient';\n\nimport * as cli from './cli';\nimport * as config from './config';\nimport { DatabaseItem, DatabaseManager } from './databases';\nimport { createTimestampFile, getOnDiskWorkspaceFolders, showAndLogErrorMessage, tryGetQueryMetadata, upgradesTmpDir } from './helpers';\nimport { ProgressCallback, UserCancellationException } from './commandRunner';\nimport { DatabaseInfo, QueryMetadata } from './pure/interface-types';\nimport { logger } from './logging';\nimport * as messages from './pure/messages';\nimport { InitialQueryInfo } from './query-results';\nimport * as qsClient from './queryserver-client';\nimport { isQuickQueryPath } from './quick-query';\nimport { compileDatabaseUpgradeSequence, hasNondestructiveUpgradeCapabilities, upgradeDatabaseExplicit } from './upgrades';\nimport { ensureMetadataIsComplete } from './query-results';\nimport { SELECT_QUERY_NAME } from './contextual/locationFinder';\nimport { DecodedBqrsChunk } from './pure/bqrs-cli-types';\n\n/**\n * run-queries.ts\n * --------------\n *\n * Compiling and running QL queries.\n */\n\n/**\n * Information about which query will be to be run. `quickEvalPosition` and `quickEvalText`\n * is only filled in if the query is a quick query.\n */\ninterface SelectedQuery {\n  queryPath: string;\n  quickEvalPosition?: messages.Position;\n  quickEvalText?: string;\n}\n\n/**\n * A collection of evaluation-time information about a query,\n * including the query itself, and where we have decided to put\n * temporary files associated with it, such as the compiled query\n * output and results.\n */\nexport class QueryEvaluationInfo {\n\n  /**\n   * Note that in the {@link FullQueryInfo.slurp} method, we create a QueryEvaluationInfo instance\n   * by explicitly setting the prototype in order to avoid calling this constructor.\n   */\n  constructor(\n    private readonly querySaveDir: string,\n    public readonly dbItemPath: string,\n    private readonly databaseHasMetadataFile: boolean,\n    public readonly queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution\n    public readonly quickEvalPosition?: messages.Position,\n    public readonly metadata?: QueryMetadata,\n    public readonly templates?: messages.TemplateDefinitions\n  ) {\n    /**/\n  }\n\n  get dilPath() {\n    return path.join(this.querySaveDir, 'results.dil');\n  }\n\n  get csvPath() {\n    return path.join(this.querySaveDir, 'results.csv');\n  }\n\n  get compiledQueryPath() {\n    return path.join(this.querySaveDir, 'compiledQuery.qlo');\n  }\n\n  get resultsPaths() {\n    return {\n      resultsPath: path.join(this.querySaveDir, 'results.bqrs'),\n      interpretedResultsPath: path.join(this.querySaveDir, 'interpretedResults.sarif'),\n    };\n  }\n\n  getSortedResultSetPath(resultSetName: string) {\n    return path.join(this.querySaveDir, `sortedResults-${resultSetName}.bqrs`);\n  }\n\n  /**\n   * Creates a file in the query directory that indicates when this query was created.\n   * This is important for keeping track of when queries should be removed.\n   */\n  async createTimestampFile() {\n    await createTimestampFile(this.querySaveDir);\n  }\n\n  async run(\n    qs: qsClient.QueryServerClient,\n    upgradeQlo: string | undefined,\n    availableMlModels: cli.MlModelInfo[],\n    dbItem: DatabaseItem,\n    progress: ProgressCallback,\n    token: CancellationToken,\n  ): Promise<messages.EvaluationResult> {\n    if (!dbItem.contents || dbItem.error) {\n      throw new Error('Can\\'t run query on invalid database.');\n    }\n\n    let result: messages.EvaluationResult | null = null;\n\n    const callbackId = qs.registerCallback(res => { result = res; });\n\n    const availableMlModelUris: messages.MlModel[] = availableMlModels.map(model => ({ uri: Uri.file(model.path).toString(true) }));\n\n    const queryToRun: messages.QueryToRun = {\n      resultsPath: this.resultsPaths.resultsPath,\n      qlo: Uri.file(this.compiledQueryPath).toString(),\n      compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),\n      allowUnknownTemplates: true,\n      templateValues: this.templates,\n      availableMlModels: availableMlModelUris,\n      id: callbackId,\n      timeoutSecs: qs.config.timeoutSecs,\n    };\n\n    const dataset: messages.Dataset = {\n      dbDir: dbItem.contents.datasetUri.fsPath,\n      workingSet: 'default'\n    };\n    const params: messages.EvaluateQueriesParams = {\n      db: dataset,\n      evaluateId: callbackId,\n      queries: [queryToRun],\n      stopOnError: false,\n      useSequenceHint: false\n    };\n    try {\n      await qs.sendRequest(messages.runQueries, params, token, progress);\n    } finally {\n      qs.unRegisterCallback(callbackId);\n    }\n    return result || {\n      evaluationTime: 0,\n      message: 'No result from server',\n      queryId: -1,\n      runId: callbackId,\n      resultType: messages.QueryResultType.OTHER_ERROR\n    };\n  }\n\n  async compile(\n    qs: qsClient.QueryServerClient,\n    program: messages.QlProgram,\n    progress: ProgressCallback,\n    token: CancellationToken,\n  ): Promise<messages.CompilationMessage[]> {\n    let compiled: messages.CheckQueryResult | undefined;\n    try {\n      const target = this.quickEvalPosition ? {\n        quickEval: { quickEvalPos: this.quickEvalPosition }\n      } : { query: {} };\n      const params: messages.CompileQueryParams = {\n        compilationOptions: {\n          computeNoLocationUrls: true,\n          failOnWarnings: false,\n          fastCompilation: false,\n          includeDilInQlo: true,\n          localChecking: false,\n          noComputeGetUrl: false,\n          noComputeToString: false,\n          computeDefaultStrings: true\n        },\n        extraOptions: {\n          timeoutSecs: qs.config.timeoutSecs\n        },\n        queryToCheck: program,\n        resultPath: this.compiledQueryPath,\n        target,\n      };\n\n      compiled = await qs.sendRequest(messages.compileQuery, params, token, progress);\n    } finally {\n      void qs.logger.log(' - - - COMPILATION DONE - - - ');\n    }\n    return (compiled?.messages || []).filter(msg => msg.severity === messages.Severity.ERROR);\n  }\n\n  /**\n   * Holds if this query can in principle produce interpreted results.\n   */\n  canHaveInterpretedResults(): boolean {\n    if (!this.databaseHasMetadataFile) {\n      void logger.log('Cannot produce interpreted results since the database does not have a .dbinfo or codeql-database.yml file.');\n      return false;\n    }\n\n    const hasKind = !!this.metadata?.kind;\n    if (!hasKind) {\n      void logger.log('Cannot produce interpreted results since the query does not have @kind metadata.');\n      return false;\n    }\n\n    // table is the default query kind. It does not produce interpreted results.\n    // any query kind that is not table can, in principle, produce interpreted results.\n    const isTable = hasKind && this.metadata?.kind === 'table';\n    return !isTable;\n  }\n\n  /**\n   * Holds if this query actually has produced interpreted results.\n   */\n  async hasInterpretedResults(): Promise<boolean> {\n    return fs.pathExists(this.resultsPaths.interpretedResultsPath);\n  }\n\n  /**\n   * Holds if this query already has DIL produced\n   */\n  async hasDil(): Promise<boolean> {\n    return fs.pathExists(this.dilPath);\n  }\n\n  /**\n   * Holds if this query already has CSV results produced\n   */\n  async hasCsv(): Promise<boolean> {\n    return fs.pathExists(this.csvPath);\n  }\n\n  /**\n   * Returns the path to the DIL file produced by this query. If the query has not yet produced DIL,\n   * this will return first create the DIL file and then return the path to the DIL file.\n   */\n  async ensureDilPath(qs: qsClient.QueryServerClient): Promise<string> {\n    if (await this.hasDil()) {\n      return this.dilPath;\n    }\n\n    if (!(await fs.pathExists(this.compiledQueryPath))) {\n      throw new Error(\n        `Cannot create DIL because compiled query is missing. ${this.compiledQueryPath}`\n      );\n    }\n\n    await qs.cliServer.generateDil(this.compiledQueryPath, this.dilPath);\n    return this.dilPath;\n  }\n\n  /**\n   * Creates the CSV file containing the results of this query. This will only be called if the query\n   * does not have interpreted results and the CSV file does not already exist.\n   */\n  async exportCsvResults(qs: qsClient.QueryServerClient, csvPath: string, onFinish: () => void): Promise<void> {\n    let stopDecoding = false;\n    const out = fs.createWriteStream(csvPath);\n    out.on('finish', onFinish);\n    out.on('error', () => {\n      if (!stopDecoding) {\n        stopDecoding = true;\n        void showAndLogErrorMessage(`Failed to write CSV results to ${csvPath}`);\n      }\n    });\n    let nextOffset: number | undefined = 0;\n    while (nextOffset !== undefined && !stopDecoding) {\n      const chunk: DecodedBqrsChunk = await qs.cliServer.bqrsDecode(this.resultsPaths.resultsPath, SELECT_QUERY_NAME, {\n        pageSize: 100,\n        offset: nextOffset,\n      });\n      for (const tuple of chunk.tuples) {\n        out.write(tuple.join(',') + '\\n');\n      }\n      nextOffset = chunk.next;\n    }\n    out.end();\n  }\n\n  /**\n   * Returns the path to the CSV alerts interpretation of this query results. If CSV results have\n   * not yet been produced, this will return first create the CSV results and then return the path.\n   *\n   * This method only works for queries with interpreted results.\n   */\n  async ensureCsvAlerts(qs: qsClient.QueryServerClient, dbm: DatabaseManager): Promise<string> {\n    if (await this.hasCsv()) {\n      return this.csvPath;\n    }\n\n    const dbItem = dbm.findDatabaseItem(Uri.file(this.dbItemPath));\n    if (!dbItem) {\n      throw new Error(`Cannot produce CSV results because database is missing. ${this.dbItemPath}`);\n    }\n\n    let sourceInfo;\n    if (dbItem.sourceArchive !== undefined) {\n      sourceInfo = {\n        sourceArchive: dbItem.sourceArchive.fsPath,\n        sourceLocationPrefix: await dbItem.getSourceLocationPrefix(\n          qs.cliServer\n        ),\n      };\n    }\n\n    await qs.cliServer.generateResultsCsv(ensureMetadataIsComplete(this.metadata), this.resultsPaths.resultsPath, this.csvPath, sourceInfo);\n    return this.csvPath;\n  }\n\n  /**\n   * Cleans this query's results directory.\n   */\n  async deleteQuery(): Promise<void> {\n    await fs.remove(this.querySaveDir);\n  }\n}\n\nexport interface QueryWithResults {\n  readonly query: QueryEvaluationInfo;\n  readonly result: messages.EvaluationResult;\n  readonly logFileLocation?: string;\n  readonly dispose: () => void;\n}\n\nexport async function clearCacheInDatabase(\n  qs: qsClient.QueryServerClient,\n  dbItem: DatabaseItem,\n  progress: ProgressCallback,\n  token: CancellationToken,\n): Promise<messages.ClearCacheResult> {\n  if (dbItem.contents === undefined) {\n    throw new Error('Can\\'t clear the cache in an invalid database.');\n  }\n\n  const db: messages.Dataset = {\n    dbDir: dbItem.contents.datasetUri.fsPath,\n    workingSet: 'default',\n  };\n\n  const params: messages.ClearCacheParams = {\n    dryRun: false,\n    db,\n  };\n\n  return qs.sendRequest(messages.clearCache, params, token, progress);\n}\n\n/**\n * @param filePath This needs to be equivalent to Java's `Path.toRealPath(NO_FOLLOW_LINKS)`\n */\nasync function convertToQlPath(filePath: string): Promise<string> {\n  if (process.platform === 'win32') {\n\n    if (path.parse(filePath).root === filePath) {\n      // Java assumes uppercase drive letters are canonical.\n      return filePath.toUpperCase();\n    } else {\n      const dir = await convertToQlPath(path.dirname(filePath));\n      const fileName = path.basename(filePath);\n      const fileNames = await fs.readdir(dir);\n      for (const name of fileNames) {\n        // Leave the locale argument empty so that the default OS locale is used.\n        // We do this because this operation works on filesystem entities, which\n        // use the os locale, regardless of the locale of the running VS Code instance.\n        if (fileName.localeCompare(name, undefined, { sensitivity: 'accent' }) === 0) {\n          return path.join(dir, name);\n        }\n      }\n    }\n    throw new Error('Can\\'t convert path to form suitable for QL:' + filePath);\n  } else {\n    return filePath;\n  }\n}\n\n\n\n/** Gets the selected position within the given editor. */\nasync function getSelectedPosition(editor: TextEditor, range?: Range): Promise<messages.Position> {\n  const selectedRange = range || editor.selection;\n  const pos = selectedRange.start;\n  const posEnd = selectedRange.end;\n  // Convert from 0-based to 1-based line and column numbers.\n  return {\n    fileName: await convertToQlPath(editor.document.fileName),\n    line: pos.line + 1,\n    column: pos.character + 1,\n    endLine: posEnd.line + 1,\n    endColumn: posEnd.character + 1\n  };\n}\n\n/**\n * Compare the dbscheme implied by the query `query` and that of the current database.\n * - If they are compatible, do nothing.\n * - If they are incompatible but the database can be upgraded, suggest that upgrade.\n * - If they are incompatible and the database cannot be upgraded, throw an error.\n */\nasync function checkDbschemeCompatibility(\n  cliServer: cli.CodeQLCliServer,\n  qs: qsClient.QueryServerClient,\n  query: QueryEvaluationInfo,\n  qlProgram: messages.QlProgram,\n  dbItem: DatabaseItem,\n  progress: ProgressCallback,\n  token: CancellationToken,\n): Promise<void> {\n  const searchPath = getOnDiskWorkspaceFolders();\n\n  if (dbItem.contents?.dbSchemeUri !== undefined) {\n    const { finalDbscheme } = await cliServer.resolveUpgrades(dbItem.contents.dbSchemeUri.fsPath, searchPath, false);\n    const hash = async function(filename: string): Promise<string> {\n      return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');\n    };\n\n    // At this point, we have learned about three dbschemes:\n\n    // the dbscheme of the actual database we're querying.\n    const dbschemeOfDb = await hash(dbItem.contents.dbSchemeUri.fsPath);\n\n    // the dbscheme of the query we're running, including the library we've resolved it to use.\n    const dbschemeOfLib = await hash(query.queryDbscheme);\n\n    // the database we're able to upgrade to\n    const upgradableTo = await hash(finalDbscheme);\n\n    if (upgradableTo != dbschemeOfLib) {\n      reportNoUpgradePath(qlProgram, query);\n    }\n\n    if (upgradableTo == dbschemeOfLib &&\n      dbschemeOfDb != dbschemeOfLib) {\n      // Try to upgrade the database\n      await upgradeDatabaseExplicit(\n        qs,\n        dbItem,\n        progress,\n        token\n      );\n    }\n  }\n}\n\nfunction reportNoUpgradePath(qlProgram: messages.QlProgram, query: QueryEvaluationInfo): void {\n  throw new Error(\n    `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.`\n  );\n}\n\n/**\n * Compile a non-destructive upgrade.\n */\nasync function compileNonDestructiveUpgrade(\n  qs: qsClient.QueryServerClient,\n  upgradeTemp: tmp.DirectoryResult,\n  query: QueryEvaluationInfo,\n  qlProgram: messages.QlProgram,\n  dbItem: DatabaseItem,\n  progress: ProgressCallback,\n  token: CancellationToken,\n): Promise<string> {\n\n  if (!dbItem?.contents?.dbSchemeUri) {\n    throw new Error('Database is invalid, and cannot be upgraded.');\n  }\n\n  // When packaging is used, dependencies may exist outside of the workspace and they are always on the resolved search path.\n  // When packaging is not used, all dependencies are in the workspace.\n  const upgradesPath = (await qs.cliServer.cliConstraints.supportsPackaging())\n    ? qlProgram.libraryPath\n    : getOnDiskWorkspaceFolders();\n\n  const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(\n    dbItem.contents.dbSchemeUri.fsPath,\n    upgradesPath,\n    true,\n    query.queryDbscheme\n  );\n\n  if (!matchesTarget) {\n    reportNoUpgradePath(qlProgram, query);\n  }\n  const result = await compileDatabaseUpgradeSequence(qs, dbItem, scripts, upgradeTemp, progress, token);\n  if (result.compiledUpgrade === undefined) {\n    const error = result.error || '[no error message available]';\n    throw new Error(error);\n  }\n  // We can upgrade to the actual target\n  qlProgram.dbschemePath = query.queryDbscheme;\n  // We are new enough that we will always support single file upgrades.\n  return result.compiledUpgrade;\n}\n\n/**\n * Prompts the user to save `document` if it has unsaved changes.\n *\n * @param document The document to save.\n *\n * @returns true if we should save changes and false if we should continue without saving changes.\n * @throws UserCancellationException if we should abort whatever operation triggered this prompt\n */\nasync function promptUserToSaveChanges(document: TextDocument): Promise<boolean> {\n  if (document.isDirty) {\n    if (config.AUTOSAVE_SETTING.getValue()) {\n      return true;\n    }\n    else {\n      const yesItem = { title: 'Yes', isCloseAffordance: false };\n      const alwaysItem = { title: 'Always Save', isCloseAffordance: false };\n      const noItem = { title: 'No (run version on disk)', isCloseAffordance: false };\n      const cancelItem = { title: 'Cancel', isCloseAffordance: true };\n      const message = 'Query file has unsaved changes. Save now?';\n      const chosenItem = await window.showInformationMessage(\n        message,\n        { modal: true },\n        yesItem, alwaysItem, noItem, cancelItem\n      );\n\n      if (chosenItem === alwaysItem) {\n        await config.AUTOSAVE_SETTING.updateValue(true, ConfigurationTarget.Workspace);\n        return true;\n      }\n\n      if (chosenItem === yesItem) {\n        return true;\n      }\n\n      if (chosenItem === cancelItem) {\n        throw new UserCancellationException('Query run cancelled.', true);\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Determines which QL file to run during an invocation of `Run Query` or `Quick Evaluation`, as follows:\n * - If the command was called by clicking on a file, then use that file.\n * - Otherwise, use the file open in the current editor.\n * - In either case, prompt the user to save the file if it is open with unsaved changes.\n * - For `Quick Evaluation`, ensure the selected file is also the one open in the editor,\n * and use the selected region.\n * @param selectedResourceUri The selected resource when the command was run.\n * @param quickEval Whether the command being run is `Quick Evaluation`.\n*/\nexport async function determineSelectedQuery(\n  selectedResourceUri: Uri | undefined,\n  quickEval: boolean,\n  range?: Range\n): Promise<SelectedQuery> {\n  const editor = window.activeTextEditor;\n\n  // Choose which QL file to use.\n  let queryUri: Uri;\n  if (selectedResourceUri) {\n    // A resource was passed to the command handler, so use it.\n    queryUri = selectedResourceUri;\n  } else {\n    // No resource was passed to the command handler, so obtain it from the active editor.\n    // This usually happens when the command is called from the Command Palette.\n    if (editor === undefined) {\n      throw new Error('No query was selected. Please select a query and try again.');\n    } else {\n      queryUri = editor.document.uri;\n    }\n  }\n\n  if (queryUri.scheme !== 'file') {\n    throw new Error('Can only run queries that are on disk.');\n  }\n  const queryPath = queryUri.fsPath;\n\n  if (quickEval) {\n    if (!(queryPath.endsWith('.ql') || queryPath.endsWith('.qll'))) {\n      throw new Error('The selected resource is not a CodeQL file; It should have the extension \".ql\" or \".qll\".');\n    }\n  } else {\n    if (!(queryPath.endsWith('.ql'))) {\n      throw new Error('The selected resource is not a CodeQL query file; It should have the extension \".ql\".');\n    }\n  }\n\n  // Whether we chose the file from the active editor or from a context menu,\n  // if the same file is open with unsaved changes in the active editor,\n  // then prompt the user to save it first.\n  if (editor !== undefined && editor.document.uri.fsPath === queryPath) {\n    if (await promptUserToSaveChanges(editor.document)) {\n      await editor.document.save();\n    }\n  }\n\n  let quickEvalPosition: messages.Position | undefined = undefined;\n  let quickEvalText: string | undefined = undefined;\n  if (quickEval) {\n    if (editor == undefined) {\n      throw new Error('Can\\'t run quick evaluation without an active editor.');\n    }\n    if (editor.document.fileName !== queryPath) {\n      // For Quick Evaluation we expect these to be the same.\n      // Report an error if we end up in this (hopefully unlikely) situation.\n      throw new Error('The selected resource for quick evaluation should match the active editor.');\n    }\n    quickEvalPosition = await getSelectedPosition(editor, range);\n    if (!editor.selection?.isEmpty) {\n      quickEvalText = editor.document.getText(editor.selection);\n    } else {\n      // capture the entire line if the user didn't select anything\n      const line = editor.document.lineAt(editor.selection.active.line);\n      quickEvalText = line.text.trim();\n    }\n  }\n\n  return { queryPath, quickEvalPosition, quickEvalText };\n}\n\nexport async function compileAndRunQueryAgainstDatabase(\n  cliServer: cli.CodeQLCliServer,\n  qs: qsClient.QueryServerClient,\n  dbItem: DatabaseItem,\n  initialInfo: InitialQueryInfo,\n  queryStorageDir: string,\n  progress: ProgressCallback,\n  token: CancellationToken,\n  templates?: messages.TemplateDefinitions,\n): Promise<QueryWithResults> {\n  if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {\n    throw new Error(`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`);\n  }\n\n  // Get the workspace folder paths.\n  const diskWorkspaceFolders = getOnDiskWorkspaceFolders();\n  // Figure out the library path for the query.\n  const packConfig = await cliServer.resolveLibraryPath(diskWorkspaceFolders, initialInfo.queryPath);\n\n  if (!packConfig.dbscheme) {\n    throw new Error('Could not find a database scheme for this query. Please check that you have a valid qlpack.yml file for this query, which refers to a database scheme either in the `dbscheme` field or through one of its dependencies.');\n  }\n\n  // Check whether the query has an entirely different schema from the\n  // database. (Queries that merely need the database to be upgraded\n  // won't trigger this check)\n  // This test will produce confusing results if we ever change the name of the database schema files.\n  const querySchemaName = path.basename(packConfig.dbscheme);\n  const dbSchemaName = path.basename(dbItem.contents.dbSchemeUri.fsPath);\n  if (querySchemaName != dbSchemaName) {\n    void logger.log(`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`);\n    throw new Error(`The query ${path.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.`);\n  }\n\n  const qlProgram: messages.QlProgram = {\n    // The project of the current document determines which library path\n    // we use. The `libraryPath` field in this server message is relative\n    // to the workspace root, not to the project root.\n    libraryPath: packConfig.libraryPath,\n    // Since we are compiling and running a query against a database,\n    // we use the database's DB scheme here instead of the DB scheme\n    // from the current document's project.\n    dbschemePath: dbItem.contents.dbSchemeUri.fsPath,\n    queryPath: initialInfo.queryPath\n  };\n\n  // Read the query metadata if possible, to use in the UI.\n  const metadata = await tryGetQueryMetadata(cliServer, qlProgram.queryPath);\n\n  let availableMlModels: cli.MlModelInfo[] = [];\n  if (await cliServer.cliConstraints.supportsResolveMlModels()) {\n    try {\n      availableMlModels = (await cliServer.resolveMlModels(diskWorkspaceFolders)).models;\n      void logger.log(`Found available ML models at the following paths: ${availableMlModels.map(x => `'${x.path}'`).join(', ')}.`);\n    } catch (e) {\n      const message = `Couldn't resolve available ML models for ${qlProgram.queryPath}. Running the ` +\n        `query without any ML models: ${e}.`;\n      void showAndLogErrorMessage(message);\n    }\n  }\n\n  const hasMetadataFile = (await dbItem.hasMetadataFile());\n  const query = new QueryEvaluationInfo(\n    path.join(queryStorageDir, initialInfo.id),\n    dbItem.databaseUri.fsPath,\n    hasMetadataFile,\n    packConfig.dbscheme,\n    initialInfo.quickEvalPosition,\n    metadata,\n    templates\n  );\n  await query.createTimestampFile();\n\n  let upgradeDir: tmp.DirectoryResult | undefined;\n  try {\n    let upgradeQlo;\n    if (await hasNondestructiveUpgradeCapabilities(qs)) {\n      upgradeDir = await tmp.dir({ dir: upgradesTmpDir, unsafeCleanup: true });\n      upgradeQlo = await compileNonDestructiveUpgrade(qs, upgradeDir, query, qlProgram, dbItem, progress, token);\n    } else {\n      await checkDbschemeCompatibility(cliServer, qs, query, qlProgram, dbItem, progress, token);\n    }\n    let errors;\n    try {\n      errors = await query.compile(qs, qlProgram, progress, token);\n    } catch (e) {\n      if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {\n        return createSyntheticResult(query, 'Query cancelled', messages.QueryResultType.CANCELLATION);\n      } else {\n        throw e;\n      }\n    }\n\n    if (errors.length === 0) {\n      const result = await query.run(qs, upgradeQlo, availableMlModels, dbItem, progress, token);\n      if (result.resultType !== messages.QueryResultType.SUCCESS) {\n        const message = result.message || 'Failed to run query';\n        void logger.log(message);\n        void showAndLogErrorMessage(message);\n      }\n      return {\n        query,\n        result,\n        logFileLocation: result.logFileLocation,\n        dispose: () => {\n          qs.logger.removeAdditionalLogLocation(result.logFileLocation);\n        }\n      };\n    } else {\n      // Error dialogs are limited in size and scrollability,\n      // so we include a general description of the problem,\n      // and direct the user to the output window for the detailed compilation messages.\n      // However we don't show quick eval errors there so we need to display them anyway.\n      void qs.logger.log(`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`);\n\n      const formattedMessages: string[] = [];\n\n      for (const error of errors) {\n        const message = error.message || '[no error message available]';\n        const formatted = `ERROR: ${message} (${error.position.fileName}:${error.position.line}:${error.position.column}:${error.position.endLine}:${error.position.endColumn})`;\n        formattedMessages.push(formatted);\n        void qs.logger.log(formatted);\n      }\n      if (initialInfo.isQuickEval && formattedMessages.length <= 2) {\n        // If there are more than 2 error messages, they will not be displayed well in a popup\n        // and will be trimmed by the function displaying the error popup. Accordingly, we only\n        // try to show the errors if there are 2 or less, otherwise we direct the user to the log.\n        void showAndLogErrorMessage('Quick evaluation compilation failed: ' + formattedMessages.join('\\n'));\n      } else {\n        void showAndLogErrorMessage((initialInfo.isQuickEval ? 'Quick evaluation' : 'Query') + compilationFailedErrorTail);\n      }\n\n      return createSyntheticResult(query, 'Query had compilation errors', messages.QueryResultType.OTHER_ERROR);\n    }\n  } finally {\n    try {\n      await upgradeDir?.cleanup();\n    } catch (e) {\n      void qs.logger.log(`Could not clean up the upgrades dir. Reason: ${e.message || e}`);\n    }\n  }\n}\n\n/**\n * Determines the initial information for a query. This is everything of interest\n * we know about this query that is available before it is run.\n *\n * @param selectedQueryUri The Uri of the document containing the query to be run.\n * @param databaseInfo The database to run the query against.\n * @param isQuickEval true if this is a quick evaluation.\n * @param range the selection range of the query to be run. Only used if isQuickEval is true.\n * @returns The initial information for the query to be run.\n */\nexport async function createInitialQueryInfo(\n  selectedQueryUri: Uri | undefined,\n  databaseInfo: DatabaseInfo,\n  isQuickEval: boolean,\n  range?: Range\n): Promise<InitialQueryInfo> {\n  // Determine which query to run, based on the selection and the active editor.\n  const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, isQuickEval, range);\n\n  return {\n    queryPath,\n    isQuickEval,\n    isQuickQuery: isQuickQueryPath(queryPath),\n    databaseInfo,\n    id: `${path.basename(queryPath)}-${nanoid()}`,\n    start: new Date(),\n    ... (isQuickEval ? {\n      queryText: quickEvalText!, // if this query is quick eval, it must have quick eval text\n      quickEvalPosition: quickEvalPosition\n    } : {\n      queryText: await fs.readFile(queryPath, 'utf8')\n    })\n  };\n}\n\n\nconst compilationFailedErrorTail = ' compilation failed. Please make sure there are no errors in the query, the database is up to date,' +\n  ' and the query and database use the same target language. For more details on the error, go to View > Output,' +\n  ' and choose CodeQL Query Server from the dropdown.';\n\n/**\n * Create a synthetic result for a query that failed to compile.\n */\nfunction createSyntheticResult(\n  query: QueryEvaluationInfo,\n  message: string,\n  resultType: number\n): QueryWithResults {\n  return {\n    query,\n    result: {\n      evaluationTime: 0,\n      resultType: resultType,\n      queryId: -1,\n      runId: -1,\n      message\n    },\n    dispose: () => { /**/ },\n  };\n}\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 9
      },
      "contents" : {
        "text" : "import { expect } from 'chai';\nimport 'mocha';\nimport { EnvironmentVariableCollection, EnvironmentVariableMutator, Event, ExtensionContext, ExtensionMode, Memento, SecretStorage, SecretStorageChangeEvent, Uri, window } from 'vscode';\nimport * as yaml from 'js-yaml';\nimport * as tmp from 'tmp';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport * as sinon from 'sinon';\n\nimport {\n  getInitialQueryContents,\n  InvocationRateLimiter,\n  isLikelyDbLanguageFolder,\n  showBinaryChoiceDialog,\n  showBinaryChoiceWithUrlDialog,\n  showInformationMessageWithAction\n} from '../../helpers';\nimport { reportStreamProgress } from '../../commandRunner';\nimport Sinon = require('sinon');\nimport { fail } from 'assert';\n\ndescribe('helpers', () => {\n  let sandbox: sinon.SinonSandbox;\n\n  beforeEach(() => {\n    sandbox = sinon.createSandbox();\n  });\n\n  afterEach(() => {\n    sandbox.restore();\n  });\n\n  describe('Invocation rate limiter', () => {\n    // 1 January 2020\n    let currentUnixTime = 1577836800;\n\n    function createDate(dateString?: string): Date {\n      if (dateString) {\n        return new Date(dateString);\n      }\n      const numMillisecondsPerSecond = 1000;\n      return new Date(currentUnixTime * numMillisecondsPerSecond);\n    }\n\n    function createInvocationRateLimiter<T>(funcIdentifier: string, func: () => Promise<T>): InvocationRateLimiter<T> {\n      return new InvocationRateLimiter(new MockExtensionContext(), funcIdentifier, func, s => createDate(s));\n    }\n\n    it('initially invokes function', async () => {\n      let numTimesFuncCalled = 0;\n      const invocationRateLimiter = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncCalled++;\n      });\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);\n      expect(numTimesFuncCalled).to.equal(1);\n    });\n\n    it('doesn\\'t invoke function again if no time has passed', async () => {\n      let numTimesFuncCalled = 0;\n      const invocationRateLimiter = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncCalled++;\n      });\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);\n      expect(numTimesFuncCalled).to.equal(1);\n    });\n\n    it('doesn\\'t invoke function again if requested time since last invocation hasn\\'t passed', async () => {\n      let numTimesFuncCalled = 0;\n      const invocationRateLimiter = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncCalled++;\n      });\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(100);\n      currentUnixTime += 1;\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(2);\n      expect(numTimesFuncCalled).to.equal(1);\n    });\n\n    it('invokes function again immediately if requested time since last invocation is 0 seconds', async () => {\n      let numTimesFuncCalled = 0;\n      const invocationRateLimiter = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncCalled++;\n      });\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(0);\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(0);\n      expect(numTimesFuncCalled).to.equal(2);\n    });\n\n    it('invokes function again after requested time since last invocation has elapsed', async () => {\n      let numTimesFuncCalled = 0;\n      const invocationRateLimiter = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncCalled++;\n      });\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);\n      currentUnixTime += 1;\n      await invocationRateLimiter.invokeFunctionIfIntervalElapsed(1);\n      expect(numTimesFuncCalled).to.equal(2);\n    });\n\n    it('invokes functions with different rate limiters', async () => {\n      let numTimesFuncACalled = 0;\n      const invocationRateLimiterA = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncACalled++;\n      });\n      let numTimesFuncBCalled = 0;\n      const invocationRateLimiterB = createInvocationRateLimiter('funcid', async () => {\n        numTimesFuncBCalled++;\n      });\n      await invocationRateLimiterA.invokeFunctionIfIntervalElapsed(100);\n      await invocationRateLimiterB.invokeFunctionIfIntervalElapsed(100);\n      expect(numTimesFuncACalled).to.equal(1);\n      expect(numTimesFuncBCalled).to.equal(1);\n    });\n  });\n\n  describe('codeql-database.yml tests', () => {\n    let dir: tmp.DirResult;\n    beforeEach(() => {\n      dir = tmp.dirSync();\n      const contents = yaml.dump({\n        primaryLanguage: 'cpp'\n      });\n      fs.writeFileSync(path.join(dir.name, 'codeql-database.yml'), contents, 'utf8');\n    });\n\n    afterEach(() => {\n      dir.removeCallback();\n    });\n\n    it('should get initial query contents when language is known', () => {\n      expect(getInitialQueryContents('cpp', 'hucairz')).to.eq('import cpp\\n\\nselect \"\"');\n    });\n\n    it('should get initial query contents when dbscheme is known', () => {\n      expect(getInitialQueryContents('', 'semmlecode.cpp.dbscheme')).to.eq('import cpp\\n\\nselect \"\"');\n    });\n\n    it('should get initial query contents when nothing is known', () => {\n      expect(getInitialQueryContents('', 'hucairz')).to.eq('select \"\"');\n    });\n  });\n\n  it('should find likely db language folders', () => {\n    expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;\n    expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;\n  });\n\n  class MockExtensionContext implements ExtensionContext {\n    extensionMode: ExtensionMode = 3;\n    subscriptions: { dispose(): unknown }[] = [];\n    workspaceState: Memento = new MockMemento();\n    globalState = new MockGlobalStorage();\n    extensionPath = '';\n    asAbsolutePath(_relativePath: string): string {\n      throw new Error('Method not implemented.');\n    }\n    storagePath = '';\n    globalStoragePath = '';\n    logPath = '';\n    extensionUri = Uri.parse('');\n    environmentVariableCollection = new MockEnvironmentVariableCollection();\n    secrets = new MockSecretStorage();\n    storageUri = Uri.parse('');\n    globalStorageUri = Uri.parse('');\n    logUri = Uri.parse('');\n    extension: any;\n  }\n\n  class MockEnvironmentVariableCollection implements EnvironmentVariableCollection {\n    persistent = false;\n    replace(_variable: string, _value: string): void {\n      throw new Error('Method not implemented.');\n    }\n    append(_variable: string, _value: string): void {\n      throw new Error('Method not implemented.');\n    }\n    prepend(_variable: string, _value: string): void {\n      throw new Error('Method not implemented.');\n    }\n    get(_variable: string): EnvironmentVariableMutator | undefined {\n      throw new Error('Method not implemented.');\n    }\n    forEach(_callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, _thisArg?: any): void {\n      throw new Error('Method not implemented.');\n    }\n    delete(_variable: string): void {\n      throw new Error('Method not implemented.');\n    }\n    clear(): void {\n      throw new Error('Method not implemented.');\n    }\n  }\n\n  class MockMemento implements Memento {\n    keys(): readonly string[] {\n      throw new Error('Method not implemented.');\n    }\n    map = new Map<any, any>();\n\n    /**\n     * Return a value.\n     *\n     * @param key A string.\n     * @param defaultValue A value that should be returned when there is no\n     * value (`undefined`) with the given key.\n     * @return The stored value or the defaultValue.\n     */\n    get<T>(key: string, defaultValue?: T): T {\n      return this.map.has(key) ? this.map.get(key) : defaultValue;\n    }\n\n    /**\n     * Store a value. The value must be JSON-stringifyable.\n     *\n     * @param key A string.\n     * @param value A value. MUST not contain cyclic references.\n     */\n    async update(key: string, value: any): Promise<void> {\n      this.map.set(key, value);\n    }\n  }\n\n  class MockGlobalStorage extends MockMemento {\n    public setKeysForSync(_keys: string[]): void {\n      return;\n    }\n  }\n\n  class MockSecretStorage implements SecretStorage {\n    get(_key: string): Thenable<string | undefined> {\n      throw new Error('Method not implemented.');\n    }\n    store(_key: string, _value: string): Thenable<void> {\n      throw new Error('Method not implemented.');\n    }\n    delete(_key: string): Thenable<void> {\n      throw new Error('Method not implemented.');\n    }\n    onDidChange!: Event<SecretStorageChangeEvent>;\n  }\n\n  it('should report stream progress', () => {\n    const spy = sandbox.spy();\n    const mockReadable = {\n      on: sandbox.spy()\n    };\n    const max = 1024 * 1024 * 4;\n    const firstStep = (1024 * 1024) + (1024 * 600);\n    const secondStep = 1024 * 1024 * 2;\n\n    (reportStreamProgress as any)(mockReadable, 'My prefix', max, spy);\n\n    // now pretend that we have received some messages\n    mockReadable.on.getCall(0).args[1]({ length: firstStep });\n    mockReadable.on.getCall(0).args[1]({ length: secondStep });\n\n    expect(spy).to.have.callCount(3);\n    expect(spy).to.have.been.calledWith({\n      step: 0,\n      maxStep: max,\n      message: 'My prefix [0.0 MB of 4.0 MB]',\n    });\n    expect(spy).to.have.been.calledWith({\n      step: firstStep,\n      maxStep: max,\n      message: 'My prefix [1.6 MB of 4.0 MB]',\n    });\n    expect(spy).to.have.been.calledWith({\n      step: firstStep + secondStep,\n      maxStep: max,\n      message: 'My prefix [3.6 MB of 4.0 MB]',\n    });\n  });\n\n  it('should report stream progress when total bytes unknown', () => {\n    const spy = sandbox.spy();\n    const mockReadable = {\n      on: sandbox.spy()\n    };\n    (reportStreamProgress as any)(mockReadable, 'My prefix', undefined, spy);\n\n    // There are no listeners registered to this readable\n    expect(mockReadable.on).not.to.have.been.called;\n\n    expect(spy).to.have.callCount(1);\n    expect(spy).to.have.been.calledWith({\n      step: 1,\n      maxStep: 2,\n      message: 'My prefix (Size unknown)',\n    });\n  });\n\n  describe('open dialog', () => {\n    let showInformationMessageSpy: Sinon.SinonStub;\n    beforeEach(() => {\n      showInformationMessageSpy = sandbox.stub(window, 'showInformationMessage');\n    });\n\n    it('should show a binary choice dialog and return `yes`', (done) => {\n      // pretend user chooses 'yes'\n      showInformationMessageSpy.onCall(0).resolvesArg(2);\n      const res = showBinaryChoiceDialog('xxx');\n      res.then((val) => {\n        expect(val).to.eq(true);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show a binary choice dialog and return `no`', (done) => {\n      // pretend user chooses 'no'\n      showInformationMessageSpy.onCall(0).resolvesArg(3);\n      const res = showBinaryChoiceDialog('xxx');\n      res.then((val) => {\n        expect(val).to.eq(false);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show an info dialog and confirm the action', (done) => {\n      // pretend user chooses to run action\n      showInformationMessageSpy.onCall(0).resolvesArg(1);\n      const res = showInformationMessageWithAction('xxx', 'yyy');\n      res.then((val) => {\n        expect(val).to.eq(true);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show an action dialog and avoid choosing the action', (done) => {\n      // pretend user does not choose to run action\n      showInformationMessageSpy.onCall(0).resolves(undefined);\n      const res = showInformationMessageWithAction('xxx', 'yyy');\n      res.then((val) => {\n        expect(val).to.eq(false);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show a binary choice dialog with a url and return `yes`', (done) => {\n      // pretend user clicks on the url twice and then clicks 'yes'\n      showInformationMessageSpy.onCall(0).resolvesArg(2);\n      showInformationMessageSpy.onCall(1).resolvesArg(2);\n      showInformationMessageSpy.onCall(2).resolvesArg(3);\n      const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');\n      res.then((val) => {\n        expect(val).to.eq(true);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show a binary choice dialog with a url and return `no`', (done) => {\n      // pretend user clicks on the url twice and then clicks 'no'\n      showInformationMessageSpy.onCall(0).resolvesArg(2);\n      showInformationMessageSpy.onCall(1).resolvesArg(2);\n      showInformationMessageSpy.onCall(2).resolvesArg(4);\n      const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');\n      res.then((val) => {\n        expect(val).to.eq(false);\n        done();\n      }).catch(e => fail(e));\n    });\n\n    it('should show a binary choice dialog and exit after clcking `more info` 5 times', (done) => {\n      // pretend user clicks on the url twice and then clicks 'no'\n      showInformationMessageSpy.onCall(0).resolvesArg(2);\n      showInformationMessageSpy.onCall(1).resolvesArg(2);\n      showInformationMessageSpy.onCall(2).resolvesArg(2);\n      showInformationMessageSpy.onCall(3).resolvesArg(2);\n      showInformationMessageSpy.onCall(4).resolvesArg(2);\n      const res = showBinaryChoiceWithUrlDialog('xxx', 'invalid:url');\n      res.then((val) => {\n        // No choie was made\n        expect(val).to.eq(undefined);\n        expect(showInformationMessageSpy.getCalls().length).to.eq(5);\n        done();\n      }).catch(e => fail(e));\n    });\n  });\n});\n"
      }
    }, {
      "location" : {
        "uri" : "extensions/ql-vscode/test/pure-tests/disposable-object.test.ts",
        "uriBaseId" : "%SRCROOT%",
        "index" : 10
      },
      "contents" : {
        "text" : "import 'chai';\nimport 'chai/register-should';\nimport 'sinon-chai';\nimport * as sinon from 'sinon';\nimport 'mocha';\n\nimport { DisposableObject } from '../../src/pure/disposable-object';\nimport { expect } from 'chai';\n\ndescribe('DisposableObject and DisposeHandler', () => {\n\n  let disposable1: { dispose: sinon.SinonSpy };\n  let disposable2: { dispose: sinon.SinonSpy };\n  let disposable3: { dispose: sinon.SinonSpy };\n  let disposable4: { dispose: sinon.SinonSpy };\n  let disposableObject: any;\n  let nestedDisposableObject: any;\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(() => {\n    sandbox.restore();\n    disposable1 = { dispose: sandbox.spy() };\n    disposable2 = { dispose: sandbox.spy() };\n    disposable3 = { dispose: sandbox.spy() };\n    disposable4 = { dispose: sandbox.spy() };\n\n    disposableObject = new MyDisposableObject();\n    nestedDisposableObject = new MyDisposableObject();\n  });\n\n  afterEach(() => {\n    sandbox.restore();\n  });\n\n  it('should dispose tracked and pushed objects', () => {\n    disposableObject.push(disposable1);\n    disposableObject.push(disposable2);\n    disposableObject.track(nestedDisposableObject);\n    nestedDisposableObject.track(disposable3);\n\n    disposableObject.dispose();\n\n    expect(disposable1.dispose).to.have.been.called;\n    expect(disposable2.dispose).to.have.been.called;\n    expect(disposable3.dispose).to.have.been.called;\n\n    // pushed items must be called in reverse order\n    sinon.assert.callOrder(disposable2.dispose, disposable1.dispose);\n\n    // now that disposableObject has been disposed, subsequent disposals are\n    // no-ops\n    disposable1.dispose.resetHistory();\n    disposable2.dispose.resetHistory();\n    disposable3.dispose.resetHistory();\n\n    disposableObject.dispose();\n\n    expect(disposable1.dispose).not.to.have.been.called;\n    expect(disposable2.dispose).not.to.have.been.called;\n    expect(disposable3.dispose).not.to.have.been.called;\n  });\n\n  it('should dispose and stop tracking objects', () => {\n    disposableObject.track(disposable1);\n    disposableObject.disposeAndStopTracking(disposable1);\n\n    expect(disposable1.dispose).to.have.been.called;\n    disposable1.dispose.resetHistory();\n\n    disposableObject.dispose();\n    expect(disposable1.dispose).not.to.have.been.called;\n  });\n\n  it('should avoid disposing an object that is not tracked', () => {\n    disposableObject.push(disposable1);\n    disposableObject.disposeAndStopTracking(disposable1);\n\n    expect(disposable1.dispose).not.to.have.been.called;\n\n    disposableObject.dispose();\n    expect(disposable1.dispose).to.have.been.called;\n  });\n\n  it('ahould use a dispose handler', () => {\n    const handler = (d: any) => (d === disposable1 || d === disposable3 || d === nestedDisposableObject)\n      ? d.dispose(handler)\n      : void (0);\n\n    disposableObject.push(disposable1);\n    disposableObject.push(disposable2);\n    disposableObject.track(nestedDisposableObject);\n    nestedDisposableObject.track(disposable3);\n    nestedDisposableObject.track(disposable4);\n\n    disposableObject.dispose(handler);\n\n    expect(disposable1.dispose).to.have.been.called;\n    expect(disposable2.dispose).not.to.have.been.called;\n    expect(disposable3.dispose).to.have.been.called;\n    expect(disposable4.dispose).not.to.have.been.called;\n\n    // now that disposableObject has been disposed, subsequent disposals are\n    // no-ops\n    disposable1.dispose.resetHistory();\n    disposable2.dispose.resetHistory();\n    disposable3.dispose.resetHistory();\n    disposable4.dispose.resetHistory();\n\n    disposableObject.dispose();\n\n    expect(disposable1.dispose).not.to.have.been.called;\n    expect(disposable2.dispose).not.to.have.been.called;\n    expect(disposable3.dispose).not.to.have.been.called;\n    expect(disposable4.dispose).not.to.have.been.called;\n  });\n\n  class MyDisposableObject extends DisposableObject {\n    constructor() {\n      super();\n    }\n  }\n});\n"
      }
    } ],
    "results" : [ {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/cli.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 0
          },
          "region" : {
            "startLine" : 211,
            "startColumn" : 3,
            "endLine" : 213,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "b8c591ba929c04b3:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/extension.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 1
          },
          "region" : {
            "startLine" : 166,
            "startColumn" : 3,
            "endColumn" : 32
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "f25bab33c73a59c3:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/logging.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 2
          },
          "region" : {
            "startLine" : 151,
            "startColumn" : 3,
            "endLine" : 155,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "6fdd1bd9f00e8bc8:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/pure/disposable-object.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 3
          },
          "region" : {
            "startLine" : 5,
            "startColumn" : 3,
            "endColumn" : 18
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "76f5b5b432081148:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/pure/disposable-object.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 3
          },
          "region" : {
            "startLine" : 65,
            "startColumn" : 3,
            "endLine" : 84,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "73c3b2d36bd682c3:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/query-results.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 4
          },
          "region" : {
            "startLine" : 54,
            "startColumn" : 3,
            "endColumn" : 23
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "a753440ec58c92e1:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/queryserver-client.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 5
          },
          "region" : {
            "startLine" : 32,
            "startColumn" : 3,
            "endLine" : 42,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "d17936e7ccfe4023:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/telemetry.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 6
          },
          "region" : {
            "startLine" : 129,
            "startColumn" : 3,
            "endLine" : 132,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "4591af6a6181df91:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/test-ui.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 7
          },
          "region" : {
            "startLine" : 54,
            "startColumn" : 3,
            "endLine" : 58,
            "endColumn" : 4
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "6566573d37945133:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/run-queries.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 8
          },
          "region" : {
            "startLine" : 327,
            "startColumn" : 3,
            "endColumn" : 32
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "3392644b8e58bdcd:1",
        "primaryLocationStartColumnFingerprint" : "0"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 9
          },
          "region" : {
            "startLine" : 150,
            "startColumn" : 22,
            "endColumn" : 40
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "da376cf6b390c728:1",
        "primaryLocationStartColumnFingerprint" : "17"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/test/pure-tests/disposable-object.test.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 10
          },
          "region" : {
            "startLine" : 12,
            "startColumn" : 22,
            "endColumn" : 45
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "a8fca1d11b4e7ba9:1",
        "primaryLocationStartColumnFingerprint" : "19"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/test/pure-tests/disposable-object.test.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 10
          },
          "region" : {
            "startLine" : 13,
            "startColumn" : 22,
            "endColumn" : 45
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "bb1d0421397e4d8:1",
        "primaryLocationStartColumnFingerprint" : "19"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/test/pure-tests/disposable-object.test.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 10
          },
          "region" : {
            "startLine" : 14,
            "startColumn" : 22,
            "endColumn" : 45
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "6e66feb30ee7663f:1",
        "primaryLocationStartColumnFingerprint" : "19"
      }
    }, {
      "ruleId" : "remote-query",
      "ruleIndex" : 0,
      "rule" : {
        "id" : "remote-query",
        "index" : 0
      },
      "message" : {
        "text" : "Dispose method"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "extensions/ql-vscode/test/pure-tests/disposable-object.test.ts",
            "uriBaseId" : "%SRCROOT%",
            "index" : 10
          },
          "region" : {
            "startLine" : 15,
            "startColumn" : 22,
            "endColumn" : 45
          }
        }
      } ],
      "partialFingerprints" : {
        "primaryLocationLineHash" : "a748ac7e30673354:1",
        "primaryLocationStartColumnFingerprint" : "19"
      }
    } ],
    "columnKind" : "utf16CodeUnits",
    "properties" : {
      "semmle.formatSpecifier" : "sarif-latest"
    }
  } ]
}
