Compare commits

...

56 Commits

Author SHA1 Message Date
Andrew Eisenberg
6bbb14edd4 Merge pull request #2129 from github/v1.7.11
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
v1.7.11
2023-03-01 10:57:50 -08:00
Andrew Eisenberg
fb5675a7c5 Update extensions/ql-vscode/CHANGELOG.md
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2023-03-01 10:42:26 -08:00
Andrew Eisenberg
82a2db9fec v1.7.11
Release prep and fix markdown linting warnings in test plan.
2023-03-01 18:33:51 +00:00
Andrew Eisenberg
25d85c3e61 Merge pull request #2128 from github/revert-2120-charisk/learn-more-controller-repo
Revert "Add controller repo 'learn more' link"
2023-03-01 09:43:20 -08:00
Andrew Eisenberg
81e6d8583a Merge pull request #2127 from github/revert-2121-charisk/mrva-public-beta
Revert "Move MRVA out of canary"
2023-03-01 09:35:42 -08:00
Andrew Eisenberg
ea9dede453 Merge pull request #2126 from github/charisk/cli-v2.12.3-hack
Hack to avoid CodeQL CLI v2.12.3
2023-03-01 09:33:21 -08:00
Charis Kyriakou
e9062551ee Revert "Add controller repo 'learn more' link (#2120)"
This reverts commit fd6cd1f2d2.
2023-03-01 17:03:24 +00:00
Charis Kyriakou
dd2e79477f Revert "Move MRVA out of canary " 2023-03-01 17:02:11 +00:00
Charis Kyriakou
e9787c2702 Hack to avoid CodeQL CLI v2.12.3 2023-03-01 16:26:49 +00:00
Charis Kyriakou
56d283f6d5 Merge pull request #2121 from github/charisk/mrva-public-beta
Move MRVA out of canary
2023-03-01 09:55:28 +00:00
Charis Kyriakou
fd6cd1f2d2 Add controller repo 'learn more' link (#2120) 2023-03-01 09:40:11 +00:00
Shati Patel
57a4a2f717 Merge pull request #2112 from github/github-action/bump-cli
Bump CLI Version to v2.12.3 for integration tests
2023-02-28 16:25:28 +00:00
Charis Kyriakou
ff0425d889 Update CHANGELOG to include MRVA 2023-02-28 15:02:37 +00:00
Elena Tanasoiu
5fd902257e Merge pull request #2111 from github/elena/update-tutorial-database-path
Update path to codespace tutorial database
2023-02-28 14:51:14 +00:00
Charis Kyriakou
55761aa4ee Update README to include MRVA 2023-02-28 14:06:26 +00:00
Charis Kyriakou
1bf7fc148a MRVA execution not behind canary 2023-02-28 14:04:30 +00:00
Charis Kyriakou
590b839166 MRVA repositories panel not behind canary 2023-02-28 14:04:30 +00:00
Koen Vlaswinkel
fd57133a41 Merge pull request #2103 from github/koesie10/mock-more-objects
Remove more instances of `as unknown as`
2023-02-28 13:44:41 +01:00
Nora
f39bbd325c Merge pull request #2119 from github/nora/cleanup-split-runquery
Remove new commands from command palette
2023-02-28 13:28:16 +01:00
Nora
9a53b637e8 Remove new commands from comman palette 2023-02-28 11:42:08 +00:00
Nora
eb25f31b9f Merge pull request #2117 from github/nora/split-run-query
Split usage of runQuery and runQueryOnMultipleDatabases
2023-02-28 12:26:39 +01:00
Nora
7188d8df41 reorder command 2023-02-28 11:01:44 +00:00
Robert
badbee1bfb Merge pull request #2101 from github/robertbrignull/unique-command-use-query
Add unique-command-use.ql
2023-02-28 09:15:18 +00:00
Elena Tanasoiu
2f92ea396a Merge pull request #2090 from github/elena/install-csv-depenedencies
Install dependencies for tutorial query in codespace
2023-02-27 20:24:04 +00:00
Elena Tanasoiu
1f4790bbb7 Use join as it's meant to be used
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2023-02-27 20:09:05 +00:00
Elena Tanasoiu
28abc1e3a6 Don't reparse Uri path
I was initially trying to understand why this method was failing due
to an unrelated error [1] so I ended up over-engineering the path
parsing.

We can use the path from the first workspace folder, like we do in
other places in the extension.

[1]: https://github.com/github/vscode-codeql/pull/2104
2023-02-27 20:09:05 +00:00
Elena Tanasoiu
9b7c3bc2bf Install dependencies for tutorial query in codespace
When a user goes through the Code Tour, we select a dummy `csv` database
for them to get them up and running.

Once they complete the code tour and would like to continue writing
queries, they will need to add their own database.

After they do that, we check the language of their new database and
generate a skeleton QL pack for them so that they don't need to create
these files by hand. See [1] for details.

This skeleton pack folder will be called
`codeql-custom-queries-<language>` and it comes with its own example
query: `example.ql`.

When we try to run this example query, the query gets confused about
which `dbscheme` to pick, as it sees a `qlpack.yml` file in the new
skeleton pack folder, as well as one in the existing `tutorial-lib`
folder.

So we'll need to get rid of the `tutorial-lib` folder in order to make room
for new queries to be run once the tour is complete.

This commit introduces a `handleTourDependencies` step which will
trigger a `codeql pack install` command in order to install real library
dependencies for `tutorial-queries`, since we no longer have the dummy
library in `tutorial-lib`.
2023-02-27 20:09:04 +00:00
Robert
4d73e1a068 Remove getLocationOrdinal 2023-02-27 17:46:49 +00:00
Robert
6f5ac5df4f Introduce getAUse 2023-02-27 17:45:24 +00:00
Robert
4c880dfb19 Fix typos 2023-02-27 17:42:43 +00:00
Robert
56e8d8aac7 Merge pull request #2114 from github/robertbrignull/enable_telemetry
Enable new telemetry by default
2023-02-27 16:46:41 +00:00
Robert
ced9f60949 Add documentation 2023-02-27 16:06:38 +00:00
Charis Kyriakou
4fa530d69d Check controller repo before we set it (#2116) 2023-02-27 16:01:39 +00:00
Robert
c0a65c994a Convert to alert only first usage, instead of all other usages 2023-02-27 15:54:38 +00:00
Nora
921d9d22e4 split usage of runQuery and runQueryOnMultipleDatabases 2023-02-27 15:53:00 +00:00
Robert
ead1869a7e Use PackageJson class 2023-02-27 15:30:45 +00:00
Robert
b1ddf89fe3 Update CHANGELOG.md 2023-02-27 15:23:36 +00:00
Robert
c37096bf2c Update changelog 2023-02-27 13:12:28 +00:00
Robert
4127be2905 Enable new telemetry by default 2023-02-27 13:07:10 +00:00
Charis Kyriakou
ce29768796 Minor changes to the test plan (#2113) 2023-02-27 12:59:06 +00:00
Charis Kyriakou
571d9d1424 Merge pull request #2109 from github/version/bump-to-v1.7.11
Bump version to v1.7.11
2023-02-27 11:54:51 +00:00
Koen Vlaswinkel
65e652b5e4 Fix mockResolvedValue not working with mocked objects 2023-02-27 11:59:11 +01:00
Koen Vlaswinkel
fd2b91d4d4 Remove casting to QueryHistoryConfig 2023-02-27 11:59:11 +01:00
Koen Vlaswinkel
dd4df012e9 Remove as unknown as ExtensionContext 2023-02-27 11:59:11 +01:00
Koen Vlaswinkel
af167c6d6e Remove as unknown as DatabaseManager
Unfortunately `Object.defineProperty` doesn't work on proxies, so I've
added an options object to `mockedObject` which allows passing in
methods that will return a value for a specific property.
2023-02-27 11:59:11 +01:00
Koen Vlaswinkel
551ed95fc8 Remove as unknown as QueryHistoryManager 2023-02-27 11:59:11 +01:00
Koen Vlaswinkel
9b1ca5136e Remove as unknown as WorkspaceFolder 2023-02-27 11:59:10 +01:00
Koen Vlaswinkel
fa5bad6946 Remove as unknown as TextEditor/TextDocument 2023-02-27 11:59:10 +01:00
github-actions[bot]
6229de8634 Bump CLI version from v2.12.2 to v2.12.3 for integration tests 2023-02-24 19:18:50 +00:00
Koen Vlaswinkel
4c14db951b Merge pull request #2102 from github/koesie10/mock-objects
Add new `mockedObject` function
2023-02-24 15:43:19 +01:00
Koen Vlaswinkel
bed4e8a060 Rename methods parameter to props 2023-02-24 15:28:01 +01:00
Elena Tanasoiu
2c6dc24525 Update path to codespace tutorial database
In https://github.com/github/codespaces-codeql/pull/12 we moved
the source for our tutorial database into `.tours` in order to
avoid confusing the user when we load the database into the
workspace, since they'd see two databases.

Since this is just the source, we'd like to hide it.
2023-02-23 17:08:20 +00:00
Charis Kyriakou
8d5a00dcc7 Remove obsolete config setting (#2110) 2023-02-23 16:47:30 +00:00
github-actions[bot]
3ea3cd8e9b Bump version to v1.7.11 2023-02-23 08:58:41 +00:00
Koen Vlaswinkel
2a43ffb49a Add new mockedObject function
This will remove some instances where we're using `as unknown as T` and
replace them by a call to `mockedObject<T>()`. The `mockedObject`
function is a bit more explicit about what it does and has types which
ensure that the methods that are set on the object actually exist.

Unfortunately, we can't fully get rid of `as unknown as T` in the
`mockedObject` function. However, this construct is more localized and
does not need to be used in as many places. If we do enable an ESLint
rule to prevent the use of `as unknown as T`, I would feel comfortable
with disabling the rule for the `mockedObject` function.
2023-02-21 13:01:28 +01:00
Robert
70ae7284f3 Add unique-command-use.ql 2023-02-21 11:09:15 +00:00
28 changed files with 595 additions and 257 deletions

View 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"

View File

@@ -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.

View File

@@ -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 doesnt 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.

View File

@@ -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": {

View File

@@ -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"
},
{

View File

@@ -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

View File

@@ -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,
);

View File

@@ -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,

View File

@@ -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;

View File

@@ -105,7 +105,7 @@ export function transformBqrsResultSet(
};
}
type BqrsKind =
export type BqrsKind =
| "String"
| "Float"
| "Integer"

View File

@@ -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}") {

View File

@@ -1,5 +1,5 @@
[
"v2.12.2",
"v2.12.3",
"v2.11.6",
"v2.7.6",
"v2.8.5",

View File

@@ -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(() => {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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];

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);
});

View File

@@ -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];

View File

@@ -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];

View File

@@ -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++,

View File

@@ -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);

View File

@@ -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 () => {

View File

@@ -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);
}
});

View File

@@ -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();

View File

@@ -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,
);
});

View File

@@ -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`);
},
});
}