Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bbb14edd4 | ||
|
|
fb5675a7c5 | ||
|
|
82a2db9fec | ||
|
|
25d85c3e61 | ||
|
|
81e6d8583a | ||
|
|
ea9dede453 | ||
|
|
e9062551ee | ||
|
|
dd2e79477f | ||
|
|
e9787c2702 | ||
|
|
56d283f6d5 | ||
|
|
fd6cd1f2d2 | ||
|
|
57a4a2f717 | ||
|
|
ff0425d889 | ||
|
|
5fd902257e | ||
|
|
55761aa4ee | ||
|
|
1bf7fc148a | ||
|
|
590b839166 | ||
|
|
fd57133a41 | ||
|
|
f39bbd325c | ||
|
|
9a53b637e8 | ||
|
|
eb25f31b9f | ||
|
|
7188d8df41 | ||
|
|
badbee1bfb | ||
|
|
2f92ea396a | ||
|
|
1f4790bbb7 | ||
|
|
28abc1e3a6 | ||
|
|
9b7c3bc2bf | ||
|
|
4d73e1a068 | ||
|
|
6f5ac5df4f | ||
|
|
4c880dfb19 | ||
|
|
56e8d8aac7 | ||
|
|
ced9f60949 | ||
|
|
4fa530d69d | ||
|
|
c0a65c994a | ||
|
|
921d9d22e4 | ||
|
|
ead1869a7e | ||
|
|
b1ddf89fe3 | ||
|
|
c37096bf2c | ||
|
|
4127be2905 | ||
|
|
ce29768796 | ||
|
|
571d9d1424 | ||
|
|
65e652b5e4 | ||
|
|
fd2b91d4d4 | ||
|
|
dd4df012e9 | ||
|
|
af167c6d6e | ||
|
|
551ed95fc8 | ||
|
|
9b1ca5136e | ||
|
|
fa5bad6946 | ||
|
|
6229de8634 | ||
|
|
4c14db951b | ||
|
|
bed4e8a060 | ||
|
|
2c6dc24525 | ||
|
|
8d5a00dcc7 | ||
|
|
3ea3cd8e9b | ||
|
|
2a43ffb49a | ||
|
|
70ae7284f3 |
134
.github/codeql/queries/unique-command-use.ql
vendored
Normal file
134
.github/codeql/queries/unique-command-use.ql
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @name A VS Code command should not be used in multiple locations
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id vscode-codeql/unique-command-use
|
||||
* @description Using each VS Code command from only one location makes
|
||||
* our telemetry more useful, because we can differentiate more user
|
||||
* interactions and know which features of the UI our users are using.
|
||||
* To fix this alert, new commands will need to be made so that each one
|
||||
* is only used from one location. The commands should share the same
|
||||
* implementation so we do not introduce duplicate code.
|
||||
* When fixing this alert, search the codebase for all other references
|
||||
* to the command name. The location of the alert is an arbitrarily
|
||||
* chosen usage of the command, and may not necessarily be the location
|
||||
* that should be changed to fix the alert.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* The name of a VS Code command.
|
||||
*/
|
||||
class CommandName extends string {
|
||||
CommandName() { exists(CommandUsage e | e.getCommandName() = this) }
|
||||
|
||||
/**
|
||||
* In how many ways is this command used. Will always be at least 1.
|
||||
*/
|
||||
int getNumberOfUsages() { result = count(this.getAUse()) }
|
||||
|
||||
/**
|
||||
* Get a usage of this command.
|
||||
*/
|
||||
CommandUsage getAUse() { result.getCommandName() = this }
|
||||
|
||||
/**
|
||||
* Get the canonical first usage of this command, to use for the location
|
||||
* of the alert. The implementation of this ordering of usages is arbitrary
|
||||
* and the usage given may not be the one that should be changed when fixing
|
||||
* the alert.
|
||||
*/
|
||||
CommandUsage getFirstUsage() {
|
||||
result =
|
||||
max(CommandUsage use |
|
||||
use = this.getAUse()
|
||||
|
|
||||
use
|
||||
order by
|
||||
use.getFile().getRelativePath(), use.getLocation().getStartLine(),
|
||||
use.getLocation().getStartColumn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single usage of a command, either from within code or
|
||||
* from the command's definition in package.json
|
||||
*/
|
||||
abstract class CommandUsage extends Locatable {
|
||||
abstract string getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `executeCommand`.
|
||||
*/
|
||||
class CommandUsageCallExpr extends CommandUsage, CallExpr {
|
||||
CommandUsageCallExpr() {
|
||||
this.getCalleeName() = "executeCommand" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from any menu that isn't the command palette.
|
||||
* This means a user could invoke the command by clicking on a button in
|
||||
* something like a menu or a dropdown.
|
||||
*/
|
||||
class CommandUsagePackageJsonMenuItem extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonMenuItem() {
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson, string menuName |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue(menuName)
|
||||
.getElementValue(_) = this and
|
||||
menuName != "commandPalette"
|
||||
)
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given command disabled for use in the command palette by
|
||||
* a block with a `"when": "false"` field.
|
||||
*/
|
||||
predicate isDisabledInCommandPalette(string commandName) {
|
||||
exists(PackageJson packageJson, JsonObject commandPaletteObject |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue("commandPalette")
|
||||
.getElementValue(_) = commandPaletteObject and
|
||||
commandPaletteObject.getPropValue("command").getStringValue() = commandName and
|
||||
commandPaletteObject.getPropValue("when").getStringValue() = "false"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a command being usable from the command palette.
|
||||
* This means that a user could choose to manually invoke the command.
|
||||
*/
|
||||
class CommandUsagePackageJsonCommandPalette extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonCommandPalette() {
|
||||
this.getFile().getBaseName() = "package.json" and
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson |
|
||||
packageJson.getPropValue("contributes").getPropValue("commands").getElementValue(_) = this
|
||||
) and
|
||||
not isDisabledInCommandPalette(this.getPropValue("command").getStringValue())
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
from CommandName c
|
||||
where c.getNumberOfUsages() > 1
|
||||
select c.getFirstUsage(),
|
||||
"The " + c + " command is used from " + c.getNumberOfUsages() + " locations"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## [UNRELEASED]
|
||||
## 1.7.11 - 1 March 2023
|
||||
|
||||
# 1.7.10 - 23 February 2023
|
||||
- Enable collection of telemetry concerning interactions with UI elements, including buttons, links, and other inputs. [#2114](https://github.com/github/vscode-codeql/pull/2114)
|
||||
- Prevent the automatic installation of CodeQL CLI version 2.12.3 to avoid a bug in the language server. CodeQL CLI 2.12.2 will be used instead. [#2126](https://github.com/github/vscode-codeql/pull/2126)
|
||||
|
||||
## 1.7.10 - 23 February 2023
|
||||
|
||||
- Fix bug that was causing unwanted error notifications.
|
||||
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
This document describes the manual test plan for the QL extension for Visual Studio Code.
|
||||
|
||||
The plan will be executed manually to start with but the goal is to eventually automate parts of the process (based on
|
||||
The plan will be executed manually to start with but the goal is to eventually automate parts of the process (based on
|
||||
effort vs value basis).
|
||||
|
||||
#### What this doesn't cover
|
||||
## What this doesn't cover
|
||||
|
||||
We don't need to test features (and permutations of features) that are covered by automated tests.
|
||||
|
||||
### Before releasing the VS Code extension
|
||||
## Before releasing the VS Code extension
|
||||
|
||||
- Go through the required test cases listed below
|
||||
- Check major PRs since the previous release for specific one-off things to test. Based on that, you might want to
|
||||
- Check major PRs since the previous release for specific one-off things to test. Based on that, you might want to
|
||||
choose to go through some of the Optional Test Cases.
|
||||
- Run a query using the existing version of the extension (to generate an "old" query history item)
|
||||
|
||||
@@ -24,23 +26,25 @@ choose to go through some of the Optional Test Cases.
|
||||
|
||||
1. Open the [UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql).
|
||||
2. Run a MRVA against the following repo list:
|
||||
```
|
||||
{
|
||||
"name": "test-repo-list",
|
||||
"repositories": [
|
||||
"angular-cn/ng-nice",
|
||||
"apache/hadoop",
|
||||
"apache/hive"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "test-repo-list",
|
||||
"repositories": [
|
||||
"angular-cn/ng-nice",
|
||||
"apache/hadoop",
|
||||
"apache/hive"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. Check that a notification message pops up and the results view is opened.
|
||||
4. Check the query history. It should:
|
||||
- Show that an item has been added to the query history
|
||||
- The item should be marked as "in progress".
|
||||
5. Once the query starts:
|
||||
- Check the results view
|
||||
- Check the code paths view, including the code paths drop down menu.
|
||||
- Check the results view
|
||||
- Check the code paths view, including the code paths drop down menu.
|
||||
- Check that the repository filter box works
|
||||
- Click links to files/locations on GitHub
|
||||
- Check that the query history item is updated to show the number of results
|
||||
@@ -51,9 +55,7 @@ choose to go through some of the Optional Test Cases.
|
||||
|
||||
1. Open the [ReDoS query](https://github.com/github/codeql/blob/main/javascript/ql/src/Performance/ReDoS.ql).
|
||||
2. Run a MRVA against the "Top 10" repositories.
|
||||
3. Check the notification message. It should:
|
||||
- Show the number of repos that are going to be queried
|
||||
- Provide a link to the actions workflow
|
||||
3. Check that there is a notification message.
|
||||
4. Check the query history. It should:
|
||||
- Show that an item has been added to the query history
|
||||
- The item should be marked as "in progress".
|
||||
@@ -76,22 +78,22 @@ choose to go through some of the Optional Test Cases.
|
||||
1. Click a history item (for MRVA):
|
||||
- Check that exporting results works
|
||||
- Check that sorting results works
|
||||
- Check that copying repo lists works
|
||||
2. Open the query directory (containing results):
|
||||
- Check that copying repo lists works
|
||||
2. Open the query results directory:
|
||||
- Check that the correct directory is opened and there are results in it
|
||||
3. Open variant analysis on GitHub
|
||||
3. View logs
|
||||
- Check that the correct workflow is opened
|
||||
|
||||
### Test Case 5: MRVA - Canceling a variant analysis run
|
||||
|
||||
Run one of the above MRVAs, but cancel it from within VS Code:
|
||||
- Check that the query is canceled and the query history item is updated.
|
||||
- Check that the workflow run is also canceled.
|
||||
- Check that the workflow run is also canceled.
|
||||
- Check that any available results are visible in VS Code.
|
||||
|
||||
### Test Case 6: MRVA - Change to a different colour theme
|
||||
### Test Case 6: MRVA - Change to a different colour theme
|
||||
|
||||
Open one of the above MRVAs, try changing to a different colour theme and check that everything looks sensible.
|
||||
Open one of the above MRVAs, try changing to a different colour theme and check that everything looks sensible.
|
||||
Are there any components that are not showing up?
|
||||
|
||||
## Optional Test Cases
|
||||
@@ -101,9 +103,10 @@ These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA
|
||||
### Selecting repositories to run on
|
||||
|
||||
#### Test case 1: Running a query on a single repository
|
||||
1. When the repository exists and is public
|
||||
1. Has a CodeQL database for the correct language
|
||||
2. Has a CodeQL database for another language
|
||||
|
||||
1. When the repository exists and is public
|
||||
1. Has a CodeQL database for the correct language
|
||||
2. Has a CodeQL database for another language
|
||||
3. Does not have any CodeQL databases
|
||||
2. When the repository exists and is private
|
||||
1. Is accessible and has a CodeQL database
|
||||
@@ -111,14 +114,16 @@ These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA
|
||||
3. When the repository does not exist
|
||||
|
||||
#### Test case 2: Running a query on a custom repository list
|
||||
|
||||
1. The repository list is non-empty
|
||||
1. All repositories in the list have a CodeQL database
|
||||
1. All repositories in the list have a CodeQL database
|
||||
2. Some but not all repositories in the list have a CodeQL database
|
||||
3. No repositories in the list have a CodeQL database
|
||||
2. The repository list is empty
|
||||
|
||||
#### Test case 3: Running a query on all repositories in an organization
|
||||
1. The org exists
|
||||
|
||||
1. The org exists
|
||||
1. The org contains repositories that have CodeQL databases
|
||||
2. The org contains repositories of the right language but without CodeQL databases
|
||||
3. The org contains repositories not of the right language
|
||||
@@ -128,20 +133,25 @@ These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA
|
||||
### Using different types of controller repos
|
||||
|
||||
#### Test case 1: Running a query when the controller repository is public
|
||||
|
||||
1. Can run queries on public repositories
|
||||
2. Can not run queries on private repositories
|
||||
|
||||
#### Test case 2: Running a query when the controller repository is private
|
||||
|
||||
1. Can run queries on public repositories
|
||||
2. Can run queries on private repositories
|
||||
|
||||
#### Test case 3: Running a query when the controller repo exists but you do not have write access
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
#### Test case 4: Running a query when the controller repo doesn’t exist
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
#### Test case 5: Running a query when the "config field" for the controller repo is not set
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
### Query History
|
||||
@@ -152,6 +162,7 @@ The first test case specifies actions that you can do when the query is first ru
|
||||
with this since it has quite a limited number of actions you can do.
|
||||
|
||||
#### Test case 1: When variant analysis state is "pending"
|
||||
|
||||
1. Starts monitoring variant analysis
|
||||
2. Cannot open query history item
|
||||
3. Can delete a query history item
|
||||
@@ -162,10 +173,10 @@ with this since it has quite a limited number of actions you can do.
|
||||
2. By query date
|
||||
3. By result count
|
||||
5. Cannot open query directory
|
||||
6. Can open query that produced these results
|
||||
1. When the file still exists and has not moved
|
||||
6. Can open query that produced these results
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
7. Cannot open variant analysis on github
|
||||
7. Cannot view logs
|
||||
8. Cannot copy repository list
|
||||
9. Cannot export results
|
||||
10. Cannot select to create a gist
|
||||
@@ -173,6 +184,7 @@ with this since it has quite a limited number of actions you can do.
|
||||
12. Cannot cancel analysis
|
||||
|
||||
#### Test case 2: When the variant analysis state is not "pending"
|
||||
|
||||
1. Query history is loaded when VSCode starts
|
||||
2. Handles when action workflow was canceled while VSCode was closed
|
||||
3. Can open query history item
|
||||
@@ -189,7 +201,7 @@ with this since it has quite a limited number of actions you can do.
|
||||
7. Can open query that produced these results
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
8. Can open variant analysis on github
|
||||
8. Can view logs
|
||||
9. Can copy repository list
|
||||
1. Text is copied to clipboard
|
||||
2. Text is a valid repository list
|
||||
@@ -206,12 +218,14 @@ with this since it has quite a limited number of actions you can do.
|
||||
4. A popup allows you to open the directory
|
||||
|
||||
#### Test case 3: When variant analysis state is "in_progress"
|
||||
|
||||
1. Starts monitoring variant analysis
|
||||
1. Ready results are downloaded
|
||||
2. Can cancel analysis
|
||||
1. Ready results are downloaded
|
||||
2. Can cancel analysis
|
||||
1. Causes the actions run to be canceled
|
||||
|
||||
#### Test case 4: When variant analysis state is in final state ("succeeded"/"failed"/"canceled")
|
||||
|
||||
1. Stops monitoring variant analysis
|
||||
1. All results are downloaded if state is succeeded
|
||||
2. Otherwise, ready results are downloaded, if any are available
|
||||
@@ -222,6 +236,7 @@ with this since it has quite a limited number of actions you can do.
|
||||
This requires running a MRVA query and seeing the results view.
|
||||
|
||||
#### Test case 1: When variant analysis state is "pending"
|
||||
|
||||
1. Can open a results view
|
||||
2. Results view opens automatically
|
||||
- When starting variant analysis run
|
||||
@@ -229,9 +244,10 @@ This requires running a MRVA query and seeing the results view.
|
||||
3. Results view is empty
|
||||
|
||||
#### Test case 2: When variant analysis state is not "pending"
|
||||
|
||||
1. Can open a results view
|
||||
2. Results view opens automatically
|
||||
1. When starting variant analysis run
|
||||
1. When starting variant analysis run
|
||||
2. When VSCode opens (if view was open when VSCode was closed)
|
||||
3. Can copy repository list
|
||||
1. Text is copied to clipboard
|
||||
@@ -242,43 +258,45 @@ This requires running a MRVA query and seeing the results view.
|
||||
6. Can open query file
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
7. Can open query text
|
||||
8. Can sort repos
|
||||
1. By name
|
||||
2. By results
|
||||
3. By stars
|
||||
7. Can open query text
|
||||
8. Can sort repos
|
||||
1. By name
|
||||
2. By results
|
||||
3. By stars
|
||||
4. By last updated
|
||||
9. Can filter repos
|
||||
10. Shows correct statistics
|
||||
1. Total number of results
|
||||
2. Total number of repositories
|
||||
10. Shows correct statistics
|
||||
1. Total number of results
|
||||
2. Total number of repositories
|
||||
3. Duration
|
||||
11. Can see live results
|
||||
11. Can see live results
|
||||
1. Results appear in extension as soon as each query is completed
|
||||
12. Can view interpreted results (i.e. for a "problem" query)
|
||||
1. Can view non-path results
|
||||
1. Can view non-path results
|
||||
2. Can view code paths for "path-problem" queries
|
||||
13. Can view raw results (i.e. for a non "problem" query)
|
||||
1. Renders a table
|
||||
14. Can see skipped repositories
|
||||
1. Can see repos with no db in a tab
|
||||
1. Shown warning that explains the tab
|
||||
14. Can see skipped repositories
|
||||
1. Can see repos with no db in a tab
|
||||
1. Shown warning that explains the tab
|
||||
2. Can see repos with no access in a tab
|
||||
1. Shown warning that explains the tab
|
||||
1. Shown warning that explains the tab
|
||||
3. Only shows tab when there are skipped repos
|
||||
15. Result downloads
|
||||
1. All results are downloaded automatically
|
||||
15. Result downloads
|
||||
1. All results are downloaded automatically
|
||||
2. Download status is indicated by a spinner (Not currently any indication of progress beyond "downloading" and "not downloading")
|
||||
3. Only 3 items are downloaded at a time
|
||||
4. Results for completed queries are still downloaded when
|
||||
1. Some but not all queries failed
|
||||
3. Only 3 items are downloaded at a time
|
||||
4. Results for completed queries are still downloaded when
|
||||
1. Some but not all queries failed
|
||||
2. The variant analysis was canceled after some queries completed
|
||||
|
||||
#### Test case 3: When variant analysis state is in "succeeded" state
|
||||
|
||||
1. Can view logs
|
||||
2. All results are downloaded
|
||||
2. All results are downloaded
|
||||
|
||||
#### Test case 4: When variant analysis is in "failed" or "canceled" state
|
||||
|
||||
1. Can view logs
|
||||
1. Results for finished queries are still downloaded.
|
||||
|
||||
@@ -307,14 +325,17 @@ This requires running a MRVA query and seeing the results view.
|
||||
1. Collapse/expand tree nodes
|
||||
|
||||
Error cases that trigger an error notification:
|
||||
1. Try to add a list with a name that already exists
|
||||
|
||||
1. Try to add a list with a name that already exists
|
||||
1. Try to add a top-level database that already exists
|
||||
1. Try to add a database in a list that already exists in the list
|
||||
|
||||
Error cases that show an error in the panel (and only the edit button should be visible):
|
||||
|
||||
1. Edit the db config file directly and save invalid JSON
|
||||
1. Edit the db config file directly and save valid JSON but invalid config (e.g. add an unknown property)
|
||||
1. Edit the db config file directly and save two lists with the same name
|
||||
1. Edit the db config file directly and save two lists with the same name
|
||||
|
||||
Cases where there the welcome view is shown:
|
||||
1. No controller repo is set in the user's settings JSON.
|
||||
|
||||
1. No controller repo is set in the user's settings JSON.
|
||||
|
||||
4
extensions/ql-vscode/package-lock.json
generated
4
extensions/ql-vscode/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.7.10",
|
||||
"version": "1.7.11",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.7.10",
|
||||
"version": "1.7.11",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.7.10",
|
||||
"version": "1.7.11",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -281,22 +281,6 @@
|
||||
"scope": "application",
|
||||
"description": "Specifies whether or not to write telemetry events to the extension log."
|
||||
},
|
||||
"codeQL.variantAnalysis.repositoryLists": {
|
||||
"type": [
|
||||
"object",
|
||||
null
|
||||
],
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": null,
|
||||
"markdownDescription": "[For internal use only] Lists of GitHub repositories that you want to run variant analysis against. This should be a JSON object where each key is a user-specified name for this repository list, and the value is an array of GitHub repositories (of the form `<owner>/<repo>`)."
|
||||
},
|
||||
"codeQL.variantAnalysis.controllerRepo": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -322,10 +306,18 @@
|
||||
"command": "codeQL.runQuery",
|
||||
"title": "CodeQL: Run Query on Selected Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"title": "CodeQL: Run Query on Selected Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
@@ -972,10 +964,18 @@
|
||||
"command": "codeQL.runQuery",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
@@ -1227,11 +1227,11 @@
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -83,13 +83,8 @@ export const GLOBAL_ENABLE_TELEMETRY = new Setting(
|
||||
GLOBAL_TELEMETRY_SETTING,
|
||||
);
|
||||
|
||||
const ENABLE_NEW_TELEMETRY = new Setting(
|
||||
"enableNewTelemetry",
|
||||
TELEMETRY_SETTING,
|
||||
);
|
||||
|
||||
export function newTelemetryEnabled(): boolean {
|
||||
return ENABLE_NEW_TELEMETRY.getValue<boolean>();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Distribution configuration
|
||||
|
||||
@@ -315,6 +315,15 @@ class ExtensionSpecificDistributionManager {
|
||||
const extensionSpecificRelease = this.getInstalledRelease();
|
||||
const latestRelease = await this.getLatestRelease();
|
||||
|
||||
// v2.12.3 was released with a bug that causes the extension to fail
|
||||
// so we force the extension to ignore it.
|
||||
if (
|
||||
extensionSpecificRelease &&
|
||||
extensionSpecificRelease.name === "v2.12.3"
|
||||
) {
|
||||
return createUpdateAvailableResult(latestRelease);
|
||||
}
|
||||
|
||||
if (
|
||||
extensionSpecificRelease !== undefined &&
|
||||
codeQlPath !== undefined &&
|
||||
@@ -430,6 +439,12 @@ class ExtensionSpecificDistributionManager {
|
||||
this.versionRange,
|
||||
this.config.includePrerelease,
|
||||
(release) => {
|
||||
// v2.12.3 was released with a bug that causes the extension to fail
|
||||
// so we force the extension to ignore it.
|
||||
if (release.name === "v2.12.3") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const matchingAssets = release.assets.filter(
|
||||
(asset) => asset.name === requiredAssetName,
|
||||
);
|
||||
|
||||
@@ -771,6 +771,74 @@ async function activateWithInstalledDistribution(
|
||||
}
|
||||
}
|
||||
|
||||
async function compileAndRunQueryOnMultipleDatabases(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined,
|
||||
): Promise<void> {
|
||||
let filteredDBs = dbm.databaseItems;
|
||||
if (filteredDBs.length === 0) {
|
||||
void showAndLogErrorMessage(
|
||||
"No databases found. Please add a suitable database to your workspace.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
// If possible, only show databases with the right language (otherwise show all databases).
|
||||
const queryLanguage = await findLanguage(cliServer, uri);
|
||||
if (queryLanguage) {
|
||||
filteredDBs = dbm.databaseItems.filter(
|
||||
(db) => db.language === queryLanguage,
|
||||
);
|
||||
if (filteredDBs.length === 0) {
|
||||
void showAndLogErrorMessage(
|
||||
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>((dbItem) => ({
|
||||
databaseItem: dbItem,
|
||||
label: dbItem.name,
|
||||
description: dbItem.language,
|
||||
}));
|
||||
/**
|
||||
* Databases that were selected in the quick pick menu.
|
||||
*/
|
||||
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
|
||||
quickPickItems,
|
||||
{ canPickMany: true, ignoreFocusOut: true },
|
||||
);
|
||||
if (quickpick !== undefined) {
|
||||
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
|
||||
const skippedDatabases = [];
|
||||
const errors = [];
|
||||
for (const item of quickpick) {
|
||||
try {
|
||||
await compileAndRunQuery(
|
||||
false,
|
||||
uri,
|
||||
progress,
|
||||
token,
|
||||
item.databaseItem,
|
||||
);
|
||||
} catch (e) {
|
||||
skippedDatabases.push(item.label);
|
||||
errors.push(getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
if (skippedDatabases.length > 0) {
|
||||
void extLogger.log(`Errors:\n${errors.join("\n")}`);
|
||||
void showAndLogWarningMessage(
|
||||
`The following databases were skipped:\n${skippedDatabases.join(
|
||||
"\n",
|
||||
)}.\nFor details about the errors, see the logs.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
void showAndLogErrorMessage("No databases selected.");
|
||||
}
|
||||
}
|
||||
|
||||
const qhelpTmpDir = dirSync({
|
||||
prefix: "qhelp_",
|
||||
keep: false,
|
||||
@@ -871,6 +939,25 @@ async function activateWithInstalledDistribution(
|
||||
queryServerLogger,
|
||||
),
|
||||
);
|
||||
|
||||
// Since we are tracking extension usage through commands, this command mirrors the runQuery command
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.runQueryContextEditor",
|
||||
async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined,
|
||||
) => await compileAndRunQuery(false, uri, progress, token, undefined),
|
||||
{
|
||||
title: "Running query",
|
||||
cancellable: true,
|
||||
},
|
||||
|
||||
// Open the query server logger on error since that's usually where the interesting errors appear.
|
||||
queryServerLogger,
|
||||
),
|
||||
);
|
||||
interface DatabaseQuickPickItem extends QuickPickItem {
|
||||
databaseItem: DatabaseItem;
|
||||
}
|
||||
@@ -881,71 +968,22 @@ async function activateWithInstalledDistribution(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined,
|
||||
) => {
|
||||
let filteredDBs = dbm.databaseItems;
|
||||
if (filteredDBs.length === 0) {
|
||||
void showAndLogErrorMessage(
|
||||
"No databases found. Please add a suitable database to your workspace.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
// If possible, only show databases with the right language (otherwise show all databases).
|
||||
const queryLanguage = await findLanguage(cliServer, uri);
|
||||
if (queryLanguage) {
|
||||
filteredDBs = dbm.databaseItems.filter(
|
||||
(db) => db.language === queryLanguage,
|
||||
);
|
||||
if (filteredDBs.length === 0) {
|
||||
void showAndLogErrorMessage(
|
||||
`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(
|
||||
(dbItem) => ({
|
||||
databaseItem: dbItem,
|
||||
label: dbItem.name,
|
||||
description: dbItem.language,
|
||||
}),
|
||||
);
|
||||
/**
|
||||
* Databases that were selected in the quick pick menu.
|
||||
*/
|
||||
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
|
||||
quickPickItems,
|
||||
{ canPickMany: true, ignoreFocusOut: true },
|
||||
);
|
||||
if (quickpick !== undefined) {
|
||||
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
|
||||
const skippedDatabases = [];
|
||||
const errors = [];
|
||||
for (const item of quickpick) {
|
||||
try {
|
||||
await compileAndRunQuery(
|
||||
false,
|
||||
uri,
|
||||
progress,
|
||||
token,
|
||||
item.databaseItem,
|
||||
);
|
||||
} catch (e) {
|
||||
skippedDatabases.push(item.label);
|
||||
errors.push(getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
if (skippedDatabases.length > 0) {
|
||||
void extLogger.log(`Errors:\n${errors.join("\n")}`);
|
||||
void showAndLogWarningMessage(
|
||||
`The following databases were skipped:\n${skippedDatabases.join(
|
||||
"\n",
|
||||
)}.\nFor details about the errors, see the logs.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
void showAndLogErrorMessage("No databases selected.");
|
||||
}
|
||||
) => await compileAndRunQueryOnMultipleDatabases(progress, token, uri),
|
||||
{
|
||||
title: "Running query on selected databases",
|
||||
cancellable: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
// Since we are tracking extension usage through commands, this command mirrors the runQueryOnMultipleDatabases command
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined,
|
||||
) => await compileAndRunQueryOnMultipleDatabases(progress, token, uri),
|
||||
{
|
||||
title: "Running query on selected databases",
|
||||
cancellable: true,
|
||||
|
||||
@@ -375,7 +375,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
// This specifically refers to the database folder in
|
||||
// https://github.com/github/codespaces-codeql
|
||||
const uri = Uri.parse(
|
||||
`${workspace.workspaceFolders[0].uri}/codeql-tutorial-database`,
|
||||
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
|
||||
);
|
||||
|
||||
let databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
@@ -390,6 +390,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
);
|
||||
}
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
await this.handleTourDependencies();
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
@@ -401,6 +402,22 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
};
|
||||
|
||||
private handleTourDependencies = async (): Promise<void> => {
|
||||
if (!workspace.workspaceFolders?.length) {
|
||||
throw new Error("No workspace folder is open.");
|
||||
} else {
|
||||
const tutorialQueriesPath = join(
|
||||
workspace.workspaceFolders[0].uri.fsPath,
|
||||
"tutorial-queries",
|
||||
);
|
||||
const cli = this.queryServer?.cliServer;
|
||||
if (!cli) {
|
||||
throw new Error("No CLI server found");
|
||||
}
|
||||
await cli.packInstall(tutorialQueriesPath);
|
||||
}
|
||||
};
|
||||
|
||||
handleRemoveOrphanedDatabases = async (): Promise<void> => {
|
||||
void extLogger.log("Removing orphaned databases from workspace storage.");
|
||||
let dbDirs = undefined;
|
||||
|
||||
@@ -105,7 +105,7 @@ export function transformBqrsResultSet(
|
||||
};
|
||||
}
|
||||
|
||||
type BqrsKind =
|
||||
export type BqrsKind =
|
||||
| "String"
|
||||
| "Float"
|
||||
| "Integer"
|
||||
|
||||
@@ -365,7 +365,10 @@ export async function getControllerRepo(
|
||||
credentials: Credentials,
|
||||
): Promise<Repository> {
|
||||
// Get the controller repo from the config, if it exists.
|
||||
// If it doesn't exist, prompt the user to enter it, and save that value to the config.
|
||||
// If it doesn't exist, prompt the user to enter it, check
|
||||
// whether the repo exists, and save the nwo to the config.
|
||||
|
||||
let shouldSetControllerRepo = false;
|
||||
let controllerRepoNwo: string | undefined;
|
||||
controllerRepoNwo = getRemoteControllerRepo();
|
||||
if (!controllerRepoNwo || !REPO_REGEX.test(controllerRepoNwo)) {
|
||||
@@ -390,15 +393,31 @@ export async function getControllerRepo(
|
||||
"Invalid repository format. Must be a valid GitHub repository in the format <owner>/<repo>.",
|
||||
);
|
||||
}
|
||||
|
||||
shouldSetControllerRepo = true;
|
||||
}
|
||||
|
||||
void extLogger.log(`Using controller repository: ${controllerRepoNwo}`);
|
||||
const controllerRepo = await getControllerRepoFromApi(
|
||||
credentials,
|
||||
controllerRepoNwo,
|
||||
);
|
||||
|
||||
if (shouldSetControllerRepo) {
|
||||
void extLogger.log(
|
||||
`Setting the controller repository as: ${controllerRepoNwo}`,
|
||||
);
|
||||
await setRemoteControllerRepo(controllerRepoNwo);
|
||||
}
|
||||
|
||||
void extLogger.log(`Using controller repository: ${controllerRepoNwo}`);
|
||||
const [owner, repo] = controllerRepoNwo.split("/");
|
||||
return controllerRepo;
|
||||
}
|
||||
|
||||
async function getControllerRepoFromApi(
|
||||
credentials: Credentials,
|
||||
nwo: string,
|
||||
): Promise<Repository> {
|
||||
const [owner, repo] = nwo.split("/");
|
||||
try {
|
||||
const controllerRepo = await getRepositoryFromNwo(credentials, owner, repo);
|
||||
void extLogger.log(`Controller repository ID: ${controllerRepo.id}`);
|
||||
@@ -419,6 +438,7 @@ export async function getControllerRepo(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeWorkspaceRefs(qlpack: QlPack) {
|
||||
for (const [key, value] of Object.entries(qlpack.dependencies || {})) {
|
||||
if (value === "${workspace}") {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.12.2",
|
||||
"v2.12.3",
|
||||
"v2.11.6",
|
||||
"v2.7.6",
|
||||
"v2.8.5",
|
||||
|
||||
@@ -59,6 +59,7 @@ import { DbManager } from "../../../../src/databases/db-manager";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
|
||||
import { DbConfigStore } from "../../../../src/databases/config/db-config-store";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
// up to 3 minutes per test
|
||||
jest.setTimeout(3 * 60 * 1000);
|
||||
@@ -992,10 +993,10 @@ describe("Variant Analysis Manager", () => {
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(undefined as unknown as TextEditor);
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(undefined as unknown as TextDocument);
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -1005,8 +1006,8 @@ describe("Variant Analysis Manager", () => {
|
||||
it("opens the query text", async () => {
|
||||
await variantAnalysisManager.openQueryText(variantAnalysis.id);
|
||||
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(openTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
expect(showTextDocumentSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const uri: Uri = openTextDocumentSpy.mock.calls[0][0] as Uri;
|
||||
expect(uri.scheme).toEqual("codeql-variant-analysis");
|
||||
@@ -1040,10 +1041,10 @@ describe("Variant Analysis Manager", () => {
|
||||
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(window, "showTextDocument")
|
||||
.mockResolvedValue(undefined as unknown as TextEditor);
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
openTextDocumentSpy = jest
|
||||
.spyOn(workspace, "openTextDocument")
|
||||
.mockResolvedValue(undefined as unknown as TextDocument);
|
||||
.mockResolvedValue(mockedObject<TextDocument>({}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -4,13 +4,13 @@ import { join } from "path";
|
||||
import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode";
|
||||
|
||||
import {
|
||||
DatabaseEventKind,
|
||||
DatabaseManager,
|
||||
DatabaseItemImpl,
|
||||
DatabaseContents,
|
||||
FullDatabaseOptions,
|
||||
findSourceArchive,
|
||||
DatabaseEventKind,
|
||||
DatabaseItemImpl,
|
||||
DatabaseManager,
|
||||
DatabaseResolver,
|
||||
findSourceArchive,
|
||||
FullDatabaseOptions,
|
||||
} from "../../../src/local-databases";
|
||||
import { Logger } from "../../../src/common";
|
||||
import { ProgressCallback } from "../../../src/commandRunner";
|
||||
@@ -24,6 +24,7 @@ import { QueryRunner } from "../../../src/queryRunner";
|
||||
import * as helpers from "../../../src/helpers";
|
||||
import { Setting } from "../../../src/config";
|
||||
import { QlPackGenerator } from "../../../src/qlpack-generator";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
describe("local databases", () => {
|
||||
const MOCK_DB_OPTIONS: FullDatabaseOptions = {
|
||||
@@ -47,6 +48,7 @@ describe("local databases", () => {
|
||||
>;
|
||||
|
||||
let dir: tmp.DirResult;
|
||||
let extensionContextStoragePath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = tmp.dirSync();
|
||||
@@ -64,33 +66,41 @@ describe("local databases", () => {
|
||||
.spyOn(helpers, "showBinaryChoiceDialog")
|
||||
.mockResolvedValue(true);
|
||||
|
||||
extensionContext = {
|
||||
workspaceState: {
|
||||
update: updateSpy,
|
||||
get: () => [],
|
||||
extensionContextStoragePath = dir.name;
|
||||
|
||||
extensionContext = mockedObject<ExtensionContext>(
|
||||
{
|
||||
workspaceState: {
|
||||
update: updateSpy,
|
||||
get: () => [],
|
||||
},
|
||||
},
|
||||
// pretend like databases added in the temp dir are controlled by the extension
|
||||
// so that they are deleted upon removal
|
||||
storagePath: dir.name,
|
||||
storageUri: Uri.parse(dir.name),
|
||||
} as unknown as ExtensionContext;
|
||||
{
|
||||
dynamicProperties: {
|
||||
// pretend like databases added in the temp dir are controlled by the extension
|
||||
// so that they are deleted upon removal
|
||||
storagePath: () => extensionContextStoragePath,
|
||||
storageUri: () => Uri.parse(extensionContextStoragePath),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
databaseManager = new DatabaseManager(
|
||||
extensionContext,
|
||||
{
|
||||
mockedObject<QueryRunner>({
|
||||
registerDatabase: registerSpy,
|
||||
deregisterDatabase: deregisterSpy,
|
||||
onStart: () => {
|
||||
/**/
|
||||
},
|
||||
} as unknown as QueryRunner,
|
||||
{
|
||||
}),
|
||||
mockedObject<CodeQLCliServer>({
|
||||
resolveDatabase: resolveDatabaseSpy,
|
||||
packAdd: packAddSpy,
|
||||
} as unknown as CodeQLCliServer,
|
||||
{
|
||||
}),
|
||||
mockedObject<Logger>({
|
||||
log: logSpy,
|
||||
} as unknown as Logger,
|
||||
}),
|
||||
);
|
||||
|
||||
// Unfortunately, during a test it is not possible to convert from
|
||||
@@ -266,6 +276,7 @@ describe("local databases", () => {
|
||||
|
||||
// pretend that the database location is not controlled by the extension
|
||||
(databaseManager as any).ctx.storagePath = "hucairz";
|
||||
extensionContextStoragePath = "hucairz";
|
||||
|
||||
await databaseManager.removeDatabaseItem(
|
||||
{} as ProgressCallback,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CodeQLCliServer } from "../../../src/cli";
|
||||
import { Uri, workspace } from "vscode";
|
||||
import { getErrorMessage } from "../../../src/pure/helpers-pure";
|
||||
import * as tmp from "tmp";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
describe("QlPackGenerator", () => {
|
||||
let packFolderName: string;
|
||||
@@ -14,7 +15,7 @@ describe("QlPackGenerator", () => {
|
||||
let exampleQlFilePath: string;
|
||||
let language: string;
|
||||
let generator: QlPackGenerator;
|
||||
let packAddSpy: jest.SpyInstance;
|
||||
let packAddSpy: jest.Mock<any, []>;
|
||||
let dir: tmp.DirResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -28,9 +29,9 @@ describe("QlPackGenerator", () => {
|
||||
exampleQlFilePath = join(packFolderPath, "example.ql");
|
||||
|
||||
packAddSpy = jest.fn();
|
||||
const mockCli = {
|
||||
const mockCli = mockedObject<CodeQLCliServer>({
|
||||
packAdd: packAddSpy,
|
||||
} as unknown as CodeQLCliServer;
|
||||
});
|
||||
|
||||
generator = new QlPackGenerator(
|
||||
packFolderName,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DirectoryResult } from "tmp-promise";
|
||||
import * as tmp from "tmp-promise";
|
||||
|
||||
import "../../matchers/toEqualPath";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
describe("qltest-discovery", () => {
|
||||
describe("discoverTests", () => {
|
||||
@@ -34,10 +35,10 @@ describe("qltest-discovery", () => {
|
||||
iFile = join(hDir, "i.ql");
|
||||
|
||||
qlTestDiscover = new QLTestDiscovery(
|
||||
{
|
||||
mockedObject<WorkspaceFolder>({
|
||||
uri: baseUri,
|
||||
name: "My tests",
|
||||
} as unknown as WorkspaceFolder,
|
||||
}),
|
||||
{
|
||||
resolveTests() {
|
||||
return [dFile, eFile, iFile];
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { DatabaseItem } from "../../../../src/local-databases";
|
||||
import { Uri } from "vscode";
|
||||
import { QueryWithResults } from "../../../../src/run-queries-shared";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,7 +33,7 @@ describe("AstBuilder", () => {
|
||||
let overrides: Record<string, Record<string, unknown> | undefined>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCli = {
|
||||
mockCli = mockedObject<CodeQLCliServer>({
|
||||
bqrsDecode: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
@@ -40,7 +41,7 @@ describe("AstBuilder", () => {
|
||||
return mockDecode(resultSet);
|
||||
},
|
||||
),
|
||||
} as unknown as CodeQLCliServer;
|
||||
});
|
||||
overrides = {
|
||||
nodes: undefined,
|
||||
edges: undefined,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../../../../src/contextual/queryResolver";
|
||||
import { CodeQLCliServer } from "../../../../src/cli";
|
||||
import { DatabaseItem } from "../../../../src/local-databases";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
describe("queryResolver", () => {
|
||||
let getQlPackForDbschemeSpy: jest.SpiedFunction<
|
||||
@@ -20,9 +21,11 @@ describe("queryResolver", () => {
|
||||
typeof helpers.getPrimaryDbscheme
|
||||
>;
|
||||
|
||||
const mockCli = {
|
||||
resolveQueriesInSuite: jest.fn(),
|
||||
};
|
||||
const resolveQueriesInSuite = jest.fn();
|
||||
|
||||
const mockCli = mockedObject<CodeQLCliServer>({
|
||||
resolveQueriesInSuite,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
getQlPackForDbschemeSpy = jest
|
||||
@@ -41,20 +44,20 @@ describe("queryResolver", () => {
|
||||
|
||||
describe("resolveQueries", () => {
|
||||
it("should resolve a query", async () => {
|
||||
mockCli.resolveQueriesInSuite.mockReturnValue(["a", "b"]);
|
||||
resolveQueriesInSuite.mockReturnValue(["a", "b"]);
|
||||
const result = await resolveQueries(
|
||||
mockCli as unknown as CodeQLCliServer,
|
||||
mockCli,
|
||||
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
|
||||
KeyType.DefinitionQuery,
|
||||
);
|
||||
expect(result).toEqual(["a", "b"]);
|
||||
|
||||
expect(mockCli.resolveQueriesInSuite).toHaveBeenCalledWith(
|
||||
expect(resolveQueriesInSuite).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/\.qls$/),
|
||||
[],
|
||||
);
|
||||
|
||||
const fileName = mockCli.resolveQueriesInSuite.mock.calls[0][0];
|
||||
const fileName = resolveQueriesInSuite.mock.calls[0][0];
|
||||
|
||||
expect(load(await fs.readFile(fileName, "utf-8"))).toEqual([
|
||||
{
|
||||
@@ -69,11 +72,11 @@ describe("queryResolver", () => {
|
||||
});
|
||||
|
||||
it("should throw an error when there are no queries found", async () => {
|
||||
mockCli.resolveQueriesInSuite.mockReturnValue([]);
|
||||
resolveQueriesInSuite.mockReturnValue([]);
|
||||
|
||||
try {
|
||||
await resolveQueries(
|
||||
mockCli as unknown as CodeQLCliServer,
|
||||
mockCli,
|
||||
{ dbschemePack: "my-qlpack", dbschemePackIsLibraryPack: false },
|
||||
KeyType.DefinitionQuery,
|
||||
);
|
||||
@@ -100,10 +103,7 @@ describe("queryResolver", () => {
|
||||
},
|
||||
},
|
||||
} as unknown as DatabaseItem;
|
||||
const result = await qlpackOfDatabase(
|
||||
mockCli as unknown as CodeQLCliServer,
|
||||
db,
|
||||
);
|
||||
const result = await qlpackOfDatabase(mockCli, db);
|
||||
expect(result).toEqual({
|
||||
dbschemePack: "my-qlpack",
|
||||
dbschemePackIsLibraryPack: false,
|
||||
|
||||
@@ -22,7 +22,9 @@ describe("HistoryItemLabelProvider", () => {
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
format: "xxx %q xxx",
|
||||
} as unknown as QueryHistoryConfig;
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
};
|
||||
labelProvider = new HistoryItemLabelProvider(config);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@ import { join } from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import {
|
||||
QueryHistoryConfig,
|
||||
QueryHistoryConfigListener,
|
||||
} from "../../../../src/config";
|
||||
import { QueryHistoryConfigListener } from "../../../../src/config";
|
||||
import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/local-databases";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
@@ -121,8 +118,10 @@ describe("HistoryTreeDataProvider", () => {
|
||||
]);
|
||||
|
||||
labelProvider = new HistoryItemLabelProvider({
|
||||
/**/
|
||||
} as QueryHistoryConfig);
|
||||
format: "",
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
});
|
||||
historyTreeDataProvider = new HistoryTreeDataProvider(labelProvider);
|
||||
});
|
||||
|
||||
@@ -432,7 +431,11 @@ describe("HistoryTreeDataProvider", () => {
|
||||
extensionPath: vscode.Uri.file("/x/y/z").fsPath,
|
||||
} as vscode.ExtensionContext,
|
||||
configListener,
|
||||
new HistoryItemLabelProvider({} as QueryHistoryConfig),
|
||||
new HistoryItemLabelProvider({
|
||||
format: "",
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
}),
|
||||
doCompareCallback,
|
||||
);
|
||||
(qhm.treeDataProvider as any).history = [...allHistory];
|
||||
|
||||
@@ -3,10 +3,7 @@ import * as vscode from "vscode";
|
||||
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
import {
|
||||
QueryHistoryConfig,
|
||||
QueryHistoryConfigListener,
|
||||
} from "../../../../src/config";
|
||||
import { QueryHistoryConfigListener } from "../../../../src/config";
|
||||
import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/local-databases";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
@@ -28,6 +25,7 @@ import { VariantAnalysisStatus } from "../../../../src/variant-analysis/shared/v
|
||||
import { QuickPickItem, TextEditor } from "vscode";
|
||||
import { WebviewReveal } from "../../../../src/interface-utils";
|
||||
import * as helpers from "../../../../src/helpers";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
describe("QueryHistoryManager", () => {
|
||||
const mockExtensionLocation = join(tmpDir.name, "mock-extension-location");
|
||||
@@ -58,7 +56,7 @@ describe("QueryHistoryManager", () => {
|
||||
beforeEach(() => {
|
||||
showTextDocumentSpy = jest
|
||||
.spyOn(vscode.window, "showTextDocument")
|
||||
.mockResolvedValue(undefined as unknown as TextEditor);
|
||||
.mockResolvedValue(mockedObject<TextEditor>({}));
|
||||
showInformationMessageSpy = jest
|
||||
.spyOn(vscode.window, "showInformationMessage")
|
||||
.mockResolvedValue(undefined);
|
||||
@@ -1158,7 +1156,11 @@ describe("QueryHistoryManager", () => {
|
||||
extensionPath: vscode.Uri.file("/x/y/z").fsPath,
|
||||
} as vscode.ExtensionContext,
|
||||
configListener,
|
||||
new HistoryItemLabelProvider({} as QueryHistoryConfig),
|
||||
new HistoryItemLabelProvider({
|
||||
format: "",
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
}),
|
||||
doCompareCallback,
|
||||
);
|
||||
(qhm.treeDataProvider as any).history = [...allHistory];
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
THREE_HOURS_IN_MS,
|
||||
TWO_HOURS_IN_MS,
|
||||
} from "../../../../src/pure/time";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
describe("query history scrubber", () => {
|
||||
const now = Date.now();
|
||||
@@ -181,11 +182,11 @@ describe("query history scrubber", () => {
|
||||
TWO_HOURS_IN_MS,
|
||||
LESS_THAN_ONE_DAY,
|
||||
dir,
|
||||
{
|
||||
mockedObject<QueryHistoryManager>({
|
||||
removeDeletedQueries: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
} as QueryHistoryManager,
|
||||
}),
|
||||
mockCtx,
|
||||
{
|
||||
increment: () => runCount++,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { join } from "path";
|
||||
|
||||
import { commands, ExtensionContext, Uri } from "vscode";
|
||||
import { QueryHistoryConfig } from "../../../../src/config";
|
||||
import { DatabaseManager } from "../../../../src/local-databases";
|
||||
import { tmpDir, walkDirectory } from "../../../../src/helpers";
|
||||
import { DisposableBucket } from "../../disposable-bucket";
|
||||
@@ -20,6 +19,7 @@ import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
// set a higher timeout since recursive delete may take a while, expecially on Windows.
|
||||
jest.setTimeout(120000);
|
||||
@@ -75,14 +75,21 @@ describe("Variant Analyses and QueryHistoryManager", () => {
|
||||
variantAnalysisManagerStub,
|
||||
{} as EvalLogViewer,
|
||||
STORAGE_DIR,
|
||||
{
|
||||
mockedObject<ExtensionContext>({
|
||||
globalStorageUri: Uri.file(STORAGE_DIR),
|
||||
storageUri: undefined,
|
||||
extensionPath: EXTENSION_PATH,
|
||||
} as ExtensionContext,
|
||||
}),
|
||||
{
|
||||
format: "",
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: () => new DisposableBucket(),
|
||||
} as unknown as QueryHistoryConfig,
|
||||
new HistoryItemLabelProvider({} as QueryHistoryConfig),
|
||||
},
|
||||
new HistoryItemLabelProvider({
|
||||
format: "",
|
||||
ttlInMillis: 0,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
}),
|
||||
asyncNoop,
|
||||
);
|
||||
disposables.push(qhm);
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
QueryResultType,
|
||||
} from "../../../src/pure/legacy-messages";
|
||||
import { sleep } from "../../../src/pure/time";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
describe("query-results", () => {
|
||||
let queryPath: string;
|
||||
@@ -139,9 +140,9 @@ describe("query-results", () => {
|
||||
const completedQuery = fqi.completedQuery!;
|
||||
|
||||
const spy = jest.fn();
|
||||
const mockServer = {
|
||||
const mockServer = mockedObject<CodeQLCliServer>({
|
||||
sortBqrs: spy,
|
||||
} as unknown as CodeQLCliServer;
|
||||
});
|
||||
const sortState = {
|
||||
columnIndex: 1,
|
||||
sortDirection: SortDirection.desc,
|
||||
@@ -196,9 +197,9 @@ describe("query-results", () => {
|
||||
|
||||
await ensureDir(basename(interpretedResultsPath));
|
||||
|
||||
mockServer = {
|
||||
mockServer = mockedObject<CodeQLCliServer>({
|
||||
interpretBqrsSarif: spy,
|
||||
} as unknown as CodeQLCliServer;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -16,6 +16,8 @@ import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import { QueryInProgress } from "../../../src/legacy-query-server/run-queries";
|
||||
import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner";
|
||||
import { DatabaseItem } from "../../../src/local-databases";
|
||||
import { DeepPartial, mockedObject } from "../utils/mocking.helpers";
|
||||
import { BqrsKind } from "../../../src/pure/bqrs-cli-types";
|
||||
|
||||
describe("run-queries", () => {
|
||||
let isCanarySpy: jest.SpiedFunction<typeof config.isCanary>;
|
||||
@@ -77,7 +79,7 @@ describe("run-queries", () => {
|
||||
],
|
||||
bqrsDecode: [
|
||||
{
|
||||
columns: [{ kind: "NotString" }, { kind: "String" }],
|
||||
columns: [{ kind: "NotString" as BqrsKind }, { kind: "String" }],
|
||||
tuples: [
|
||||
["a", "b"],
|
||||
["c", "d"],
|
||||
@@ -89,8 +91,8 @@ describe("run-queries", () => {
|
||||
// this won't happen with the real CLI, but it's a good test
|
||||
columns: [
|
||||
{ kind: "String" },
|
||||
{ kind: "NotString" },
|
||||
{ kind: "StillNotString" },
|
||||
{ kind: "NotString" as BqrsKind },
|
||||
{ kind: "StillNotString" as BqrsKind },
|
||||
],
|
||||
tuples: [["a", "b", "c"]],
|
||||
},
|
||||
@@ -125,7 +127,7 @@ describe("run-queries", () => {
|
||||
],
|
||||
bqrsDecode: [
|
||||
{
|
||||
columns: [{ kind: "NotString" }, { kind: "String" }],
|
||||
columns: [{ kind: "NotString" as BqrsKind }, { kind: "String" }],
|
||||
// We only escape string columns. In practice, we will only see quotes in strings, but
|
||||
// it is a good test anyway.
|
||||
tuples: [
|
||||
@@ -312,7 +314,7 @@ describe("run-queries", () => {
|
||||
function createMockQueryServerClient(
|
||||
cliServer?: CodeQLCliServer,
|
||||
): QueryServerClient {
|
||||
return {
|
||||
return mockedObject<QueryServerClient>({
|
||||
config: {
|
||||
timeoutSecs: 5,
|
||||
},
|
||||
@@ -326,20 +328,32 @@ describe("run-queries", () => {
|
||||
log: jest.fn(),
|
||||
},
|
||||
cliServer,
|
||||
} as unknown as QueryServerClient;
|
||||
});
|
||||
}
|
||||
|
||||
// A type that represents the mocked methods of a CodeQLCliServer. Exclude any non-methods.
|
||||
// This allows passing in an array of return values for a single method.
|
||||
type MockedCLIMethods = {
|
||||
[K in keyof CodeQLCliServer]: CodeQLCliServer[K] extends (
|
||||
...args: any
|
||||
) => any
|
||||
? Array<DeepPartial<Awaited<ReturnType<CodeQLCliServer[K]>>>>
|
||||
: never;
|
||||
};
|
||||
|
||||
function createMockCliServer(
|
||||
mockOperations: Record<string, any[]>,
|
||||
mockOperations: Partial<MockedCLIMethods>,
|
||||
): CodeQLCliServer {
|
||||
const mockServer: Record<string, any> = {};
|
||||
const mockedMethods: Record<string, jest.Mock> = {};
|
||||
|
||||
for (const [operation, returns] of Object.entries(mockOperations)) {
|
||||
mockServer[operation] = jest.fn();
|
||||
returns.forEach((returnValue) => {
|
||||
mockServer[operation].mockResolvedValueOnce(returnValue);
|
||||
const fn = jest.fn();
|
||||
returns.forEach((returnValue: any) => {
|
||||
fn.mockResolvedValueOnce(returnValue);
|
||||
});
|
||||
mockedMethods[operation] = fn;
|
||||
}
|
||||
|
||||
return mockServer as unknown as CodeQLCliServer;
|
||||
return mockedObject<CodeQLCliServer>(mockedMethods);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -394,6 +394,10 @@ describe("telemetry reporting", () => {
|
||||
});
|
||||
|
||||
describe("when new telementry is not enabled", () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(Config, "newTelemetryEnabled").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("should not send a ui-interaction telementry event", async () => {
|
||||
await telemetryListener.initialize();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DatabaseManager,
|
||||
FullDatabaseOptions,
|
||||
} from "../../../src/local-databases";
|
||||
import { mockedObject } from "../utils/mocking.helpers";
|
||||
|
||||
jest.mock("fs-extra", () => {
|
||||
const original = jest.requireActual("fs-extra");
|
||||
@@ -58,31 +59,33 @@ describe("test-adapter", () => {
|
||||
setCurrentDatabaseItemSpy.mockResolvedValue(undefined);
|
||||
resolveQlpacksSpy.mockResolvedValue({});
|
||||
resolveTestsSpy.mockResolvedValue([]);
|
||||
fakeDatabaseManager = {
|
||||
openDatabase: openDatabaseSpy,
|
||||
removeDatabaseItem: removeDatabaseItemSpy,
|
||||
renameDatabaseItem: renameDatabaseItemSpy,
|
||||
setCurrentDatabaseItem: setCurrentDatabaseItemSpy,
|
||||
} as unknown as DatabaseManager;
|
||||
Object.defineProperty(fakeDatabaseManager, "currentDatabaseItem", {
|
||||
get: () => currentDatabaseItem,
|
||||
});
|
||||
Object.defineProperty(fakeDatabaseManager, "databaseItems", {
|
||||
get: () => databaseItems,
|
||||
});
|
||||
fakeDatabaseManager = mockedObject<DatabaseManager>(
|
||||
{
|
||||
openDatabase: openDatabaseSpy,
|
||||
removeDatabaseItem: removeDatabaseItemSpy,
|
||||
renameDatabaseItem: renameDatabaseItemSpy,
|
||||
setCurrentDatabaseItem: setCurrentDatabaseItemSpy,
|
||||
},
|
||||
{
|
||||
dynamicProperties: {
|
||||
currentDatabaseItem: () => currentDatabaseItem,
|
||||
databaseItems: () => databaseItems,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
jest.spyOn(preTestDatabaseItem, "isAffectedByTest").mockResolvedValue(true);
|
||||
|
||||
adapter = new QLTestAdapter(
|
||||
{
|
||||
mockedObject<WorkspaceFolder>({
|
||||
name: "ABC",
|
||||
uri: Uri.parse("file:/ab/c"),
|
||||
} as WorkspaceFolder,
|
||||
{
|
||||
}),
|
||||
mockedObject<CodeQLCliServer>({
|
||||
runTests: runTestsSpy,
|
||||
resolveQlpacks: resolveQlpacksSpy,
|
||||
resolveTests: resolveTestsSpy,
|
||||
} as unknown as CodeQLCliServer,
|
||||
}),
|
||||
fakeDatabaseManager,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
export type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
export type DynamicProperties<T extends object> = {
|
||||
[P in keyof T]?: () => T[P];
|
||||
};
|
||||
|
||||
type MockedObjectOptions<T extends object> = {
|
||||
/**
|
||||
* Properties for which the given method should be called when accessed.
|
||||
* The method should return the value to be returned when the property is accessed.
|
||||
* Methods which are explicitly defined in `methods` will take precedence over
|
||||
* dynamic properties.
|
||||
*/
|
||||
dynamicProperties?: DynamicProperties<T>;
|
||||
};
|
||||
|
||||
export function mockedObject<T extends object>(
|
||||
props: DeepPartial<T>,
|
||||
{ dynamicProperties }: MockedObjectOptions<T> = {},
|
||||
): T {
|
||||
return new Proxy<T>({} as unknown as T, {
|
||||
get: (_target, prop) => {
|
||||
if (prop in props) {
|
||||
return (props as any)[prop];
|
||||
}
|
||||
if (dynamicProperties && prop in dynamicProperties) {
|
||||
return (dynamicProperties as any)[prop]();
|
||||
}
|
||||
|
||||
// The `then` method is accessed by `Promise.resolve` to check if the object is a thenable.
|
||||
// We don't want to throw an error when this happens.
|
||||
if (prop === "then") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
throw new Error(`Method ${String(prop)} not mocked`);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user