Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7354446b | ||
|
|
d909f98fcb | ||
|
|
8c2db75886 | ||
|
|
73e560e6da | ||
|
|
ada1180468 | ||
|
|
d1e70816aa | ||
|
|
df936167d5 | ||
|
|
0327ec358c | ||
|
|
7a78fca252 | ||
|
|
10e86f1835 | ||
|
|
dbaed3acd5 | ||
|
|
6830bdd28d | ||
|
|
e316decae1 | ||
|
|
a86c1ce69b | ||
|
|
01418cba26 | ||
|
|
35d98f62e8 | ||
|
|
b30121b84c | ||
|
|
fd15217a20 | ||
|
|
1d03702334 | ||
|
|
c47029e9eb | ||
|
|
5fdfb44c2e | ||
|
|
6e40478440 | ||
|
|
9e68b4f061 | ||
|
|
0f82875b9d | ||
|
|
fd52f66f6d | ||
|
|
42cfa45d7e | ||
|
|
5023f91475 | ||
|
|
48df77f673 | ||
|
|
839665588f | ||
|
|
ab31d86a8d | ||
|
|
f2d07729b9 | ||
|
|
707cba4ac9 | ||
|
|
6304fe0e30 | ||
|
|
be9084e83e | ||
|
|
57d856ff5c | ||
|
|
343e9e5466 | ||
|
|
f2620c65af | ||
|
|
c5fe58db37 | ||
|
|
47b57c01f3 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -9,4 +9,4 @@ Replace this with a description of the changes your pull request makes.
|
||||
|
||||
- [ ] [CHANGELOG.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md) has been updated to incorporate all user visible changes made by this pull request.
|
||||
- [ ] Issues have been created for any UI or other user-facing changes made by this pull request.
|
||||
- [ ] `@github/docs-content-dsp` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
|
||||
- [ ] `@github/docs-content-codeql` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
|
||||
|
||||
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name: Build Extension
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
@@ -125,7 +126,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
version: ['v2.2.6', 'v2.3.3', 'v2.4.2']
|
||||
version: ['v2.2.6', 'v2.3.3', 'v2.4.5', 'v2.4.6']
|
||||
env:
|
||||
CLI_VERSION: ${{ matrix.version }}
|
||||
TEST_CODEQL_PATH: '${{ github.workspace }}/codeql'
|
||||
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
|
||||
@@ -25,12 +25,11 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
* Follow the [style guide][style].
|
||||
* Write tests. Tests that don't require the VS Code API are located [here](extensions/ql-vscode/test). Integration tests that do require the VS Code API are located [here](extensions/ql-vscode/src/vscode-tests).
|
||||
* Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
* Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
* Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Setting up a local build
|
||||
|
||||
Make sure you have a fairly recent version of vscode (>1.32) and are using nodejs
|
||||
version >=v10.13.0. (Tested on v10.15.1 and v10.16.0).
|
||||
Make sure you have installed recent versions of vscode (>= v1.52), node (>=12.16), and npm (>= 7.5.2). Earlier versions will probably work, but we no longer test against them.
|
||||
|
||||
### Installing all packages
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ To see what has changed in the last few versions of the extension, see the [Chan
|
||||
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/github/codeql).
|
||||
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.
|
||||
|
||||
|
||||
## Project goals and scope
|
||||
|
||||
This project will track new feature development in CodeQL and, whenever appropriate, bring that functionality to the Visual Studio Code experience.
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
## 1.4.5 - 22 March 2021
|
||||
|
||||
- Avoid showing an error popup when user runs a query without `@kind` metadata. [#801](https://github.com/github/vscode-codeql/pull/801)
|
||||
- Fix running of tests when the `ms-python` extension is installed. [#803](https://github.com/github/vscode-codeql/pull/803)
|
||||
|
||||
## 1.4.4 - 19 March 2021
|
||||
|
||||
- Introduce evaluator options for saving intermediate results to the disk cache (`codeQL.runningQueries.saveCache`) and for limiting the size of this cache (`codeQL.runningQueries.cacheSize`). [#778](https://github.com/github/vscode-codeql/pull/778)
|
||||
- Respect the `codeQL.runningQueries.numberOfThreads` setting when creating SARIF files during result interpretation. [#771](https://github.com/github/vscode-codeql/pull/771)
|
||||
- Allow using raw LGTM project slugs for fetching LGTM databases. [#769](https://github.com/github/vscode-codeql/pull/769)
|
||||
- Better error messages when BQRS interpretation fails to produce SARIF. [#770](https://github.com/github/vscode-codeql/pull/770)
|
||||
- Implement sorting of the query history view by name, date, and results count. [#777](https://github.com/github/vscode-codeql/pull/777)
|
||||
- Add a configuration option to pass additional arguments to the CLI when running tests. [#785](https://github.com/github/vscode-codeql/pull/785)
|
||||
- Introduce option to view query results as CSV. [#784](https://github.com/github/vscode-codeql/pull/784)
|
||||
- Add some snippets for commonly used QL statements. [#782](https://github.com/github/vscode-codeql/pull/782)
|
||||
- More descriptive error messages on QL test failures. [#788](https://github.com/github/vscode-codeql/pull/788)
|
||||
|
||||
## 1.4.3 - 22 February 2021
|
||||
|
||||
- Avoid displaying an error when removing orphaned databases and the storage folder does not exist. [#748](https://github.com/github/vscode-codeql/pull/748)
|
||||
- Add better error messages when AST Viewer is unable to create an AST. [#753](https://github.com/github/vscode-codeql/pull/753)
|
||||
- Cache AST viewing operations so that subsequent calls to view the AST of a single file will be extremely fast. [#753](https://github.com/github/vscode-codeql/pull/753)
|
||||
- Ensure CodeQL version in status bar updates correctly when version changes. [#754](https://github.com/github/vscode-codeql/pull/754)
|
||||
- Avoid deleting the quick query file when it is re-opened. [#747](https://github.com/github/vscode-codeql/pull/747)
|
||||
|
||||
## 1.4.2 - 2 February 2021
|
||||
|
||||
- Add a status bar item for the CodeQL CLI to show the current version. [#741](https://github.com/github/vscode-codeql/pull/741)
|
||||
- Fix version constraint for flagging CLI support of non-destructive updates. [#744](https://github.com/github/vscode-codeql/pull/744)
|
||||
- Add a _More Information_ button in the telemetry popup that opens [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md) in a browser tab. [#742](https://github.com/github/vscode-codeql/pull/742)
|
||||
- Add a _More Information_ button in the telemetry popup that opens the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code) in a browser tab. [#742](https://github.com/github/vscode-codeql/pull/742)
|
||||
|
||||
## 1.4.1 - 29 January 2021
|
||||
|
||||
@@ -14,7 +41,7 @@
|
||||
|
||||
- Fix bug where databases are not reregistered when the query server restarts. [#734](https://github.com/github/vscode-codeql/pull/734)
|
||||
- Fix bug where upgrade requests were erroneously being marked as failed. [#734](https://github.com/github/vscode-codeql/pull/734)
|
||||
- On a strictly opt-in basis, collect anonymized usage data from the VS Code extension, helping improve CodeQL's usability and performance. See [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md) for more information on exactly what data is collected and what it is used for. [#611](https://github.com/github/vscode-codeql/pull/611)
|
||||
- On a strictly opt-in basis, collect anonymized usage data from the VS Code extension, helping improve CodeQL's usability and performance. See the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code) for more information on exactly what data is collected and what it is used for. [#611](https://github.com/github/vscode-codeql/pull/611)
|
||||
|
||||
## 1.3.10 - 20 January 2021
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CodeQL extension for Visual Studio Code
|
||||
|
||||
This project is an extension for Visual Studio Code that adds rich language support for [CodeQL](https://help.semmle.com/codeql) and allows you to easily find problems in codebases. In particular, the extension:
|
||||
This project is an extension for Visual Studio Code that adds rich language support for [CodeQL](https://codeql.github.com/docs/) and allows you to easily find problems in codebases. In particular, the extension:
|
||||
|
||||
- Enables you to use CodeQL to query databases generated from source code.
|
||||
- Shows the flow of data through the results of path queries, which is essential for triaging security results.
|
||||
@@ -12,7 +12,7 @@ To see what has changed in the last few versions of the extension, see the [Chan
|
||||
## Quick start overview
|
||||
|
||||
The information in this `README` file describes the quickest way to start using CodeQL.
|
||||
For information about other configurations, see the separate [CodeQL help](https://help.semmle.com/codeql/codeql-for-vscode.html).
|
||||
For information about other configurations, see the separate [CodeQL help](https://codeql.github.com/docs/codeql-for-visual-studio-code/).
|
||||
|
||||
### Quick start: Installing and configuring the extension
|
||||
|
||||
@@ -40,9 +40,9 @@ The CodeQL extension requires a minimum of Visual Studio Code 1.39. Older versio
|
||||
|
||||
### Checking access to the CodeQL CLI
|
||||
|
||||
The extension uses the [CodeQL CLI](https://help.semmle.com/codeql/codeql-cli.html) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command.
|
||||
The extension uses the [CodeQL CLI](https://codeql.github.com/docs/codeql-cli/) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command.
|
||||
|
||||
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#configuring-access-to-the-codeql-cli).
|
||||
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#configuring-access-to-the-codeql-cli).
|
||||
|
||||
If you have any difficulty with CodeQL CLI access, see the **CodeQL Extension Log** in the **Output** view for any error messages.
|
||||
|
||||
@@ -52,7 +52,7 @@ When you're working with CodeQL, you need access to the standard CodeQL librarie
|
||||
Initially, we recommend that you clone and use the ready-to-use [starter workspace](https://github.com/github/vscode-codeql-starter/).
|
||||
This includes libraries and queries for the main supported languages, with folders set up ready for your custom queries. After cloning the workspace (use `git clone --recursive`), you can use it in the same way as any other VS Code workspace—with the added advantage that you can easily update the CodeQL libraries.
|
||||
|
||||
For information about configuring an existing workspace for CodeQL, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#updating-an-existing-workspace-for-codeql).
|
||||
For information about configuring an existing workspace for CodeQL, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#updating-an-existing-workspace-for-codeql).
|
||||
|
||||
## Upgrading CodeQL standard libraries
|
||||
|
||||
@@ -75,7 +75,7 @@ You can find all the commands contributed by the extension in the Command Palett
|
||||
|
||||
### Importing a database from LGTM
|
||||
|
||||
While you can use the [CodeQL CLI to create your own databases](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html), the simplest way to start is by downloading a database from LGTM.com.
|
||||
While you can use the [CodeQL CLI to create your own databases](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/), the simplest way to start is by downloading a database from LGTM.com.
|
||||
|
||||
1. Open [LGTM.com](https://lgtm.com/#explore) in your browser.
|
||||
1. Search for a project you're interested in, for example [Apache Kafka](https://lgtm.com/projects/g/apache/kafka).
|
||||
@@ -100,11 +100,11 @@ If there are any problems running a query, a notification is displayed in the bo
|
||||
|
||||
## What next?
|
||||
|
||||
For more information about the CodeQL extension, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode.html). Otherwise, you could:
|
||||
For more information about the CodeQL extension, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/). Otherwise, you could:
|
||||
|
||||
- [Create a database for a different codebase](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html).
|
||||
- [Create a database for a different codebase](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/).
|
||||
- [Try out variant analysis](https://help.semmle.com/QL/learn-ql/ql-training.html).
|
||||
- [Learn more about CodeQL](https://help.semmle.com/QL/learn-ql/).
|
||||
- [Learn more about CodeQL](https://codeql.github.com/docs/).
|
||||
- [Read how security researchers use CodeQL to find CVEs](https://securitylab.github.com/research).
|
||||
|
||||
## License
|
||||
@@ -113,4 +113,4 @@ The CodeQL extension for Visual Studio Code is [licensed](LICENSE.md) under the
|
||||
|
||||
## Data and Telemetry
|
||||
|
||||
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days. Please see [telemetry](TELEMETRY.md) for more information.
|
||||
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days. For more information about telemetry, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code).
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# Telemetry in the CodeQL extension for VS Code
|
||||
|
||||
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days.
|
||||
|
||||
## Why do you collect data?
|
||||
|
||||
GitHub collects aggregated, anonymous usage data and metrics to help us improve CodeQL for VS Code. IP addresses and installation IDs are collected only to ensure that anonymous data is not duplicated during aggregation.
|
||||
|
||||
## What data is collected
|
||||
|
||||
If you opt in, GitHub collects the following information related to the usage of the extension. The data collected are:
|
||||
|
||||
- The identifiers of any CodeQL-related [VS Code commands](https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_command-palette) that are run
|
||||
- For each command: the timestamp, time taken, and whether or not the command completed successfully
|
||||
- VS Code and extension version
|
||||
- Randomly generated GUID that uniquely identifies a CodeQL extension installation. (Discarded before aggregation.)
|
||||
- IP address of the client sending the telemetry data. (Discarded before aggregation.)
|
||||
- Whether or not the `codeQL.canary` setting is enabled and set to `true`
|
||||
|
||||
## How long will data be retained?
|
||||
|
||||
IP address and GUIDs will be retained for a maximum of 30 days. Anonymous, aggregated data that includes command identifiers, run times, and timestamps will be retained for a maximum of 180 days.
|
||||
|
||||
## Who will have access to this data?
|
||||
|
||||
IP address and GUIDs will only be available to the core developers of CodeQL. Aggregated data will be available to GitHub employees.
|
||||
|
||||
## What data is **NOT** collected?
|
||||
|
||||
We only collect the minimal amount of data we need to answer the questions about how our users are experiencing this product. To that end, we do not collect the following information:
|
||||
|
||||
- No GitHub user ID
|
||||
- No CodeQL database names or contents
|
||||
- No contents of CodeQL queries
|
||||
- No filesystem paths.
|
||||
|
||||
## How do I disable telemetry reporting?
|
||||
|
||||
When telemetry collection is disabled, no data will be sent to GitHub servers.
|
||||
|
||||
You can disable telemetry collection by setting `codeQL.telemetry.enableTelemetry` to `false` in [your settings](https://code.visualstudio.com/docs/getstarted/settings#_settings-editor). Telemetry collection is _disabled_ by default.
|
||||
|
||||
Additionally, telemetry collection will be disabled if the global `telemetry.enableTelemetry` setting is set to `false`. For more information on global telemetry collection, see [Microsoft’s documentation](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting).
|
||||
|
||||
## More information
|
||||
|
||||
See GitHub's [Privacy Statement](https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-privacy-statement) and [Terms of Service](https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-terms-of-service) for more information.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
|
||||
15
extensions/ql-vscode/media/dark/sort-num.svg
Normal file
15
extensions/ql-vscode/media/dark/sort-num.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="none"
|
||||
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" fill="#C5C5C5"/>
|
||||
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" fill="#C5C5C5" />
|
||||
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" fill="#C5C5C5"/>
|
||||
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" fill="#C5C5C5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
15
extensions/ql-vscode/media/light/sort-num.svg
Normal file
15
extensions/ql-vscode/media/light/sort-num.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" />
|
||||
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" />
|
||||
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" />
|
||||
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
12273
extensions/ql-vscode/package-lock.json
generated
12273
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.5",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -106,6 +106,12 @@
|
||||
"path": "./out/syntaxes/dbscheme.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"snippets": [
|
||||
{
|
||||
"language": "ql",
|
||||
"path": "./snippets.json"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "CodeQL",
|
||||
@@ -114,7 +120,7 @@
|
||||
"scope": "machine",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. This overrides all other CodeQL CLI settings."
|
||||
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. If empty, the extension will look for a CodeQL executable on your shell PATH, or if CodeQL is not on your PATH, download and manage its own CodeQL executable."
|
||||
},
|
||||
"codeQL.runningQueries.numberOfThreads": {
|
||||
"type": "integer",
|
||||
@@ -123,6 +129,21 @@
|
||||
"maximum": 1024,
|
||||
"description": "Number of threads for running queries."
|
||||
},
|
||||
"codeQL.runningQueries.saveCache": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "window",
|
||||
"description": "Aggressively save intermediate results to the disk cache. This may speed up subsequent queries if they are similar. Be aware that using this option will greatly increase disk usage and initial evaluation time."
|
||||
},
|
||||
"codeQL.runningQueries.cacheSize": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"minimum": 1024,
|
||||
"description": "Maximum size of the disk cache (in MB). Leave blank to allow the evaluator to automatically adjust the size of the disk cache based on the size of the codebase and the complexity of the queries being executed."
|
||||
},
|
||||
"codeQL.runningQueries.timeout": {
|
||||
"type": [
|
||||
"integer",
|
||||
@@ -160,12 +181,18 @@
|
||||
"codeQL.resultsDisplay.pageSize": {
|
||||
"type": "integer",
|
||||
"default": 200,
|
||||
"description": "Number of queries displayed per page of the results view."
|
||||
"description": "Max number of query results to display per page in the results view."
|
||||
},
|
||||
"codeQL.queryHistory.format": {
|
||||
"type": "string",
|
||||
"default": "[%t] %q on %d - %s",
|
||||
"description": "Default string for how to label query history items. %t is the time of the query, %q is the query name, %d is the database name, and %s is a status string."
|
||||
"default": "%q on %d - %s, %r result count [%t]",
|
||||
"description": "Default string for how to label query history items. %t is the time of the query, %q is the query name, %d is the database name, %r is the number of results, and %s is a status string."
|
||||
},
|
||||
"codeQL.runningTests.additionalTestArguments": {
|
||||
"scope": "machine",
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"markdownDescription": "Additional command line arguments to pass to the CLI when [running tests](https://codeql.github.com/docs/codeql-cli/manual/test-run/). This setting should be an array of strings, each containing an argument to be passed."
|
||||
},
|
||||
"codeQL.runningTests.numberOfThreads": {
|
||||
"scope": "window",
|
||||
@@ -179,7 +206,7 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"scope": "application",
|
||||
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see [TELEMETRY.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md)"
|
||||
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)"
|
||||
},
|
||||
"codeQL.telemetry.logTelemetry": {
|
||||
"type": "boolean",
|
||||
@@ -342,6 +369,30 @@
|
||||
"dark": "media/dark/trash.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"title": "Sort by Name",
|
||||
"icon": {
|
||||
"light": "media/light/sort-alpha.svg",
|
||||
"dark": "media/dark/sort-alpha.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByDate",
|
||||
"title": "Sort by Query Date",
|
||||
"icon": {
|
||||
"light": "media/light/sort-date.svg",
|
||||
"dark": "media/dark/sort-date.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByCount",
|
||||
"title": "Sort by Results Count",
|
||||
"icon": {
|
||||
"light": "media/light/sort-num.svg",
|
||||
"dark": "media/dark/sort-num.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"title": "Show Query Log"
|
||||
@@ -351,8 +402,12 @@
|
||||
"title": "Show Query Text"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarif",
|
||||
"title": "View SARIF"
|
||||
"command": "codeQLQueryHistory.viewCsvResults",
|
||||
"title": "View Results (CSV)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"title": "View Results (SARIF)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
@@ -446,6 +501,21 @@
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByDate",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByCount",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.clear",
|
||||
"when": "view == codeQLAstViewer",
|
||||
@@ -509,7 +579,12 @@
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarif",
|
||||
"command": "codeQLQueryHistory.viewCsvResults",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
|
||||
},
|
||||
@@ -636,7 +711,11 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarif",
|
||||
"command": "codeQLQueryHistory.viewCsvResults",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -651,6 +730,18 @@
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByDate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByCount",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.gotoCode",
|
||||
"when": "false"
|
||||
@@ -771,7 +862,7 @@
|
||||
"@types/gulp-sourcemaps": "0.0.32",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/jszip": "~3.1.6",
|
||||
"@types/mocha": "^8.0.4",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/node": "^12.14.1",
|
||||
"@types/node-fetch": "~2.5.2",
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
|
||||
134
extensions/ql-vscode/snippets.json
Normal file
134
extensions/ql-vscode/snippets.json
Normal file
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"Query Metadata": {
|
||||
"prefix": "querymetadata",
|
||||
"body": [
|
||||
"/**",
|
||||
" * @name $1",
|
||||
" * @description $2",
|
||||
" * @kind $3",
|
||||
" * @id $4",
|
||||
" * @tags $5",
|
||||
" */"
|
||||
],
|
||||
"description": "Metadata for a query"
|
||||
},
|
||||
"Class": {
|
||||
"prefix": "class",
|
||||
"body": ["class $1 extends $2 {", "\t$0", "}"],
|
||||
"description": "A class"
|
||||
},
|
||||
"From/Where/Select": {
|
||||
"prefix": "from",
|
||||
"body": ["from $1", "where $2", "select $3"],
|
||||
"description": "A from/where/select statement"
|
||||
},
|
||||
"Predicate": {
|
||||
"prefix": "predicate",
|
||||
"body": ["predicate $1($2) {", "\t$0", "}"],
|
||||
"description": "A predicate"
|
||||
},
|
||||
"Dataflow Tracking Class": {
|
||||
"prefix": "dataflowtracking",
|
||||
"body": [
|
||||
"class $1 extends DataFlow::Configuration {",
|
||||
"\t$1() { this = \"$1\" }",
|
||||
"\t",
|
||||
"\toverride predicate isSource(DataFlow::Node node) {",
|
||||
"\t\t${2:none()}",
|
||||
"\t}",
|
||||
"\t",
|
||||
"\toverride predicate isSink(DataFlow::Node node) {",
|
||||
"\t\t${3:none()}",
|
||||
"\t}",
|
||||
"}"
|
||||
],
|
||||
"description": "Boilerplate for a dataflow tracking class"
|
||||
},
|
||||
"Taint Tracking Class": {
|
||||
"prefix": "tainttracking",
|
||||
"body": [
|
||||
"class $1 extends TaintTracking::Configuration {",
|
||||
"\t$1() { this = \"$1\" }",
|
||||
"\t",
|
||||
"\toverride predicate isSource(DataFlow::Node node) {",
|
||||
"\t\t${2:none()}",
|
||||
"\t}",
|
||||
"\t",
|
||||
"\toverride predicate isSink(DataFlow::Node node) {",
|
||||
"\t\t${3:none()}",
|
||||
"\t}",
|
||||
"}"
|
||||
],
|
||||
"description": "Boilerplate for a taint tracking class"
|
||||
},
|
||||
"Count": {
|
||||
"prefix": "count",
|
||||
"body": ["count($1 | $2 | $3)"],
|
||||
"description": "A count aggregate"
|
||||
},
|
||||
"Max": {
|
||||
"prefix": "max",
|
||||
"body": ["max($1 | $2 | $3)"],
|
||||
"description": "A max aggregate"
|
||||
},
|
||||
"Min": {
|
||||
"prefix": "min",
|
||||
"body": ["min($1 | $2 | $3)"],
|
||||
"description": "A min aggregate"
|
||||
},
|
||||
"Average": {
|
||||
"prefix": "avg",
|
||||
"body": ["avg($1 | $2 | $3)"],
|
||||
"description": "An average aggregate"
|
||||
},
|
||||
"Sum": {
|
||||
"prefix": "sum",
|
||||
"body": ["sum($1 | $2 | $3)"],
|
||||
"description": "A sum aggregate"
|
||||
},
|
||||
"Concatenation": {
|
||||
"prefix": "concat",
|
||||
"body": ["concat($1 | $2 | $3)"],
|
||||
"description": "A concatenation aggregate"
|
||||
},
|
||||
"Rank": {
|
||||
"prefix": "rank",
|
||||
"body": ["rank[$1]($2 | $3 | $4)"],
|
||||
"description": "A rank aggregate"
|
||||
},
|
||||
"Strict Sum": {
|
||||
"prefix": "strictsum",
|
||||
"body": ["strictsum($1 | $2 | $3)"],
|
||||
"description": "A strict sum aggregate"
|
||||
},
|
||||
"Strict Concatenation": {
|
||||
"prefix": "strictconcat",
|
||||
"body": ["strictconcat($1 | $2 | $3)"],
|
||||
"description": "A strict concatenation aggregate"
|
||||
},
|
||||
"Strict Count": {
|
||||
"prefix": "strictcount",
|
||||
"body": ["strictcount($1 | $2 | $3)"],
|
||||
"description": "A strict count aggregate"
|
||||
},
|
||||
"Unique": {
|
||||
"prefix": "unique",
|
||||
"body": ["unique($1 | $2 | $3)"],
|
||||
"description": "A unique aggregate"
|
||||
},
|
||||
"Exists": {
|
||||
"prefix": "exists",
|
||||
"body": ["exists($1 | $2 | $3)"],
|
||||
"description": "An exists quantifier"
|
||||
},
|
||||
"For All": {
|
||||
"prefix": "forall",
|
||||
"body": ["forall($1 | $2 | $3)"],
|
||||
"description": "A for all quantifier"
|
||||
},
|
||||
"For All and Exists": {
|
||||
"prefix": "forex",
|
||||
"body": ["forex($1 | $2 | $3)"],
|
||||
"description": "A for all and exists quantifier"
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ import { UrlValue, BqrsId } from './pure/bqrs-cli-types';
|
||||
import { showLocation } from './interface-utils';
|
||||
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './pure/bqrs-utils';
|
||||
import { commandRunner } from './commandRunner';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { showAndLogErrorMessage } from './helpers';
|
||||
|
||||
export interface AstItem {
|
||||
id: BqrsId;
|
||||
@@ -129,8 +130,13 @@ export class AstViewer extends DisposableObject {
|
||||
this.treeDataProvider.db = db;
|
||||
this.treeDataProvider.refresh();
|
||||
this.treeView.message = `AST for ${path.basename(fileName)}`;
|
||||
this.treeView.reveal(roots[0], { focus: false });
|
||||
this.currentFile = fileName;
|
||||
// Handle error on reveal. This could happen if
|
||||
// the tree view is disposed during the reveal.
|
||||
this.treeView.reveal(roots[0], { focus: false })?.then(
|
||||
() => { /**/ },
|
||||
err => showAndLogErrorMessage(err)
|
||||
);
|
||||
}
|
||||
|
||||
private updateTreeSelection(e: TextEditorSelectionChangeEvent) {
|
||||
@@ -178,7 +184,12 @@ export class AstViewer extends DisposableObject {
|
||||
|
||||
const targetItem = findBest(range, this.treeDataProvider.roots);
|
||||
if (targetItem) {
|
||||
this.treeView.reveal(targetItem);
|
||||
// Handle error on reveal. This could happen if
|
||||
// the tree view is disposed during the reveal.
|
||||
this.treeView.reveal(targetItem)?.then(
|
||||
() => { /**/ },
|
||||
err => showAndLogErrorMessage(err)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ import { CompilationMessage } from './pure/messages';
|
||||
*/
|
||||
const SARIF_FORMAT = 'sarifv2.1.0';
|
||||
|
||||
/**
|
||||
* The string used to specify CSV format.
|
||||
*/
|
||||
const CSV_FORMAT = 'csv';
|
||||
|
||||
/**
|
||||
* Flags to pass to all cli commands.
|
||||
*/
|
||||
@@ -99,6 +104,7 @@ export interface TestCompleted {
|
||||
evaluationMs: number;
|
||||
expected: string;
|
||||
diff: string[] | undefined;
|
||||
failureDescription?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,6 +137,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
*/
|
||||
private static CLI_VERSION_WITH_LANGUAGE = new SemVer('2.4.1');
|
||||
|
||||
/**
|
||||
* CLI version where `codeql resolve upgrades` supports
|
||||
* the `--allow-downgrades` flag
|
||||
*/
|
||||
private static CLI_VERSION_WITH_DOWNGRADES = new SemVer('2.4.4');
|
||||
|
||||
/** The process for the cli server, or undefined if one doesn't exist yet */
|
||||
process?: child_process.ChildProcessWithoutNullStreams;
|
||||
/** Queue of future commands*/
|
||||
@@ -162,6 +174,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
if (this.distributionProvider.onDidChangeDistribution) {
|
||||
this.distributionProvider.onDidChangeDistribution(() => {
|
||||
this.restartCliServer();
|
||||
this._version = undefined;
|
||||
});
|
||||
}
|
||||
if (this.cliConfig.onDidChangeConfiguration) {
|
||||
@@ -502,12 +515,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
testPaths: string[], workspaces: string[], options: TestRunOptions
|
||||
): AsyncGenerator<TestCompleted, void, unknown> {
|
||||
|
||||
const subcommandArgs = [
|
||||
const subcommandArgs = this.cliConfig.additionalTestArguments.concat([
|
||||
'--additional-packs', workspaces.join(path.delimiter),
|
||||
'--threads',
|
||||
this.cliConfig.numberTestThreads.toString(),
|
||||
...testPaths
|
||||
];
|
||||
]);
|
||||
|
||||
for await (const event of await this.runAsyncCodeQlCliCommand<TestCompleted>(['test', 'run'],
|
||||
subcommandArgs, 'Run CodeQL Tests', options.cancellationToken, options.logger)) {
|
||||
@@ -575,18 +588,20 @@ export class CodeQLCliServer implements Disposable {
|
||||
return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(['bqrs', 'decode'], subcommandArgs, 'Reading bqrs data');
|
||||
}
|
||||
|
||||
async interpretBqrs(metadata: { kind: string; id: string; scored?: string }, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
|
||||
async runInterpretCommand(format: string, metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {
|
||||
const args = [
|
||||
`-t=kind=${metadata.kind}`,
|
||||
`-t=id=${metadata.id}`,
|
||||
'--output', interpretedResultsPath,
|
||||
'--format', SARIF_FORMAT,
|
||||
'--format', format,
|
||||
];
|
||||
if (format == SARIF_FORMAT) {
|
||||
// TODO: This flag means that we don't group interpreted results
|
||||
// by primary location. We may want to revisit whether we call
|
||||
// interpretation with and without this flag, or do some
|
||||
// grouping client-side.
|
||||
'--no-group-results',
|
||||
];
|
||||
args.push('--no-group-results');
|
||||
}
|
||||
if (config.isCanary() && metadata.scored !== undefined) {
|
||||
args.push(`-t=scored=${metadata.scored}`);
|
||||
}
|
||||
@@ -596,14 +611,28 @@ export class CodeQLCliServer implements Disposable {
|
||||
'--source-location-prefix', sourceInfo.sourceLocationPrefix
|
||||
);
|
||||
}
|
||||
|
||||
args.push(
|
||||
'--threads',
|
||||
this.cliConfig.numberThreads.toString(),
|
||||
);
|
||||
|
||||
args.push(resultsPath);
|
||||
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');
|
||||
}
|
||||
|
||||
async interpretBqrs(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
|
||||
await this.runInterpretCommand(SARIF_FORMAT, metadata, resultsPath, interpretedResultsPath, sourceInfo);
|
||||
|
||||
let output: string;
|
||||
try {
|
||||
output = await fs.readFile(interpretedResultsPath, 'utf8');
|
||||
} catch (err) {
|
||||
throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`);
|
||||
} catch (e) {
|
||||
const rawMessage = e.stderr || e.message;
|
||||
const errorMessage = rawMessage.startsWith('Cannot create a string')
|
||||
? `SARIF too large. ${rawMessage}`
|
||||
: rawMessage;
|
||||
throw new Error(`Reading output of interpretation failed: ${errorMessage}`);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(output) as sarif.Log;
|
||||
@@ -612,6 +641,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise<void> {
|
||||
await this.runInterpretCommand(CSV_FORMAT, metadata, resultsPath, csvPath, sourceInfo);
|
||||
}
|
||||
|
||||
async sortBqrs(resultsPath: string, sortedResultsPath: string, resultSet: string, sortKeys: number[], sortDirections: SortDirection[]): Promise<void> {
|
||||
const sortDirectionStrings = sortDirections.map(direction => {
|
||||
@@ -651,15 +683,19 @@ export class CodeQLCliServer implements Disposable {
|
||||
* Gets information necessary for upgrading a database.
|
||||
* @param dbScheme the path to the dbscheme of the database to be upgraded.
|
||||
* @param searchPath A list of directories to search for upgrade scripts.
|
||||
* @param allowDowngradesIfPossible Whether we should try and include downgrades of we can.
|
||||
* @param targetDbScheme The dbscheme to try to upgrade to.
|
||||
* @returns A list of database upgrade script directories
|
||||
*/
|
||||
resolveUpgrades(dbScheme: string, searchPath: string[], targetDbScheme?: string): Promise<UpgradesInfo> {
|
||||
async resolveUpgrades(dbScheme: string, searchPath: string[], allowDowngradesIfPossible: boolean, targetDbScheme?: string): Promise<UpgradesInfo> {
|
||||
const args = ['--additional-packs', searchPath.join(path.delimiter), '--dbscheme', dbScheme];
|
||||
if (targetDbScheme) {
|
||||
args.push('--target-dbscheme', targetDbScheme);
|
||||
if (allowDowngradesIfPossible && await this.supportsDowngrades()) {
|
||||
args.push('--allow-downgrades');
|
||||
}
|
||||
}
|
||||
return this.runJsonCodeQlCliCommand<UpgradesInfo>(
|
||||
return await this.runJsonCodeQlCliCommand<UpgradesInfo>(
|
||||
['resolve', 'upgrades'],
|
||||
args,
|
||||
'Resolving database upgrade scripts',
|
||||
@@ -733,6 +769,10 @@ export class CodeQLCliServer implements Disposable {
|
||||
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_LANGUAGE) >= 0;
|
||||
}
|
||||
|
||||
public async supportsDowngrades() {
|
||||
return (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DOWNGRADES) >= 0;
|
||||
}
|
||||
|
||||
private async refreshVersion() {
|
||||
const distribution = await this.distributionProvider.getDistribution();
|
||||
switch (distribution.kind) {
|
||||
@@ -866,6 +906,20 @@ class SplitBuffer {
|
||||
this.buffer += this.separators[0]; // Append a separator to the end to ensure the last line is returned.
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of startsWith that isn't overriden by a broken version of ms-python.
|
||||
*
|
||||
* The definition comes from
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
|
||||
* which is CC0/public domain
|
||||
*
|
||||
* See https://github.com/github/vscode-codeql/issues/802 for more context as to why we need it.
|
||||
*/
|
||||
private static startsWith(s: string, searchString: string, position: number): boolean {
|
||||
const pos = position > 0 ? position | 0 : 0;
|
||||
return s.substring(pos, pos + searchString.length) === searchString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the next full line from the buffer, if one is available.
|
||||
* @returns The text of the next available full line (without the separator), or `undefined` if no
|
||||
@@ -874,7 +928,7 @@ class SplitBuffer {
|
||||
public getNextLine(): string | undefined {
|
||||
while (this.searchIndex <= (this.buffer.length - this.maxSeparatorLength)) {
|
||||
for (const separator of this.separators) {
|
||||
if (this.buffer.startsWith(separator, this.searchIndex)) {
|
||||
if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {
|
||||
const line = this.buffer.substr(0, this.searchIndex);
|
||||
this.buffer = this.buffer.substr(this.searchIndex + separator.length);
|
||||
this.searchIndex = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from '../vscode-utils/disposable-object';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import {
|
||||
WebviewPanel,
|
||||
ExtensionContext,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { workspace, Event, EventEmitter, ConfigurationChangeEvent, ConfigurationTarget } from 'vscode';
|
||||
import { DistributionManager } from './distribution';
|
||||
import { logger } from './logging';
|
||||
@@ -50,7 +50,7 @@ export const GLOBAL_ENABLE_TELEMETRY = new Setting('enableTelemetry', GLOBAL_TEL
|
||||
|
||||
// Distribution configuration
|
||||
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);
|
||||
const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING);
|
||||
export const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING);
|
||||
const INCLUDE_PRERELEASE_SETTING = new Setting('includePrerelease', DISTRIBUTION_SETTING);
|
||||
const PERSONAL_ACCESS_TOKEN_SETTING = new Setting('personalAccessToken', DISTRIBUTION_SETTING);
|
||||
const QUERY_HISTORY_SETTING = new Setting('queryHistory', ROOT_SETTING);
|
||||
@@ -73,24 +73,29 @@ export interface DistributionConfig {
|
||||
|
||||
const RUNNING_QUERIES_SETTING = new Setting('runningQueries', ROOT_SETTING);
|
||||
const NUMBER_OF_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_QUERIES_SETTING);
|
||||
const SAVE_CACHE_SETTING = new Setting('saveCache', RUNNING_QUERIES_SETTING);
|
||||
const CACHE_SIZE_SETTING = new Setting('cacheSize', RUNNING_QUERIES_SETTING);
|
||||
const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING);
|
||||
const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
|
||||
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
|
||||
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
|
||||
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
|
||||
|
||||
export const ADDITIONAL_TEST_ARGUMENTS_SETTING = new Setting('additionalTestArguments', RUNNING_TESTS_SETTING);
|
||||
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
|
||||
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
|
||||
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
|
||||
export const PAGE_SIZE = new Setting('pageSize', RESULTS_DISPLAY_SETTING);
|
||||
|
||||
/** When these settings change, the running query server should be restarted. */
|
||||
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];
|
||||
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, SAVE_CACHE_SETTING, CACHE_SIZE_SETTING, MEMORY_SETTING, DEBUG_SETTING];
|
||||
|
||||
export interface QueryServerConfig {
|
||||
codeQlPath: string;
|
||||
debug: boolean;
|
||||
numThreads: number;
|
||||
saveCache: boolean;
|
||||
cacheSize: number;
|
||||
queryMemoryMb?: number;
|
||||
timeoutSecs: number;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
@@ -104,10 +109,12 @@ export interface QueryHistoryConfig {
|
||||
onDidChangeConfiguration: Event<void>;
|
||||
}
|
||||
|
||||
const CLI_SETTINGS = [NUMBER_OF_TEST_THREADS_SETTING];
|
||||
const CLI_SETTINGS = [ADDITIONAL_TEST_ARGUMENTS_SETTING, NUMBER_OF_TEST_THREADS_SETTING, NUMBER_OF_THREADS_SETTING];
|
||||
|
||||
export interface CliConfig {
|
||||
additionalTestArguments: string[];
|
||||
numberTestThreads: number;
|
||||
numberThreads: number;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
}
|
||||
|
||||
@@ -193,6 +200,14 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
|
||||
return NUMBER_OF_THREADS_SETTING.getValue<number>();
|
||||
}
|
||||
|
||||
public get saveCache(): boolean {
|
||||
return SAVE_CACHE_SETTING.getValue<boolean>();
|
||||
}
|
||||
|
||||
public get cacheSize(): number {
|
||||
return CACHE_SIZE_SETTING.getValue<number | null>() || 0;
|
||||
}
|
||||
|
||||
/** Gets the configured query timeout, in seconds. This looks up the setting at the time of access. */
|
||||
public get timeoutSecs(): number {
|
||||
return TIMEOUT_SETTING.getValue<number | null>() || 0;
|
||||
@@ -230,11 +245,18 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
|
||||
}
|
||||
|
||||
export class CliConfigListener extends ConfigListener implements CliConfig {
|
||||
public get additionalTestArguments(): string[] {
|
||||
return ADDITIONAL_TEST_ARGUMENTS_SETTING.getValue();
|
||||
}
|
||||
|
||||
public get numberTestThreads(): number {
|
||||
return NUMBER_OF_TEST_THREADS_SETTING.getValue();
|
||||
}
|
||||
|
||||
public get numberThreads(): number {
|
||||
return NUMBER_OF_THREADS_SETTING.getValue<number>();
|
||||
}
|
||||
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
CancellationToken,
|
||||
DefinitionProvider,
|
||||
Location,
|
||||
LocationLink,
|
||||
Position,
|
||||
ProgressLocation,
|
||||
ReferenceContext,
|
||||
ReferenceProvider,
|
||||
TextDocument,
|
||||
Uri
|
||||
} from 'vscode';
|
||||
|
||||
import { decodeSourceArchiveUri, encodeArchiveBasePath, zipArchiveScheme } from '../archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
@@ -22,20 +33,20 @@ import { qlpackOfDatabase, resolveQueries } from './queryResolver';
|
||||
* or from a selected identifier.
|
||||
*/
|
||||
|
||||
export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider {
|
||||
private cache: CachedOperation<vscode.LocationLink[]>;
|
||||
export class TemplateQueryDefinitionProvider implements DefinitionProvider {
|
||||
private cache: CachedOperation<LocationLink[]>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<vscode.LocationLink[]>(this.getDefinitions.bind(this));
|
||||
this.cache = new CachedOperation<LocationLink[]>(this.getDefinitions.bind(this));
|
||||
}
|
||||
|
||||
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.LocationLink[]> {
|
||||
async provideDefinition(document: TextDocument, position: Position, _token: CancellationToken): Promise<LocationLink[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.LocationLink[] = [];
|
||||
const locLinks: LocationLink[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.originSelectionRange!.contains(position)) {
|
||||
locLinks.push(link);
|
||||
@@ -44,9 +55,9 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
|
||||
return locLinks;
|
||||
}
|
||||
|
||||
private async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
|
||||
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
|
||||
return withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
location: ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: 'Finding definitions'
|
||||
}, async (progress, token) => {
|
||||
@@ -64,7 +75,7 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider {
|
||||
export class TemplateQueryReferenceProvider implements ReferenceProvider {
|
||||
private cache: CachedOperation<FullLocationLink[]>;
|
||||
|
||||
constructor(
|
||||
@@ -76,13 +87,13 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
|
||||
}
|
||||
|
||||
async provideReferences(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
_context: vscode.ReferenceContext,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.Location[]> {
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
_context: ReferenceContext,
|
||||
_token: CancellationToken
|
||||
): Promise<Location[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.Location[] = [];
|
||||
const locLinks: Location[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.targetRange!.contains(position)) {
|
||||
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
|
||||
@@ -93,7 +104,7 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
|
||||
|
||||
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
|
||||
return withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
location: ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: 'Finding references'
|
||||
}, async (progress, token) => {
|
||||
@@ -112,40 +123,41 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
|
||||
}
|
||||
|
||||
export class TemplatePrintAstProvider {
|
||||
private cache: CachedOperation<QueryWithResults | undefined>;
|
||||
private cache: CachedOperation<QueryWithResults>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
|
||||
// Note: progress and token are only used if a cached value is not available
|
||||
private progress: ProgressCallback,
|
||||
private token: vscode.CancellationToken
|
||||
) {
|
||||
this.cache = new CachedOperation<QueryWithResults | undefined>(this.getAst.bind(this));
|
||||
this.cache = new CachedOperation<QueryWithResults>(this.getAst.bind(this));
|
||||
}
|
||||
|
||||
async provideAst(document?: vscode.TextDocument): Promise<AstBuilder | undefined> {
|
||||
async provideAst(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
document?: TextDocument
|
||||
): Promise<AstBuilder | undefined> {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
const queryResults = await this.cache.get(document.uri.toString());
|
||||
if (!queryResults) {
|
||||
return;
|
||||
throw new Error('Cannot view the AST. Please select a valid source file inside a CodeQL database.');
|
||||
}
|
||||
const queryResults = await this.cache.get(document.uri.toString(), progress, token);
|
||||
|
||||
return new AstBuilder(
|
||||
queryResults, this.cli,
|
||||
this.dbm.findDatabaseItem(vscode.Uri.parse(queryResults.database.databaseUri!, true))!,
|
||||
this.dbm.findDatabaseItem(Uri.parse(queryResults.database.databaseUri!, true))!,
|
||||
document.fileName
|
||||
);
|
||||
}
|
||||
|
||||
private async getAst(uriString: string): Promise<QueryWithResults> {
|
||||
const uri = vscode.Uri.parse(uriString, true);
|
||||
private async getAst(
|
||||
uriString: string,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<QueryWithResults> {
|
||||
const uri = Uri.parse(uriString, true);
|
||||
if (uri.scheme !== zipArchiveScheme) {
|
||||
throw new Error('AST Viewing is only available for databases with zipped source archives.');
|
||||
throw new Error('Cannot view the AST. Please select a valid source file inside a CodeQL database.');
|
||||
}
|
||||
|
||||
const zippedArchive = decodeSourceArchiveUri(uri);
|
||||
@@ -181,9 +193,9 @@ export class TemplatePrintAstProvider {
|
||||
this.qs,
|
||||
db,
|
||||
false,
|
||||
vscode.Uri.file(query),
|
||||
this.progress,
|
||||
this.token,
|
||||
Uri.file(query),
|
||||
progress,
|
||||
token,
|
||||
templates
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export async function promptImportLgtmDatabase(
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const lgtmUrl = await window.showInputBox({
|
||||
prompt:
|
||||
'Enter the project URL on LGTM (e.g., https://lgtm.com/projects/g/github/codeql)',
|
||||
'Enter the project slug or URL on LGTM (e.g., g/github/codeql or https://lgtm.com/projects/g/github/codeql)',
|
||||
});
|
||||
if (!lgtmUrl) {
|
||||
return;
|
||||
@@ -352,13 +352,14 @@ export async function findDirWithFile(
|
||||
|
||||
/**
|
||||
* The URL pattern is https://lgtm.com/projects/{provider}/{org}/{name}/{irrelevant-subpages}.
|
||||
* There are several possibilities for the provider: in addition to GitHub.com(g),
|
||||
* There are several possibilities for the provider: in addition to GitHub.com (g),
|
||||
* LGTM currently hosts projects from Bitbucket (b), GitLab (gl) and plain git (git).
|
||||
*
|
||||
* After the {provider}/{org}/{name} path components, there may be the components
|
||||
* related to sub pages.
|
||||
* This function accepts any url that matches the pattern above. It also accepts the
|
||||
* raw project slug, e.g., `g/myorg/myproject`
|
||||
*
|
||||
* This function accepts any url that matches the patter above
|
||||
* After the `{provider}/{org}/{name}` path components, there may be the components
|
||||
* related to sub pages.
|
||||
*
|
||||
* @param lgtmUrl The URL to the lgtm project
|
||||
*
|
||||
@@ -370,6 +371,10 @@ export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string
|
||||
return false;
|
||||
}
|
||||
|
||||
if (convertRawLgtmSlug(lgtmUrl)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const uri = Uri.parse(lgtmUrl, true);
|
||||
if (uri.scheme !== 'https') {
|
||||
@@ -387,9 +392,23 @@ export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string
|
||||
}
|
||||
}
|
||||
|
||||
function convertRawLgtmSlug(maybeSlug: string): string | undefined {
|
||||
if (!maybeSlug) {
|
||||
return;
|
||||
}
|
||||
const segments = maybeSlug.split('/');
|
||||
const providers = ['g', 'gl', 'b', 'git'];
|
||||
if (segments.length === 3 && providers.includes(segments[0])) {
|
||||
return `https://lgtm.com/projects/${maybeSlug}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// exported for testing
|
||||
export async function convertToDatabaseUrl(lgtmUrl: string) {
|
||||
try {
|
||||
lgtmUrl = convertRawLgtmSlug(lgtmUrl) || lgtmUrl;
|
||||
|
||||
const uri = Uri.parse(lgtmUrl, true);
|
||||
const paths = ['api', 'v1.0'].concat(
|
||||
uri.path.split('/').filter((segment) => segment)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import {
|
||||
Event,
|
||||
EventEmitter,
|
||||
@@ -379,8 +379,8 @@ export class DatabaseUI extends DisposableObject {
|
||||
let dbDirs = undefined;
|
||||
|
||||
if (
|
||||
!(await fs.pathExists(this.storagePath) ||
|
||||
!(await fs.stat(this.storagePath)).isDirectory())
|
||||
!(await fs.pathExists(this.storagePath)) ||
|
||||
!(await fs.stat(this.storagePath)).isDirectory()
|
||||
) {
|
||||
logger.log('Missing or invalid storage directory. Not trying to remove orphaned databases.');
|
||||
return;
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
withProgress
|
||||
} from './commandRunner';
|
||||
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { Logger, logger } from './logging';
|
||||
import { registerDatabases, Dataset, deregisterDatabases } from './pure/messages';
|
||||
import { QueryServerClient } from './queryserver-client';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { logger } from './logging';
|
||||
|
||||
/**
|
||||
|
||||
@@ -472,12 +472,11 @@ async function activateWithInstalledDistribution(
|
||||
progress,
|
||||
token
|
||||
);
|
||||
const item = qhm.addQuery(info);
|
||||
const item = qhm.buildCompletedQuery(info);
|
||||
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
|
||||
// The call to showResults potentially creates SARIF file;
|
||||
// Update the tree item context value to allow viewing that
|
||||
// SARIF file from context menu.
|
||||
await qhm.refreshTreeView(item);
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
await qhm.addCompletedQuery(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,13 +691,18 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
|
||||
const astViewer = new AstViewer();
|
||||
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm);
|
||||
|
||||
ctx.subscriptions.push(astViewer);
|
||||
ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
) => {
|
||||
const ast = await new TemplatePrintAstProvider(cliServer, qs, dbm, progress, token)
|
||||
.provideAst(window.activeTextEditor?.document);
|
||||
const ast = await templateProvider.provideAst(
|
||||
progress,
|
||||
token,
|
||||
window.activeTextEditor?.document,
|
||||
);
|
||||
if (ast) {
|
||||
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
|
||||
}
|
||||
|
||||
@@ -296,19 +296,19 @@ export async function getPrimaryDbscheme(datasetFolder: string): Promise<string>
|
||||
* A cached mapping from strings to value of type U.
|
||||
*/
|
||||
export class CachedOperation<U> {
|
||||
private readonly operation: (t: string) => Promise<U>;
|
||||
private readonly operation: (t: string, ...args: any[]) => Promise<U>;
|
||||
private readonly cached: Map<string, U>;
|
||||
private readonly lru: string[];
|
||||
private readonly inProgressCallbacks: Map<string, [(u: U) => void, (reason?: any) => void][]>;
|
||||
|
||||
constructor(operation: (t: string) => Promise<U>, private cacheSize = 100) {
|
||||
constructor(operation: (t: string, ...args: any[]) => Promise<U>, private cacheSize = 100) {
|
||||
this.operation = operation;
|
||||
this.lru = [];
|
||||
this.inProgressCallbacks = new Map<string, [(u: U) => void, (reason?: any) => void][]>();
|
||||
this.cached = new Map<string, U>();
|
||||
}
|
||||
|
||||
async get(t: string): Promise<U> {
|
||||
async get(t: string, ...args: any[]): Promise<U> {
|
||||
// Try and retrieve from the cache
|
||||
const fromCache = this.cached.get(t);
|
||||
if (fromCache !== undefined) {
|
||||
@@ -329,7 +329,7 @@ export class CachedOperation<U> {
|
||||
const callbacks: [(u: U) => void, (reason?: any) => void][] = [];
|
||||
this.inProgressCallbacks.set(t, callbacks);
|
||||
try {
|
||||
const result = await this.operation(t);
|
||||
const result = await this.operation(t, ...args);
|
||||
callbacks.forEach(f => f[0](result));
|
||||
this.inProgressCallbacks.delete(t);
|
||||
if (this.lru.length > this.cacheSize) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import * as Sarif from 'sarif';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
Diagnostic,
|
||||
@@ -236,61 +236,67 @@ export class InterfaceManager extends DisposableObject {
|
||||
}
|
||||
|
||||
private async handleMsgFromView(msg: FromResultsViewMsg): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case 'viewSourceFile': {
|
||||
await jumpToLocation(msg, this.databaseManager, this.logger);
|
||||
break;
|
||||
}
|
||||
case 'toggleDiagnostics': {
|
||||
if (msg.visible) {
|
||||
const databaseItem = this.databaseManager.findDatabaseItem(
|
||||
Uri.parse(msg.databaseUri)
|
||||
);
|
||||
if (databaseItem !== undefined) {
|
||||
await this.showResultsAsDiagnostics(
|
||||
msg.origResultsPaths,
|
||||
msg.metadata,
|
||||
databaseItem
|
||||
try {
|
||||
switch (msg.t) {
|
||||
case 'viewSourceFile': {
|
||||
await jumpToLocation(msg, this.databaseManager, this.logger);
|
||||
break;
|
||||
}
|
||||
case 'toggleDiagnostics': {
|
||||
if (msg.visible) {
|
||||
const databaseItem = this.databaseManager.findDatabaseItem(
|
||||
Uri.parse(msg.databaseUri)
|
||||
);
|
||||
if (databaseItem !== undefined) {
|
||||
await this.showResultsAsDiagnostics(
|
||||
msg.origResultsPaths,
|
||||
msg.metadata,
|
||||
databaseItem
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO: Only clear diagnostics on the same database.
|
||||
this._diagnosticCollection.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'resultViewLoaded':
|
||||
this._panelLoaded = true;
|
||||
this._panelLoadedCallBacks.forEach((cb) => cb());
|
||||
this._panelLoadedCallBacks = [];
|
||||
break;
|
||||
case 'changeSort':
|
||||
await this.changeRawSortState(msg.resultSetName, msg.sortState);
|
||||
break;
|
||||
case 'changeInterpretedSort':
|
||||
await this.changeInterpretedSortState(msg.sortState);
|
||||
break;
|
||||
case 'changePage':
|
||||
if (msg.selectedTable === ALERTS_TABLE_NAME) {
|
||||
await this.showPageOfInterpretedResults(msg.pageNumber);
|
||||
}
|
||||
else {
|
||||
await this.showPageOfRawResults(
|
||||
msg.selectedTable,
|
||||
msg.pageNumber,
|
||||
// When we are in an unsorted state, we guarantee that
|
||||
// sortedResultsInfo doesn't have an entry for the current
|
||||
// result set. Use this to determine whether or not we use
|
||||
// the sorted bqrs file.
|
||||
this._displayedQuery?.sortedResultsInfo.has(msg.selectedTable) || false
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO: Only clear diagnostics on the same database.
|
||||
this._diagnosticCollection.clear();
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'openFile':
|
||||
await this.openFile(msg.filePath);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
case 'resultViewLoaded':
|
||||
this._panelLoaded = true;
|
||||
this._panelLoadedCallBacks.forEach((cb) => cb());
|
||||
this._panelLoadedCallBacks = [];
|
||||
break;
|
||||
case 'changeSort':
|
||||
await this.changeRawSortState(msg.resultSetName, msg.sortState);
|
||||
break;
|
||||
case 'changeInterpretedSort':
|
||||
await this.changeInterpretedSortState(msg.sortState);
|
||||
break;
|
||||
case 'changePage':
|
||||
if (msg.selectedTable === ALERTS_TABLE_NAME) {
|
||||
await this.showPageOfInterpretedResults(msg.pageNumber);
|
||||
}
|
||||
else {
|
||||
await this.showPageOfRawResults(
|
||||
msg.selectedTable,
|
||||
msg.pageNumber,
|
||||
// When we are in an unsorted state, we guarantee that
|
||||
// sortedResultsInfo doesn't have an entry for the current
|
||||
// result set. Use this to determine whether or not we use
|
||||
// the sorted bqrs file.
|
||||
this._displayedQuery?.sortedResultsInfo.has(msg.selectedTable) || false
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'openFile':
|
||||
await this.openFile(msg.filePath);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message, {
|
||||
fullMessage: e.stack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +400,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
}
|
||||
);
|
||||
const resultSet = transformBqrsResultSet(schema, chunk);
|
||||
results.setResultCount(interpretationPage?.numTotalResults || resultSet.schema.rows);
|
||||
const parsedResultSets: ParsedResultSets = {
|
||||
pageNumber: 0,
|
||||
pageSize,
|
||||
@@ -626,7 +633,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
} catch (e) {
|
||||
// If interpretation fails, accept the error and continue
|
||||
// trying to render uninterpreted results anyway.
|
||||
this.logger.log(
|
||||
showAndLogErrorMessage(
|
||||
`Exception during results interpretation: ${e.message}. Will show raw results instead.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { window as Window, OutputChannel, Progress, Disposable } from 'vscode';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
// Avoid explicitly referencing Disposable type in vscode.
|
||||
// This file cannot have dependencies on the vscode API.
|
||||
interface Disposable {
|
||||
dispose(): any;
|
||||
}
|
||||
|
||||
export type DisposeHandler = (disposable: Disposable) => void;
|
||||
|
||||
/**
|
||||
* Base class to make it easier to implement a `Disposable` that owns other disposable object.
|
||||
@@ -40,21 +47,39 @@ export abstract class DisposableObject implements Disposable {
|
||||
* @param obj The object to stop tracking.
|
||||
*/
|
||||
protected disposeAndStopTracking(obj: Disposable): void {
|
||||
if (obj !== undefined) {
|
||||
this.tracked!.delete(obj);
|
||||
if (obj && this.tracked) {
|
||||
this.tracked.delete(obj);
|
||||
obj.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
/**
|
||||
* Dispose this object and all contained objects
|
||||
*
|
||||
* @param disposeHandler An optional dispose handler that gets
|
||||
* passed each element to dispose. The dispose handler
|
||||
* can choose how (and if) to dispose the object. The
|
||||
* primary usage is for tests that should not dispose
|
||||
* all items of a disposable.
|
||||
*/
|
||||
public dispose(disposeHandler?: DisposeHandler) {
|
||||
if (this.tracked !== undefined) {
|
||||
for (const trackedObject of this.tracked.values()) {
|
||||
trackedObject.dispose();
|
||||
if (disposeHandler) {
|
||||
disposeHandler(trackedObject);
|
||||
} else {
|
||||
trackedObject.dispose();
|
||||
}
|
||||
}
|
||||
this.tracked = undefined;
|
||||
}
|
||||
while (this.disposables.length > 0) {
|
||||
this.disposables.pop()!.dispose();
|
||||
const disposable = this.disposables.pop()!;
|
||||
if (disposeHandler) {
|
||||
disposeHandler(disposable);
|
||||
} else {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { window as Window } from 'vscode';
|
||||
import { window as Window, env } from 'vscode';
|
||||
import { CompletedQuery } from './query-results';
|
||||
import { QueryHistoryConfig } from './config';
|
||||
import { QueryWithResults } from './run-queries';
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
import { logger } from './logging';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { QueryServerClient } from './queryserver-client';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { commandRunner } from './commandRunner';
|
||||
import { assertNever } from './pure/helpers-pure';
|
||||
|
||||
/**
|
||||
* query-history.ts
|
||||
@@ -58,10 +59,21 @@ const SHOW_QUERY_TEXT_QUICK_EVAL_MSG = `\
|
||||
*/
|
||||
const FAILED_QUERY_HISTORY_ITEM_ICON = 'media/red-x.svg';
|
||||
|
||||
enum SortOrder {
|
||||
NameAsc = 'NameAsc',
|
||||
NameDesc = 'NameDesc',
|
||||
DateAsc = 'DateAsc',
|
||||
DateDesc = 'DateDesc',
|
||||
CountAsc = 'CountAsc',
|
||||
CountDesc = 'CountDesc',
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree data provider for the query history view.
|
||||
*/
|
||||
export class HistoryTreeDataProvider extends DisposableObject {
|
||||
private _sortOrder = SortOrder.DateAsc;
|
||||
|
||||
private _onDidChangeTreeData = super.push(new vscode.EventEmitter<CompletedQuery | undefined>());
|
||||
|
||||
readonly onDidChangeTreeData: vscode.Event<CompletedQuery | undefined> = this
|
||||
@@ -111,7 +123,24 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
getChildren(
|
||||
element?: CompletedQuery
|
||||
): vscode.ProviderResult<CompletedQuery[]> {
|
||||
return element ? [] : this.history;
|
||||
return element ? [] : this.history.sort((q1, q2) => {
|
||||
switch (this.sortOrder) {
|
||||
case SortOrder.NameAsc:
|
||||
return q1.toString().localeCompare(q2.toString(), env.language);
|
||||
case SortOrder.NameDesc:
|
||||
return q2.toString().localeCompare(q1.toString(), env.language);
|
||||
case SortOrder.DateAsc:
|
||||
return q1.date.getTime() - q2.date.getTime();
|
||||
case SortOrder.DateDesc:
|
||||
return q2.date.getTime() - q1.date.getTime();
|
||||
case SortOrder.CountAsc:
|
||||
return q1.resultCount - q2.resultCount;
|
||||
case SortOrder.CountDesc:
|
||||
return q2.resultCount - q1.resultCount;
|
||||
default:
|
||||
assertNever(this.sortOrder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getParent(_element: CompletedQuery): vscode.ProviderResult<CompletedQuery> {
|
||||
@@ -157,6 +186,15 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
find(queryId: number): CompletedQuery | undefined {
|
||||
return this.allHistory.find((query) => query.query.queryID === queryId);
|
||||
}
|
||||
|
||||
public get sortOrder() {
|
||||
return this._sortOrder;
|
||||
}
|
||||
|
||||
public set sortOrder(newSortOrder: SortOrder) {
|
||||
this._sortOrder = newSortOrder;
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,6 +262,24 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
this.handleRemoveHistoryItem.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.sortByName',
|
||||
this.handleSortByName.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.sortByDate',
|
||||
this.handleSortByDate.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.sortByCount',
|
||||
this.handleSortByCount.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.setLabel',
|
||||
@@ -250,8 +306,14 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.viewSarif',
|
||||
this.handleViewSarif.bind(this)
|
||||
'codeQLQueryHistory.viewCsvResults',
|
||||
this.handleViewCsvResults.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.viewSarifResults',
|
||||
this.handleViewSarifResults.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
@@ -345,6 +407,30 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleSortByName() {
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) {
|
||||
this.treeDataProvider.sortOrder = SortOrder.NameDesc;
|
||||
} else {
|
||||
this.treeDataProvider.sortOrder = SortOrder.NameAsc;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSortByDate() {
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.DateAsc) {
|
||||
this.treeDataProvider.sortOrder = SortOrder.DateDesc;
|
||||
} else {
|
||||
this.treeDataProvider.sortOrder = SortOrder.DateAsc;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSortByCount() {
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.CountAsc) {
|
||||
this.treeDataProvider.sortOrder = SortOrder.CountDesc;
|
||||
} else {
|
||||
this.treeDataProvider.sortOrder = SortOrder.CountAsc;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSetLabel(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[]
|
||||
@@ -362,7 +448,12 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
if (response !== undefined) {
|
||||
// Interpret empty string response as 'go back to using default'
|
||||
singleItem.options.label = response === '' ? undefined : response;
|
||||
this.treeDataProvider.refresh(singleItem);
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc ||
|
||||
this.treeDataProvider.sortOrder === SortOrder.NameDesc) {
|
||||
this.treeDataProvider.refresh();
|
||||
} else {
|
||||
this.treeDataProvider.refresh(singleItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +550,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
|
||||
async handleViewSarif(
|
||||
async handleViewSarifResults(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[]
|
||||
) {
|
||||
@@ -480,6 +571,19 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
async handleViewCsvResults(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[]
|
||||
) {
|
||||
if (!this.assertSingleQuery(multiSelect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.tryOpenExternalFile(
|
||||
await singleItem.query.ensureCsvProduced(this.qs)
|
||||
);
|
||||
}
|
||||
|
||||
async handleViewDil(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[],
|
||||
@@ -511,11 +615,14 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
addQuery(info: QueryWithResults): CompletedQuery {
|
||||
buildCompletedQuery(info: QueryWithResults): CompletedQuery {
|
||||
const item = new CompletedQuery(info, this.queryHistoryConfigListener);
|
||||
return item;
|
||||
}
|
||||
|
||||
addCompletedQuery(item: CompletedQuery) {
|
||||
this.treeDataProvider.pushQuery(item);
|
||||
this.updateTreeViewSelectionIfVisible();
|
||||
return item;
|
||||
}
|
||||
|
||||
find(queryId: number): CompletedQuery | undefined {
|
||||
@@ -690,3 +797,4 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
this.treeDataProvider.refresh(completedQuery);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ import { QueryHistoryConfig } from './config';
|
||||
import { QueryHistoryItemOptions } from './query-history';
|
||||
|
||||
export class CompletedQuery implements QueryWithResults {
|
||||
readonly date: Date;
|
||||
readonly time: string;
|
||||
readonly query: QueryInfo;
|
||||
readonly result: messages.EvaluationResult;
|
||||
readonly database: DatabaseInfo;
|
||||
readonly logFileLocation?: string;
|
||||
options: QueryHistoryItemOptions;
|
||||
resultCount: number;
|
||||
dispose: () => void;
|
||||
|
||||
/**
|
||||
@@ -44,8 +46,14 @@ export class CompletedQuery implements QueryWithResults {
|
||||
this.options = evaluation.options;
|
||||
this.dispose = evaluation.dispose;
|
||||
|
||||
this.time = new Date().toLocaleString(env.language);
|
||||
this.date = new Date();
|
||||
this.time = this.date.toLocaleString(env.language);
|
||||
this.sortedResultsInfo = new Map();
|
||||
this.resultCount = 0;
|
||||
}
|
||||
|
||||
setResultCount(value: number) {
|
||||
this.resultCount = value;
|
||||
}
|
||||
|
||||
get databaseName(): string {
|
||||
@@ -80,11 +88,12 @@ export class CompletedQuery implements QueryWithResults {
|
||||
}
|
||||
|
||||
interpolate(template: string): string {
|
||||
const { databaseName, queryName, time, statusString } = this;
|
||||
const { databaseName, queryName, time, resultCount, statusString } = this;
|
||||
const replacements: { [k: string]: string } = {
|
||||
t: time,
|
||||
q: queryName,
|
||||
d: databaseName,
|
||||
r: resultCount.toString(),
|
||||
s: statusString,
|
||||
'%': '%',
|
||||
};
|
||||
@@ -170,6 +179,10 @@ export async function interpretResults(
|
||||
if (await fs.pathExists(interpretedResultsPath)) {
|
||||
return JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8'));
|
||||
}
|
||||
return await server.interpretBqrs(ensureMetadataIsComplete(metadata), resultsPath, interpretedResultsPath, sourceInfo);
|
||||
}
|
||||
|
||||
export function ensureMetadataIsComplete(metadata: QueryMetadata | undefined) {
|
||||
if (metadata === undefined) {
|
||||
throw new Error('Can\'t interpret results without query metadata');
|
||||
}
|
||||
@@ -182,5 +195,5 @@ export async function interpretResults(
|
||||
// SARIF format does, so in the absence of one, we use a dummy id.
|
||||
id = 'dummy-id';
|
||||
}
|
||||
return await server.interpretBqrs({ kind, id, scored }, resultsPath, interpretedResultsPath, sourceInfo);
|
||||
return { kind, id, scored };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { Disposable, CancellationToken, commands } from 'vscode';
|
||||
import { createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc';
|
||||
import * as cli from './cli';
|
||||
@@ -136,6 +136,15 @@ export class QueryServerClient extends DisposableObject {
|
||||
const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);
|
||||
const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);
|
||||
|
||||
if (this.config.saveCache) {
|
||||
args.push('--save-cache');
|
||||
}
|
||||
|
||||
if (this.config.cacheSize > 0) {
|
||||
args.push('--max-disk-cache');
|
||||
args.push(this.config.cacheSize.toString());
|
||||
}
|
||||
|
||||
if (await this.supportsDatabaseRegistration()) {
|
||||
args.push('--require-db-registration');
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as path from 'path';
|
||||
import { CancellationToken, ExtensionContext, window as Window, workspace, Uri } from 'vscode';
|
||||
import {
|
||||
CancellationToken,
|
||||
ExtensionContext,
|
||||
window as Window,
|
||||
workspace,
|
||||
Uri
|
||||
} from 'vscode';
|
||||
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
import { logger } from './logging';
|
||||
import {
|
||||
getInitialQueryContents,
|
||||
getPrimaryDbscheme,
|
||||
getQlPackForDbscheme,
|
||||
showAndLogErrorMessage,
|
||||
showBinaryChoiceDialog,
|
||||
} from './helpers';
|
||||
import {
|
||||
@@ -21,23 +25,35 @@ import {
|
||||
const QUICK_QUERIES_DIR_NAME = 'quick-queries';
|
||||
const QUICK_QUERY_QUERY_NAME = 'quick-query.ql';
|
||||
const QUICK_QUERY_WORKSPACE_FOLDER_NAME = 'Quick Queries';
|
||||
const QLPACK_FILE_HEADER = '# This is an automatically generated file.\n\n';
|
||||
|
||||
export function isQuickQueryPath(queryPath: string): boolean {
|
||||
return path.basename(queryPath) === QUICK_QUERY_QUERY_NAME;
|
||||
}
|
||||
|
||||
function getQuickQueriesDir(ctx: ExtensionContext): string {
|
||||
async function getQuickQueriesDir(ctx: ExtensionContext): Promise<string> {
|
||||
const storagePath = ctx.storagePath;
|
||||
if (storagePath === undefined) {
|
||||
throw new Error('Workspace storage path is undefined');
|
||||
}
|
||||
const queriesPath = path.join(storagePath, QUICK_QUERIES_DIR_NAME);
|
||||
fs.ensureDir(queriesPath, { mode: 0o700 });
|
||||
await fs.ensureDir(queriesPath, { mode: 0o700 });
|
||||
return queriesPath;
|
||||
}
|
||||
|
||||
function updateQuickQueryDir(queriesDir: string, index: number, len: number) {
|
||||
workspace.updateWorkspaceFolders(
|
||||
index,
|
||||
len,
|
||||
{ uri: Uri.file(queriesDir), name: QUICK_QUERY_WORKSPACE_FOLDER_NAME }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function findExistingQuickQueryEditor() {
|
||||
return Window.visibleTextEditors.find(editor =>
|
||||
path.basename(editor.document.uri.fsPath) === QUICK_QUERY_QUERY_NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a buffer the user can enter a simple query into.
|
||||
@@ -50,26 +66,18 @@ export async function displayQuickQuery(
|
||||
token: CancellationToken
|
||||
) {
|
||||
|
||||
function updateQuickQueryDir(queriesDir: string, index: number, len: number) {
|
||||
workspace.updateWorkspaceFolders(
|
||||
index,
|
||||
len,
|
||||
{ uri: Uri.file(queriesDir), name: QUICK_QUERY_WORKSPACE_FOLDER_NAME }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const workspaceFolders = workspace.workspaceFolders || [];
|
||||
const queriesDir = await getQuickQueriesDir(ctx);
|
||||
|
||||
// If there is already a quick query open, don't clobber it, just
|
||||
// show it.
|
||||
const existing = workspace.textDocuments.find(doc => path.basename(doc.uri.fsPath) === QUICK_QUERY_QUERY_NAME);
|
||||
if (existing !== undefined) {
|
||||
Window.showTextDocument(existing);
|
||||
const existing = findExistingQuickQueryEditor();
|
||||
if (existing) {
|
||||
await Window.showTextDocument(existing.document);
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceFolders = workspace.workspaceFolders || [];
|
||||
const queriesDir = await getQuickQueriesDir(ctx);
|
||||
|
||||
// We need to have a multi-root workspace to make quick query work
|
||||
// at all. Changing the workspace from single-root to multi-root
|
||||
// causes a restart of the whole extension host environment, so we
|
||||
@@ -88,10 +96,11 @@ export async function displayQuickQuery(
|
||||
}
|
||||
|
||||
const index = workspaceFolders.findIndex(folder => folder.name === QUICK_QUERY_WORKSPACE_FOLDER_NAME);
|
||||
if (index === -1)
|
||||
if (index === -1) {
|
||||
updateQuickQueryDir(queriesDir, workspaceFolders.length, 0);
|
||||
else
|
||||
} else {
|
||||
updateQuickQueryDir(queriesDir, index, 1);
|
||||
}
|
||||
|
||||
// We're going to infer which qlpack to use from the current database
|
||||
const dbItem = await databaseUI.getDatabaseItem(progress, token);
|
||||
@@ -102,31 +111,38 @@ export async function displayQuickQuery(
|
||||
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
|
||||
const dbscheme = await getPrimaryDbscheme(datasetFolder);
|
||||
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
|
||||
|
||||
const quickQueryQlpackYaml: any = {
|
||||
name: 'quick-query',
|
||||
version: '1.0.0',
|
||||
libraryPathDependencies: [qlpack]
|
||||
};
|
||||
|
||||
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
||||
const qlPackFile = path.join(queriesDir, 'qlpack.yml');
|
||||
await fs.writeFile(qlFile, getInitialQueryContents(dbItem.language, dbscheme), 'utf8');
|
||||
await fs.writeFile(qlPackFile, yaml.safeDump(quickQueryQlpackYaml), 'utf8');
|
||||
Window.showTextDocument(await workspace.openTextDocument(qlFile));
|
||||
}
|
||||
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
||||
const shouldRewrite = await checkShouldRewrite(qlPackFile, qlpack);
|
||||
|
||||
// TODO: clean up error handling for top-level commands like this
|
||||
catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
logger.log(e.message);
|
||||
// Only rewrite the qlpack file if the database has changed
|
||||
if (shouldRewrite) {
|
||||
const quickQueryQlpackYaml: any = {
|
||||
name: 'quick-query',
|
||||
version: '1.0.0',
|
||||
libraryPathDependencies: [qlpack]
|
||||
};
|
||||
await fs.writeFile(qlPackFile, QLPACK_FILE_HEADER + yaml.safeDump(quickQueryQlpackYaml), 'utf8');
|
||||
}
|
||||
else if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
||||
logger.log(e.message);
|
||||
|
||||
if (shouldRewrite || !(await fs.pathExists(qlFile))) {
|
||||
await fs.writeFile(qlFile, getInitialQueryContents(dbItem.language, dbscheme), 'utf8');
|
||||
}
|
||||
else if (e instanceof Error)
|
||||
showAndLogErrorMessage(e.message);
|
||||
else
|
||||
|
||||
await Window.showTextDocument(await workspace.openTextDocument(qlFile));
|
||||
} catch (e) {
|
||||
if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
||||
throw new UserCancellationException(e.message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkShouldRewrite(qlPackFile: string, newDependency: string) {
|
||||
if (!(await fs.pathExists(qlPackFile))) {
|
||||
return true;
|
||||
}
|
||||
const qlPackContents: any = yaml.safeLoad(await fs.readFile(qlPackFile, 'utf8'));
|
||||
return qlPackContents.libraryPathDependencies?.[0] !== newDependency;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { QueryHistoryItemOptions } from './query-history';
|
||||
import * as qsClient from './queryserver-client';
|
||||
import { isQuickQueryPath } from './quick-query';
|
||||
import { compileDatabaseUpgradeSequence, hasNondestructiveUpgradeCapabilities, upgradeDatabaseExplicit } from './upgrades';
|
||||
import { ensureMetadataIsComplete } from './query-results';
|
||||
|
||||
/**
|
||||
* run-queries.ts
|
||||
@@ -53,6 +54,7 @@ export class QueryInfo {
|
||||
|
||||
readonly compiledQueryPath: string;
|
||||
readonly dilPath: string;
|
||||
readonly csvPath: string;
|
||||
readonly resultsPaths: ResultsPaths;
|
||||
readonly dataset: Uri; // guarantee the existence of a well-defined dataset dir at this point
|
||||
readonly queryID: number;
|
||||
@@ -68,6 +70,7 @@ export class QueryInfo {
|
||||
this.queryID = QueryInfo.nextQueryId++;
|
||||
this.compiledQueryPath = path.join(tmpDir.name, `compiledQuery${this.queryID}.qlo`);
|
||||
this.dilPath = path.join(tmpDir.name, `results${this.queryID}.dil`);
|
||||
this.csvPath = path.join(tmpDir.name, `results${this.queryID}.csv`);
|
||||
this.resultsPaths = {
|
||||
resultsPath: path.join(tmpDir.name, `results${this.queryID}.bqrs`),
|
||||
interpretedResultsPath: path.join(tmpDir.name, `interpretedResults${this.queryID}.sarif`)
|
||||
@@ -166,7 +169,12 @@ export class QueryInfo {
|
||||
if (!hasMetadataFile) {
|
||||
logger.log('Cannot produce interpreted results since the database does not have a .dbinfo or codeql-database.yml file.');
|
||||
}
|
||||
return hasMetadataFile;
|
||||
|
||||
const hasKind = !!this.metadata?.kind;
|
||||
if (!hasKind) {
|
||||
logger.log('Cannot produce interpreted results since the query does not have @kind metadata.');
|
||||
}
|
||||
return hasMetadataFile && hasKind;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +191,13 @@ export class QueryInfo {
|
||||
return fs.pathExists(this.dilPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this query already has CSV results produced
|
||||
*/
|
||||
async hasCsv(): Promise<boolean> {
|
||||
return fs.pathExists(this.csvPath);
|
||||
}
|
||||
|
||||
async ensureDilPath(qs: qsClient.QueryServerClient): Promise<string> {
|
||||
if (await this.hasDil()) {
|
||||
return this.dilPath;
|
||||
@@ -198,8 +213,27 @@ export class QueryInfo {
|
||||
return this.dilPath;
|
||||
}
|
||||
|
||||
async ensureCsvProduced(qs: qsClient.QueryServerClient): Promise<string> {
|
||||
if (await this.hasCsv()) {
|
||||
return this.csvPath;
|
||||
}
|
||||
|
||||
let sourceInfo;
|
||||
if (this.dbItem.sourceArchive !== undefined) {
|
||||
sourceInfo = {
|
||||
sourceArchive: this.dbItem.sourceArchive.fsPath,
|
||||
sourceLocationPrefix: await this.dbItem.getSourceLocationPrefix(
|
||||
qs.cliServer
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
await qs.cliServer.generateResultsCsv(ensureMetadataIsComplete(this.metadata), this.resultsPaths.resultsPath, this.csvPath, sourceInfo);
|
||||
return this.csvPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface QueryWithResults {
|
||||
readonly query: QueryInfo;
|
||||
readonly result: messages.EvaluationResult;
|
||||
@@ -294,7 +328,7 @@ async function checkDbschemeCompatibility(
|
||||
const searchPath = getOnDiskWorkspaceFolders();
|
||||
|
||||
if (query.dbItem.contents !== undefined && query.dbItem.contents.dbSchemeUri !== undefined) {
|
||||
const { finalDbscheme } = await cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath);
|
||||
const { finalDbscheme } = await cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath, false);
|
||||
const hash = async function(filename: string): Promise<string> {
|
||||
return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');
|
||||
};
|
||||
@@ -348,7 +382,7 @@ async function compileNonDestructiveUpgrade(
|
||||
if (!query.dbItem?.contents?.dbSchemeUri) {
|
||||
throw new Error('Database is invalid, and cannot be upgraded.');
|
||||
}
|
||||
const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath, query.queryDbscheme);
|
||||
const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath, true, query.queryDbscheme);
|
||||
|
||||
if (!matchesTarget) {
|
||||
reportNoUpgradePath(query);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ConfigurationChangeEvent, StatusBarAlignment, StatusBarItem, window, workspace } from 'vscode';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { CANARY_FEATURES, DistributionConfigListener } from './config';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { CANARY_FEATURES, CUSTOM_CODEQL_PATH_SETTING, DistributionConfigListener } from './config';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
|
||||
/**
|
||||
* Creates and manages a status bar item for codeql. THis item contains
|
||||
@@ -24,8 +24,14 @@ export class CodeQlStatusBarHandler extends DisposableObject {
|
||||
}
|
||||
|
||||
private handleDidChangeConfiguration(e: ConfigurationChangeEvent) {
|
||||
if (e.affectsConfiguration(CANARY_FEATURES.qualifiedName)) {
|
||||
this.updateStatusItem();
|
||||
if (
|
||||
e.affectsConfiguration(CANARY_FEATURES.qualifiedName) ||
|
||||
e.affectsConfiguration(CUSTOM_CODEQL_PATH_SETTING.qualifiedName)
|
||||
) {
|
||||
// Wait a few seconds before updating the status item.
|
||||
// This avoids a race condition where the cli's version
|
||||
// is not updated before the status bar is refreshed.
|
||||
setTimeout(() => this.updateStatusItem(), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ export class TelemetryListener extends ConfigListener {
|
||||
// Extension won't start until this completes.
|
||||
result = await showBinaryChoiceWithUrlDialog(
|
||||
'Does the CodeQL Extension by GitHub have your permission to collect usage data and metrics to help us improve CodeQL for VSCode?',
|
||||
'https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/TELEMETRY.md'
|
||||
'https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code'
|
||||
);
|
||||
}
|
||||
if (result !== undefined) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
|
||||
import { QLTestFile, QLTestNode, QLTestDirectory, QLTestDiscovery } from './qltest-discovery';
|
||||
import { Event, EventEmitter, CancellationTokenSource, CancellationToken } from 'vscode';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { getOnDiskWorkspaceFolders } from './helpers';
|
||||
import { testLogger } from './logging';
|
||||
@@ -223,8 +223,8 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
|
||||
? 'errored'
|
||||
: 'failed';
|
||||
let message: string | undefined;
|
||||
if (event.diff?.length) {
|
||||
message = ['', `${state}: ${event.test}`, ...event.diff, ''].join('\n');
|
||||
if (event.failureDescription || event.diff?.length) {
|
||||
message = ['', `${state}: ${event.test}`, event.failureDescription || event.diff?.join('\n'), ''].join('\n');
|
||||
testLogger.log(message);
|
||||
}
|
||||
this._testStates.fire({
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
|
||||
import { showAndLogWarningMessage } from './helpers';
|
||||
import { TestTreeNode } from './test-tree-node';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { UIService } from './vscode-utils/ui-service';
|
||||
import { QLTestAdapter, getExpectedFile, getActualFile } from './test-adapter';
|
||||
import { logger } from './logging';
|
||||
|
||||
@@ -171,7 +171,8 @@ export async function upgradeDatabaseExplicit(
|
||||
}
|
||||
const upgradeInfo = await qs.cliServer.resolveUpgrades(
|
||||
db.contents.dbSchemeUri.fsPath,
|
||||
searchPath
|
||||
searchPath,
|
||||
false
|
||||
);
|
||||
|
||||
const { scripts, finalDbscheme } = upgradeInfo;
|
||||
|
||||
@@ -44,7 +44,6 @@ describe('Databases', function() {
|
||||
|
||||
afterEach(() => {
|
||||
try {
|
||||
// dispose();
|
||||
sandbox.restore();
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
|
||||
@@ -16,14 +16,12 @@ export const DB_URL = 'https://github.com/github/vscode-codeql/files/5586722/sim
|
||||
export const dbLoc = path.join(fs.realpathSync(path.join(__dirname, '../../../')), 'build/tests/db.zip');
|
||||
export let storagePath: string;
|
||||
|
||||
// See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/49860
|
||||
// Should be of type Mocha
|
||||
export default function(mocha: /*Mocha*/ any) {
|
||||
export default function(mocha: Mocha) {
|
||||
// create an extension storage location
|
||||
let removeStorage: tmp.DirResult['removeCallback'] | undefined;
|
||||
|
||||
mocha.globalSetup([
|
||||
// ensure the test database is downloaded
|
||||
// ensure the test database is downloaded
|
||||
(mocha.options as any).globalSetup.push(
|
||||
async () => {
|
||||
fs.mkdirpSync(path.dirname(dbLoc));
|
||||
if (!fs.existsSync(dbLoc)) {
|
||||
@@ -44,14 +42,18 @@ export default function(mocha: /*Mocha*/ any) {
|
||||
fail('Failed to download test database: ' + e);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Set the CLI version here before activation to ensure we don't accidentally try to download a cli
|
||||
// Set the CLI version here before activation to ensure we don't accidentally try to download a cli
|
||||
(mocha.options as any).globalSetup.push(
|
||||
async () => {
|
||||
await workspace.getConfiguration().update('codeQL.cli.executablePath', process.env.CLI_PATH, ConfigurationTarget.Global);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Create the temp directory to be used as extension local storage.
|
||||
// Create the temp directory to be used as extension local storage.
|
||||
(mocha.options as any).globalSetup.push(
|
||||
() => {
|
||||
const dir = tmp.dirSync();
|
||||
storagePath = fs.realpathSync(dir.name);
|
||||
@@ -61,10 +63,10 @@ export default function(mocha: /*Mocha*/ any) {
|
||||
|
||||
removeStorage = dir.removeCallback;
|
||||
}
|
||||
]);
|
||||
);
|
||||
|
||||
mocha.globalTeardown([
|
||||
// ensure etension is cleaned up.
|
||||
// ensure etension is cleaned up.
|
||||
(mocha.options as any).globalTeardown.push(
|
||||
async () => {
|
||||
const extension = await extensions.getExtension<CodeQLExtensionInterface | {}>('GitHub.vscode-codeql')!.activate();
|
||||
// This shuts down the extension and can only be run after all tests have completed.
|
||||
@@ -72,10 +74,13 @@ export default function(mocha: /*Mocha*/ any) {
|
||||
if ('dispose' in extension) {
|
||||
extension.dispose();
|
||||
}
|
||||
},
|
||||
// ensure temp directory is cleaned up.
|
||||
}
|
||||
);
|
||||
|
||||
// ensure temp directory is cleaned up.
|
||||
(mocha.options as any).globalTeardown.push(
|
||||
() => {
|
||||
removeStorage?.();
|
||||
}
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { fail } from 'assert';
|
||||
import { CancellationToken, commands, extensions, Uri } from 'vscode';
|
||||
import { CancellationToken, commands, ExtensionContext, extensions, Uri } from 'vscode';
|
||||
import * as sinon from 'sinon';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
import { DatabaseItem, DatabaseManager } from '../../databases';
|
||||
import { CodeQLExtensionInterface } from '../../extension';
|
||||
@@ -34,6 +35,11 @@ describe('Queries', function() {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let progress: sinon.SinonSpy;
|
||||
let token: CancellationToken;
|
||||
let ctx: ExtensionContext;
|
||||
|
||||
let qlpackFile: string;
|
||||
let qlFile: string;
|
||||
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
@@ -45,6 +51,9 @@ describe('Queries', function() {
|
||||
cli = extension.cliServer;
|
||||
qs = extension.qs;
|
||||
cli.quiet = true;
|
||||
ctx = extension.ctx;
|
||||
qlpackFile = `${ctx.storagePath}/quick-queries/qlpack.yml`;
|
||||
qlFile = `${ctx.storagePath}/quick-queries/quick-query.ql`;
|
||||
} else {
|
||||
throw new Error('Extension not initialized. Make sure cli is downloaded and installed properly.');
|
||||
}
|
||||
@@ -126,4 +135,42 @@ describe('Queries', function() {
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
|
||||
it('should create a quick query', async () => {
|
||||
safeDel(qlFile);
|
||||
safeDel(qlpackFile);
|
||||
|
||||
await commands.executeCommand('codeQL.quickQuery');
|
||||
|
||||
// should have created the quick query file and query pack file
|
||||
expect(fs.pathExistsSync(qlFile)).to.be.true;
|
||||
expect(fs.pathExistsSync(qlpackFile)).to.be.true;
|
||||
|
||||
const qlpackContents: any = await yaml.safeLoad(
|
||||
fs.readFileSync(qlpackFile, 'utf8')
|
||||
);
|
||||
// Should have chosen the js libraries
|
||||
expect(qlpackContents.libraryPathDependencies[0]).to.eq('codeql-javascript');
|
||||
});
|
||||
|
||||
it('should avoid creating a quick query', async () => {
|
||||
fs.writeFileSync(qlpackFile, yaml.safeDump({
|
||||
name: 'quick-query',
|
||||
version: '1.0.0',
|
||||
libraryPathDependencies: ['codeql-javascript']
|
||||
}));
|
||||
fs.writeFileSync(qlFile, 'xxx');
|
||||
await commands.executeCommand('codeQL.quickQuery');
|
||||
|
||||
// should not have created the quick query file because database schema hasn't changed
|
||||
expect(fs.readFileSync(qlFile, 'utf8')).to.eq('xxx');
|
||||
});
|
||||
|
||||
function safeDel(file: string) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
import { ensureCli } from './ensureCli';
|
||||
import { env } from 'vscode';
|
||||
|
||||
|
||||
// Use this handler to avoid swallowing unhandled rejections.
|
||||
@@ -42,8 +43,17 @@ export async function runTestsInDirectory(testsRoot: string, useCli = false): Pr
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'bdd',
|
||||
color: true
|
||||
});
|
||||
color: true,
|
||||
globalSetup: [],
|
||||
globalTeardown: [],
|
||||
} as any);
|
||||
|
||||
(mocha.options as any).globalSetup.push(
|
||||
// convert this function into an noop since it should not run during tests.
|
||||
// If it does run during tests, then it can cause some testing environments
|
||||
// to hang.
|
||||
(env as any).openExternal = () => { /**/ }
|
||||
);
|
||||
|
||||
await ensureCli(useCli);
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ import {
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('config listeners', () => {
|
||||
describe('config listeners', function() {
|
||||
// Because we are adding some extra waiting, need to bump the test timeouts.
|
||||
this.timeout(5000);
|
||||
|
||||
let sandbox: Sinon.SinonSandbox;
|
||||
beforeEach(() => {
|
||||
sandbox = Sinon.createSandbox();
|
||||
@@ -26,7 +29,7 @@ describe('config listeners', () => {
|
||||
});
|
||||
|
||||
interface TestConfig<T> {
|
||||
clazz: new() => {};
|
||||
clazz: new () => {};
|
||||
settings: {
|
||||
name: string;
|
||||
property: string;
|
||||
@@ -38,6 +41,10 @@ describe('config listeners', () => {
|
||||
{
|
||||
clazz: CliConfigListener,
|
||||
settings: [{
|
||||
name: 'codeQL.runningQueries.numberOfThreads',
|
||||
property: 'numberThreads',
|
||||
values: [0, 1]
|
||||
}, {
|
||||
name: 'codeQL.runningTests.numberOfThreads',
|
||||
property: 'numberTestThreads',
|
||||
values: [1, 0]
|
||||
@@ -57,6 +64,14 @@ describe('config listeners', () => {
|
||||
name: 'codeQL.runningQueries.numberOfThreads',
|
||||
property: 'numThreads',
|
||||
values: [0, 1]
|
||||
}, {
|
||||
name: 'codeQL.runningQueries.saveCache',
|
||||
property: 'saveCache',
|
||||
values: [false, true]
|
||||
}, {
|
||||
name: 'codeQL.runningQueries.cacheSize',
|
||||
property: 'cacheSize',
|
||||
values: [0, 1]
|
||||
}, {
|
||||
name: 'codeQL.runningQueries.memory',
|
||||
property: 'queryMemoryMb',
|
||||
@@ -84,19 +99,31 @@ describe('config listeners', () => {
|
||||
beforeEach(async () => {
|
||||
origValue = workspace.getConfiguration().get(setting.name);
|
||||
await workspace.getConfiguration().update(setting.name, setting.values[0]);
|
||||
await wait();
|
||||
spy.resetHistory();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace.getConfiguration().update(setting.name, origValue);
|
||||
await wait();
|
||||
});
|
||||
|
||||
it(`should listen for changes to '${setting.name}'`, async () => {
|
||||
await workspace.getConfiguration().update(setting.name, setting.values[1]);
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
await wait();
|
||||
expect(listener[setting.property]).to.eq(setting.values[1]);
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Need to wait some time since the onDidChangeConfiguration listeners fire
|
||||
// asynchronously and we sometimes need to wait for them to complete in
|
||||
// order to have as successful test.
|
||||
async function wait(ms = 50) {
|
||||
return new Promise(resolve =>
|
||||
setTimeout(resolve, ms)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { registerDatabases } from '../../pure/messages';
|
||||
import { ProgressCallback } from '../../commandRunner';
|
||||
import { CodeQLCliServer } from '../../cli';
|
||||
import { encodeArchiveBasePath, encodeSourceArchiveUri } from '../../archive-filesystem-provider';
|
||||
import { testDisposeHandler } from '../test-dispose-handler';
|
||||
|
||||
describe('databases', () => {
|
||||
|
||||
@@ -85,7 +86,7 @@ describe('databases', () => {
|
||||
|
||||
afterEach(async () => {
|
||||
dir.removeCallback();
|
||||
databaseManager.dispose();
|
||||
databaseManager.dispose(testDisposeHandler);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as yaml from 'js-yaml';
|
||||
import { AstViewer, AstItem } from '../../astViewer';
|
||||
import { commands, Range } from 'vscode';
|
||||
import { DatabaseItem } from '../../databases';
|
||||
import { testDisposeHandler } from '../test-dispose-handler';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
@@ -31,7 +32,7 @@ describe('AstViewer', () => {
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
if (viewer) {
|
||||
viewer.dispose();
|
||||
viewer.dispose(testDisposeHandler);
|
||||
viewer = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@ describe('AstBuilder', () => {
|
||||
let mockCli: CodeQLCliServer;
|
||||
let overrides: Record<string, object | undefined>;
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
mockCli = {
|
||||
bqrsDecode: sinon.stub().callsFake((_: string, resultSet: 'nodes' | 'edges' | 'graphProperties') => {
|
||||
|
||||
@@ -55,7 +55,18 @@ describe('databaseFetcher', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail on a nonexistant prohect', async () => {
|
||||
it('should convert a raw slug to a database url with extra path segments', async () => {
|
||||
quickPickSpy.resolves('python');
|
||||
const lgtmUrl =
|
||||
'g/github/codeql';
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
|
||||
expect(dbUrl).to.equal(
|
||||
'https://lgtm.com/api/v1.0/snapshots/1506465042581/python'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail on a nonexistent project', async () => {
|
||||
quickPickSpy.resolves('javascript');
|
||||
const lgtmUrl = 'https://lgtm.com/projects/g/github/hucairz';
|
||||
expect(convertToDatabaseUrl(lgtmUrl)).to.rejectedWith(/Invalid LGTM URL/);
|
||||
@@ -71,6 +82,10 @@ describe('databaseFetcher', function() {
|
||||
.to.be.false;
|
||||
expect(looksLikeLgtmUrl('https://ww.lgtm.com/projects/g/github')).to.be
|
||||
.false;
|
||||
expect(looksLikeLgtmUrl('g/github')).to.be
|
||||
.false;
|
||||
expect(looksLikeLgtmUrl('ggg/github/myproj')).to.be
|
||||
.false;
|
||||
});
|
||||
|
||||
it('should handle valid urls', () => {
|
||||
@@ -86,6 +101,10 @@ describe('databaseFetcher', function() {
|
||||
'https://lgtm.com/projects/g/github/codeql/sub/pages?query=string'
|
||||
)
|
||||
).to.be.true;
|
||||
expect(looksLikeLgtmUrl('g/github/myproj')).to.be
|
||||
.true;
|
||||
expect(looksLikeLgtmUrl('git/github/myproj')).to.be
|
||||
.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { expect } from 'chai';
|
||||
import { Uri } from 'vscode';
|
||||
|
||||
import { DatabaseUI } from '../../databases-ui';
|
||||
import { testDisposeHandler } from '../test-dispose-handler';
|
||||
|
||||
describe('databases-ui', () => {
|
||||
describe('fixDbUri', () => {
|
||||
@@ -89,7 +90,7 @@ describe('databases-ui', () => {
|
||||
expect(fs.pathExistsSync(db4)).to.be.false;
|
||||
expect(fs.pathExistsSync(db5)).to.be.false;
|
||||
|
||||
databaseUI.dispose();
|
||||
databaseUI.dispose(testDisposeHandler);
|
||||
});
|
||||
|
||||
function createDatabase(storageDir: string, dbName: string, language: string, extraFile?: string) {
|
||||
|
||||
@@ -7,7 +7,14 @@ import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { getInitialQueryContents, InvocationRateLimiter, isLikelyDbLanguageFolder, showBinaryChoiceDialog, showBinaryChoiceWithUrlDialog, showInformationMessageWithAction } from '../../helpers';
|
||||
import {
|
||||
getInitialQueryContents,
|
||||
InvocationRateLimiter,
|
||||
isLikelyDbLanguageFolder,
|
||||
showBinaryChoiceDialog,
|
||||
showBinaryChoiceWithUrlDialog,
|
||||
showInformationMessageWithAction
|
||||
} from '../../helpers';
|
||||
import { reportStreamProgress } from '../../commandRunner';
|
||||
import Sinon = require('sinon');
|
||||
import { fail } from 'assert';
|
||||
|
||||
@@ -112,18 +112,21 @@ function getLaunchArgs(dir: TestDir) {
|
||||
switch (dir) {
|
||||
case TestDir.NoWorksspace:
|
||||
return [
|
||||
'--disable-extensions'
|
||||
'--disable-extensions',
|
||||
'--disable-gpu'
|
||||
];
|
||||
|
||||
case TestDir.MinimalWorksspace:
|
||||
return [
|
||||
'--disable-extensions',
|
||||
'--disable-gpu',
|
||||
path.resolve(__dirname, '../../test/data')
|
||||
];
|
||||
|
||||
case TestDir.CliIntegration:
|
||||
// CLI integration tests requires a multi-root workspace so that the data and the QL sources are accessible.
|
||||
return [
|
||||
'--disable-gpu',
|
||||
path.resolve(__dirname, '../../test/data'),
|
||||
process.env.TEST_CODEQL_PATH!
|
||||
];
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Disposable } from 'vscode';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
|
||||
export function testDisposeHandler(disposable: any & Disposable) {
|
||||
if (disposable.onDidExpandElement && disposable.onDidCollapseElement && disposable.reveal) {
|
||||
// This looks like a treeViewer. Don't dispose
|
||||
return;
|
||||
}
|
||||
if (disposable instanceof DisposableObject) {
|
||||
disposable.dispose(testDisposeHandler);
|
||||
} else {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from './disposable-object';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { EventEmitter, Event, Uri, GlobPattern, workspace } from 'vscode';
|
||||
|
||||
/**
|
||||
@@ -62,4 +62,3 @@ export class MultiFileSystemWatcher extends DisposableObject {
|
||||
this._onDidChange.fire(uri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TreeDataProvider, window } from 'vscode';
|
||||
import { DisposableObject } from './disposable-object';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { commandRunner } from '../commandRunner';
|
||||
|
||||
/**
|
||||
|
||||
122
extensions/ql-vscode/test/pure-tests/disposable-object.test.ts
Normal file
122
extensions/ql-vscode/test/pure-tests/disposable-object.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'chai';
|
||||
import 'chai/register-should';
|
||||
import 'sinon-chai';
|
||||
import * as sinon from 'sinon';
|
||||
import 'mocha';
|
||||
|
||||
import { DisposableObject } from '../../src/pure/disposable-object';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('DisposableObject and DisposeHandler', () => {
|
||||
|
||||
let disposable1: { dispose: sinon.SinonSpy };
|
||||
let disposable2: { dispose: sinon.SinonSpy };
|
||||
let disposable3: { dispose: sinon.SinonSpy };
|
||||
let disposable4: { dispose: sinon.SinonSpy };
|
||||
let disposableObject: any;
|
||||
let nestedDisposableObject: any;
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.restore();
|
||||
disposable1 = { dispose: sandbox.spy() };
|
||||
disposable2 = { dispose: sandbox.spy() };
|
||||
disposable3 = { dispose: sandbox.spy() };
|
||||
disposable4 = { dispose: sandbox.spy() };
|
||||
|
||||
disposableObject = new MyDisposableObject();
|
||||
nestedDisposableObject = new MyDisposableObject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should dispose tracked and pushed objects', () => {
|
||||
disposableObject.push(disposable1);
|
||||
disposableObject.push(disposable2);
|
||||
disposableObject.track(nestedDisposableObject);
|
||||
nestedDisposableObject.track(disposable3);
|
||||
|
||||
disposableObject.dispose();
|
||||
|
||||
expect(disposable1.dispose).to.have.been.called;
|
||||
expect(disposable2.dispose).to.have.been.called;
|
||||
expect(disposable3.dispose).to.have.been.called;
|
||||
|
||||
// pushed items must be called in reverse order
|
||||
sinon.assert.callOrder(disposable2.dispose, disposable1.dispose);
|
||||
|
||||
// now that disposableObject has been disposed, subsequent disposals are
|
||||
// no-ops
|
||||
disposable1.dispose.resetHistory();
|
||||
disposable2.dispose.resetHistory();
|
||||
disposable3.dispose.resetHistory();
|
||||
|
||||
disposableObject.dispose();
|
||||
|
||||
expect(disposable1.dispose).not.to.have.been.called;
|
||||
expect(disposable2.dispose).not.to.have.been.called;
|
||||
expect(disposable3.dispose).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should dispose and stop tracking objects', () => {
|
||||
disposableObject.track(disposable1);
|
||||
disposableObject.disposeAndStopTracking(disposable1);
|
||||
|
||||
expect(disposable1.dispose).to.have.been.called;
|
||||
disposable1.dispose.resetHistory();
|
||||
|
||||
disposableObject.dispose();
|
||||
expect(disposable1.dispose).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should avoid disposing an object that is not tracked', () => {
|
||||
disposableObject.push(disposable1);
|
||||
disposableObject.disposeAndStopTracking(disposable1);
|
||||
|
||||
expect(disposable1.dispose).not.to.have.been.called;
|
||||
|
||||
disposableObject.dispose();
|
||||
expect(disposable1.dispose).to.have.been.called;
|
||||
});
|
||||
|
||||
it('ahould use a dispose handler', () => {
|
||||
const handler = (d: any) => (d === disposable1 || d === disposable3 || d === nestedDisposableObject)
|
||||
? d.dispose(handler)
|
||||
: void (0);
|
||||
|
||||
disposableObject.push(disposable1);
|
||||
disposableObject.push(disposable2);
|
||||
disposableObject.track(nestedDisposableObject);
|
||||
nestedDisposableObject.track(disposable3);
|
||||
nestedDisposableObject.track(disposable4);
|
||||
|
||||
disposableObject.dispose(handler);
|
||||
|
||||
expect(disposable1.dispose).to.have.been.called;
|
||||
expect(disposable2.dispose).not.to.have.been.called;
|
||||
expect(disposable3.dispose).to.have.been.called;
|
||||
expect(disposable4.dispose).not.to.have.been.called;
|
||||
|
||||
// now that disposableObject has been disposed, subsequent disposals are
|
||||
// no-ops
|
||||
disposable1.dispose.resetHistory();
|
||||
disposable2.dispose.resetHistory();
|
||||
disposable3.dispose.resetHistory();
|
||||
disposable4.dispose.resetHistory();
|
||||
|
||||
disposableObject.dispose();
|
||||
|
||||
expect(disposable1.dispose).not.to.have.been.called;
|
||||
expect(disposable2.dispose).not.to.have.been.called;
|
||||
expect(disposable3.dispose).not.to.have.been.called;
|
||||
expect(disposable4.dispose).not.to.have.been.called;
|
||||
});
|
||||
|
||||
class MyDisposableObject extends DisposableObject {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
|
||||
Reference in New Issue
Block a user