Compare commits

...

293 Commits

Author SHA1 Message Date
Shati Patel
4c7c1cb0e6 v1.8.1 (#2216)
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
2023-03-23 11:00:29 +00:00
Robert
a3c4c8fb61 Merge pull request #2212 from github/robertbrignull/app_executeCommand
Replace app.executeCommand with app.commands.execute
2023-03-23 10:28:36 +00:00
Koen Vlaswinkel
73b0a0565f Merge pull request #2214 from github/koesie10/remove-command-runner-with-progress
Remove `commandRunnerWithProgress`
2023-03-23 11:26:20 +01:00
Robert
8fbde9fba1 Limit setContext keys 2023-03-23 10:12:20 +00:00
Koen Vlaswinkel
aca0489fdc Remove commandRunnerWithProgress
The `commandRunnerWithProgress` is unused because all commands have been
converted to using `commandRunner` in combination with `withProgress`.
2023-03-23 11:09:32 +01:00
Koen Vlaswinkel
dae472ca05 Merge pull request #2213 from github/koesie10/check-for-updates-typed-command
Add `codeQL.checkForUpdatesToCLI` type for command
2023-03-23 10:49:18 +01:00
Koen Vlaswinkel
20f85f2b81 Merge pull request #2210 from github/koesie10/base-typed-commands
Convert some base commands to typed commands
2023-03-23 10:03:53 +01:00
Koen Vlaswinkel
81cce9fa8b Add codeQL.checkForUpdatesToCLI type for command
The `codeQL.checkForUpdatesToCLI` command is registered pre-activation,
and we don't really want to create the command manager before
activation, so this will just add the correct type without registering
it using the command manager.
2023-03-23 09:58:27 +01:00
Koen Vlaswinkel
a27ca2e177 Merge remote-tracking branch 'origin/main' into koesie10/base-typed-commands 2023-03-23 09:51:24 +01:00
Robert
3e9c2c85d3 Remove app.executeCommand 2023-03-22 17:13:12 +00:00
Robert
297fa2ebd3 Merge pull request #2207 from github/robertbrignull/extension_commands
Start using app.commands.execute for all commands called from extension.ts
2023-03-22 16:59:53 +00:00
Shati Patel
844e58a1b1 Tidy up recent changelog entries (#2211) 2023-03-22 16:54:17 +00:00
Robert
9948b93b76 Merge branch 'main' into robertbrignull/extension_commands 2023-03-22 16:35:56 +00:00
Koen Vlaswinkel
e1d5a4ddaa Merge remote-tracking branch 'origin/main' into koesie10/base-typed-commands 2023-03-22 17:22:26 +01:00
Koen Vlaswinkel
7059f46141 Merge pull request #2209 from github/koesie10/query-editor-typed-commands
Convert query editing commands to typed commands
2023-03-22 17:21:31 +01:00
Koen Vlaswinkel
fe6ff6801a Convert some base commands to typed commands 2023-03-22 17:02:16 +01:00
Koen Vlaswinkel
be3459c1aa Convert query editing commands to typed commands 2023-03-22 16:52:59 +01:00
Koen Vlaswinkel
5ac5de8a5b Move query editing commands to separate file 2023-03-22 16:50:44 +01:00
Koen Vlaswinkel
322c1a8835 Merge pull request #2206 from github/koesie10/test-ui-typed-commands
Convert test UI commands to typed commands
2023-03-22 16:42:18 +01:00
Robert
5d6a2e6d7f rename types 2023-03-22 15:37:39 +00:00
Robert
0e79b92829 Merge branch 'main' into robertbrignull/extension_commands 2023-03-22 15:36:31 +00:00
Koen Vlaswinkel
59378daff3 Merge remote-tracking branch 'origin/main' into koesie10/test-ui-typed-commands 2023-03-22 16:29:36 +01:00
Koen Vlaswinkel
125af1139b Merge pull request #2208 from github/koesie10/mock-server-typed-commands
Convert mock API server commands to typed commands
2023-03-22 16:28:28 +01:00
Robert
6afdf6357b Merge pull request #2202 from github/robertbrignull/extract_progress
Move withProgress and associated code to a separate file
2023-03-22 15:19:50 +00:00
Koen Vlaswinkel
fd7013f754 Convert mock API server commands to typed commands 2023-03-22 16:04:49 +01:00
Robert
408c042b3b Fix remaining imports 2023-03-22 14:49:04 +00:00
Koen Vlaswinkel
b2fceb9b2d Merge pull request #2205 from github/koesie10/results-view-typed-commands
Convert results view commands to typed commands
2023-03-22 15:46:08 +01:00
Robert
819e596b9b Merge branch 'main' into robertbrignull/extract_progress 2023-03-22 14:31:55 +00:00
Robert
31af28e73b Add link to docs 2023-03-22 14:28:10 +00:00
Koen Vlaswinkel
c6d8a09f19 Add support for Partial in the command manager
The command manager types didn't fully support commands defined with
`Partial` because it deduced that the command function was `undefined`
when the function was not defined. However, if the command is not
present, the command registration will not be called. This fixes the
types by specifying that the command definition will never be
`undefined`.
2023-03-22 15:27:18 +01:00
Robert
39d4675b44 Start using app.commands.execute for all commands called from extension.ts 2023-03-22 14:16:55 +00:00
Koen Vlaswinkel
e74a2e4a15 Remove UIService
This class seems to have been introduced at some point to reduce the
dependency on VS Code from the test UI service. However, none of its
methods are being used anymore, and by using typed commands we have
already reduced the dependency on VS Code. Therefore, we can simply
remove this class.
2023-03-22 15:14:22 +01:00
Koen Vlaswinkel
9f85f56055 Convert test UI commands to typed commands 2023-03-22 15:13:05 +01:00
Koen Vlaswinkel
ac57f5005d Convert results view commands to typed commands 2023-03-22 15:06:15 +01:00
Koen Vlaswinkel
88a9ecbeab Merge pull request #2204 from github/koesie10/summary-language-support-typed-commands
Convert summary language commands to typed commands
2023-03-22 14:52:45 +01:00
Koen Vlaswinkel
9f7c7b2ed8 Merge pull request #2203 from github/koesie10/ast-viewer-typed-commands
Convert AST viewer commands to typed commands
2023-03-22 14:44:04 +01:00
Koen Vlaswinkel
1f8070c8b5 Convert summary language commands to typed commands 2023-03-22 14:26:06 +01:00
Koen Vlaswinkel
bc29231fec Convert AST viewer commands to typed commands 2023-03-22 14:11:43 +01:00
Koen Vlaswinkel
e724577d82 Merge pull request #2196 from github/koesie10/ast-cfg-typed-commands
Convert AST and CFG commands to typed commands
2023-03-22 13:32:34 +01:00
Robert
b914b97be7 Pull progress to separate file 2023-03-22 12:07:16 +00:00
Koen Vlaswinkel
9d4b19f91f Merge remote-tracking branch 'origin/main' into koesie10/ast-cfg-typed-commands 2023-03-22 13:03:21 +01:00
Koen Vlaswinkel
8a66bb4017 Merge pull request #2198 from github/koesie10/packaging-typed-commands
Convert packaging to typed commands
2023-03-22 13:02:21 +01:00
Robert
e55fb8c7a7 Merge pull request #2191 from github/robertbrignull/variant_analysis_commands
Convert all variant analysis commands to typed commands
2023-03-22 11:56:31 +00:00
Robert
a6fefdbabb Merge branch 'main' into robertbrignull/variant_analysis_commands 2023-03-22 11:44:25 +00:00
Robert
2334e4e7b2 Merge pull request #2192 from github/robertbrignull/export_selected_results_command
Move codeQL.exportSelectedVariantAnalysisResults to query history manager
2023-03-22 11:41:13 +00:00
Koen Vlaswinkel
5c06bcc6bd Merge remote-tracking branch 'origin/main' into koesie10/packaging-typed-commands 2023-03-22 12:39:36 +01:00
Koen Vlaswinkel
3240809d11 Merge pull request #2200 from github/koesie10/restart-typed-command
Convert codeQL.restartQueryServer to a typed command
2023-03-22 12:37:20 +01:00
Koen Vlaswinkel
3e66e7aaf3 Merge remote-tracking branch 'origin/main' into koesie10/restart-typed-command 2023-03-22 12:10:54 +01:00
Koen Vlaswinkel
71831fe460 Merge pull request #2201 from github/koesie10/eval-log-viewer-typed-command
Convert codeQLEvalLogViewer.clear to a typed command
2023-03-22 12:07:34 +01:00
Robert
0983733a67 fix typo 2023-03-22 10:51:03 +00:00
Robert
0166f9a557 Merge branch 'main' into robertbrignull/variant_analysis_commands 2023-03-22 10:50:17 +00:00
Robert
5af0ebcb24 convert to type imports 2023-03-22 10:49:50 +00:00
Koen Vlaswinkel
9d6c78b656 Merge branch 'main' into koesie10/ast-cfg-typed-commands 2023-03-22 11:37:23 +01:00
Koen Vlaswinkel
56c83eb480 Merge pull request #2195 from github/koesie10/move-database-commands
Move remaining local database commands to databases UI
2023-03-22 11:36:56 +01:00
Koen Vlaswinkel
aa0d011daa Convert codeQLEvalLogViewer.clear to a typed command 2023-03-22 11:34:39 +01:00
Koen Vlaswinkel
76558b8d41 Merge pull request #2194 from github/koesie10/typed-local-query-commands
Convert local query commands to typed commands
2023-03-22 11:34:34 +01:00
Koen Vlaswinkel
eb5659a628 Merge branch 'main' into koesie10/move-database-commands 2023-03-22 11:22:48 +01:00
Koen Vlaswinkel
11b4de1820 Merge branch 'main' into koesie10/typed-local-query-commands 2023-03-22 11:20:02 +01:00
Koen Vlaswinkel
2c8c7cec8f Convert codeQL.restartQueryServer to a typed command 2023-03-22 10:29:09 +01:00
Koen Vlaswinkel
1c6d9f3f22 Use commandRunner for codeQL.restartQueryServer 2023-03-22 10:26:29 +01:00
Elena Tanasoiu
f196e34fa5 Merge pull request #2188 from github/elena/force-workspace-in-codespace
Codespace: Open tutorial workspace on extension start
2023-03-22 08:30:31 +00:00
Koen Vlaswinkel
7c7a64ca5b Convert packaging commands to typed commands 2023-03-22 09:22:07 +01:00
Koen Vlaswinkel
dfff7ae8de Move packaging commands to withProgress 2023-03-22 09:22:07 +01:00
Koen Vlaswinkel
ef267f87bb Move packaging command registration to separate file 2023-03-22 09:22:07 +01:00
Elena Tanasoiu
a7800ce3bc Merge branch 'main' into elena/force-workspace-in-codespace 2023-03-22 07:46:19 +00:00
github-actions[bot]
168af11e00 Bump CLI version from v2.12.4 to v2.12.5 for integration tests (#2197)
Co-authored-by: github-actions[bot] <github-actions@github.com>
2023-03-21 16:27:58 +00:00
Koen Vlaswinkel
17bab1c09c Remove unnecessary viewAst function 2023-03-21 16:10:05 +01:00
Koen Vlaswinkel
b3092be5d3 Convert AST and CFG commands to typed commands 2023-03-21 16:09:15 +01:00
Koen Vlaswinkel
1909fee91f Remove use of commandRunnerWithProgress in AST and CFG commands 2023-03-21 16:04:41 +01:00
Koen Vlaswinkel
6ff2670ec2 Remove duplication of AST and CFG command implementations 2023-03-21 16:02:05 +01:00
Koen Vlaswinkel
0379575256 Move AST and CFG commands to separate file 2023-03-21 15:58:12 +01:00
Elena Tanasoiu
8fd9ebf2d8 Limit comparison to Uri path
We attempted to specify exactly which URI we're expecting here.

However, `Uri.parse` behaves differently in the test than it does in
the code so we've inadvertently created a flakey test [1]. The URI we
generate in the test has a `scheme: 'c'` while the one in the code has
a `scheme: 'C'` property.

This only happens on windows, not ubuntu.

Let's narrow the comparison to just the path of the URI.

[1]: https://github.com/github/vscode-codeql/actions/runs/4478429334/jobs/7871178529#step:7:231
2023-03-21 14:26:22 +00:00
Koen Vlaswinkel
bed56ef648 Make public methods private in local databases UI
Some of the methods in the `DatabaseUI` were public because they were
used in the `extension.ts` file. We have moved these method calls into
this file, so they do not need to be public anymore. We can also get rid
of the separation between some of these methods, so I've moved them into
the function that calls them.
2023-03-21 14:50:24 +01:00
Koen Vlaswinkel
7ab986fabe Move codeQL.chooseDatabaseGithub command 2023-03-21 14:48:14 +01:00
Koen Vlaswinkel
15d30d5342 Move codeQL.chooseDatabaseInternet command 2023-03-21 14:47:26 +01:00
Koen Vlaswinkel
71f22b9a7a Move codeQL.chooseDatabaseArchive command 2023-03-21 14:45:54 +01:00
Koen Vlaswinkel
32b6ad53cf Move codeQL.chooseDatabaseFolder command 2023-03-21 14:44:46 +01:00
Koen Vlaswinkel
ac2f4475c0 Group local database commands by type 2023-03-21 14:40:04 +01:00
Elena Tanasoiu
108943d135 existsSync -> pathExists
Use async version of file check.
2023-03-21 13:19:33 +00:00
Gulshan Singh
bb0c53d65d Display EntityValue labels in CSV export (#2170) 2023-03-21 11:59:18 +00:00
Nora
82d03091d0 Merge pull request #2193 from github/nora/query-history-data-model
Move query-serialization
2023-03-21 12:23:18 +01:00
Elena Tanasoiu
c1a515ed82 Open tutorial before extension activation
To reduce lag when checking for the existence of the tutorial workspace.
2023-03-21 11:20:14 +00:00
Elena Tanasoiu
0368d537ad Ask the user for permission to reload workspace
To make this a nicer experience for the user, we're adding a prompt
to let them know we're about to reload the workspace.
2023-03-21 11:20:14 +00:00
Elena Tanasoiu
7059802a25 Add more logging before attempting to open tutorial workspace 2023-03-21 11:20:14 +00:00
Elena Tanasoiu
0f4fcdf676 Add comment with PR description
This adds the explanation from the PR description in a comment and
removes comment that's no longer helpful.
2023-03-21 11:20:14 +00:00
Elena Tanasoiu
ce413a6385 Stub isCodespaceTemplate correctly
Now that we fixed our expectation in the previous commit, we could see we
were stubbing this to false instead of true.

So now the test is checking the right scenario.
2023-03-21 11:20:13 +00:00
Elena Tanasoiu
8db9f52df3 Adjust the rest of the test expectations
Our expectation was quite narrow: we expect to not call an `openFolder`
command. We didn't specify any params for it, which might mean this
expectation wasn't working like it should.

Let's just check that `executeCommand` isn't called at all.
2023-03-21 11:20:13 +00:00
Elena Tanasoiu
782e413c64 Add workspace filepath to test expectation 2023-03-21 11:20:13 +00:00
Elena Tanasoiu
fade710f95 Call it commandSpy everywhere 2023-03-21 11:20:13 +00:00
Elena Tanasoiu
737a1f5c37 Use existing file path consistently 2023-03-21 11:19:56 +00:00
Koen Vlaswinkel
7950c1c982 Convert local query commands to typed commands
The local query commands are using a separate logger, and this is not
supported by the command manager because it is quite specific to this
extension. Therefore, we create a separate command manager which uses
a different logger to separate the commands.
2023-03-21 12:08:44 +01:00
Koen Vlaswinkel
f37a6c5e9e Convert commandRunnerWithProgress to commandRunner 2023-03-21 12:08:44 +01:00
Koen Vlaswinkel
649179f62e Rename local queries manager variable names 2023-03-21 12:08:44 +01:00
Koen Vlaswinkel
8a2630d1b7 Move local queries commands to separate file 2023-03-21 12:08:44 +01:00
Nora
fa29bcc5fd Rename serializer 2023-03-21 11:03:58 +00:00
Nora
5cd50a67e7 Move query-serialization 2023-03-21 10:39:02 +00:00
Koen Vlaswinkel
f7b6d4c4a4 Merge pull request #2189 from github/koesie10/typed-local-database-ui-commands
Convert local database commands to typed commands
2023-03-21 10:39:57 +01:00
Koen Vlaswinkel
58bffe1edf Merge remote-tracking branch 'origin/main' into koesie10/typed-local-database-ui-commands 2023-03-21 10:27:31 +01:00
Robert
0c9df6edba Convert the codeQL.openVariantAnalysisView command 2023-03-20 17:41:23 +00:00
Robert
ac0d920156 Convert the codeQL.loadVariantAnalysisRepoResults command 2023-03-20 17:41:23 +00:00
Robert
5f2a8fa1d5 Convert the codeQL.autoDownloadVariantAnalysisResult command 2023-03-20 17:41:21 +00:00
Robert
b55910d2b9 Convert the codeQL.monitorVariantAnalysis command 2023-03-20 17:32:35 +00:00
Robert
e586f3de53 Move codeQL.exportSelectedVariantAnalysisResults to query history manager 2023-03-20 16:59:51 +00:00
Robert
08849c1df4 Convert codeQL.copyVariantAnalysisRepoList command 2023-03-20 16:50:53 +00:00
Robert
aa64459353 Convert existing variant analysis commands to bind(this) 2023-03-20 16:50:53 +00:00
Robert
3c229d244e Merge pull request #2186 from github/robertbrignull/export_results_telemetry
Add telemetry for exporting variant analysis results
2023-03-20 16:47:49 +00:00
Robert
fb70382929 Fix tests to not reference deleted command 2023-03-20 16:33:55 +00:00
Robert
f49314f6f3 Merge branch 'main' into robertbrignull/export_results_telemetry 2023-03-20 15:46:41 +00:00
Koen Vlaswinkel
9044d11d2b Merge pull request #2190 from github/koesie10/typed-new-database-panel-commands
Convert new database panel to typed commands
2023-03-20 16:25:04 +01:00
Elena Tanasoiu
5444a9e55e Fail gracefully if we can't set up code tour
We're running this at the extension start-up. We don't want it to block the extension
from completing activation, so let's swallow any errors from the code tour and output
them, instead of letting this affect the rest of the extension activation.
2023-03-20 15:01:04 +00:00
Elena Tanasoiu
4fa3c459a1 Open tutorial workspace on extension start
When opening https://github.com/github/codespaces-codeql/ in a
codespace, it's easy to miss the prompt that tells you to open the
tutorial.code-workspace file.

In fact people actively dismiss the alert to get it out of the way.

If you miss that prompt, you end up with a single-rooted workspace,
which causes various other problems.

While there is an open issue to allow VS Code to open a default
workspace [1], there doesn't seem to have been any progress on it
in the last two years.

So we're taking matters into our own hands and forcing the extension
to open the tutorial workspace, if it detects it.

This will only happen if the following three conditions are met:
- the .tours folder exists
- the tutorial.code-workspace file exists
- the CODESPACES_TEMPLATE setting hasn't been set

NB: the `CODESPACES_TEMPLATE` setting can only be found if the
tutorial.code-workspace has already been opened. So it's a good
indicator that we're in the folder, but the user has ignored the prompt.

[1]: https://github.com/microsoft/vscode-remote-release/issues/3665
2023-03-20 13:31:20 +00:00
Koen Vlaswinkel
2e9a22e86d Convert new database panel to typed commands
This converts the new database panel to use typed commands. There should
be no changes in behaviour.
2023-03-20 13:52:32 +01:00
Robert
b76bef4246 Remove the codeQL.exportVariantAnalysisResults command 2023-03-20 12:33:51 +00:00
Koen Vlaswinkel
e603de41c1 Convert local database commands to typed commands 2023-03-20 11:39:53 +01:00
Koen Vlaswinkel
dbd832f1a0 Switch local databases to function definitions
The local databases UI was essentially the only class which was defining
methods using assignment to a class property rather than using function
definitions and binding them. This switches it to use function
definitions and binding, which is more consistent with the rest of the
codebase.
2023-03-20 11:29:45 +01:00
Robert
5b2093df8f Merge pull request #2185 from github/robertbrignull/remove_internal_commands
Remove two more internal-only commands
2023-03-20 10:24:31 +00:00
Koen Vlaswinkel
af63e5094f Convert commandRunnerWithProgress invocations to withProgress 2023-03-20 11:19:32 +01:00
Robert
94db1dff73 Remove codeQL.openVariantAnalysisQueryText command 2023-03-17 16:13:21 +00:00
Robert
a44ecadae5 Add telemetry for exporting variant analysis results 2023-03-17 16:09:00 +00:00
Robert
a375afd61b Remove codeQL.cancelVariantAnalysis command 2023-03-17 15:48:00 +00:00
Charis Kyriakou
42ce27b112 Merge pull request #2183 from github/charis/remove-primer
Remove primer packages and make styled-components a top-level dependency
2023-03-17 14:46:02 +00:00
Koen Vlaswinkel
f86c0b826a Merge pull request #2176 from github/koesie10/query-history-commands-typed
Convert query history commands to typed commands
2023-03-17 15:28:21 +01:00
Charis Kyriakou
2d46365406 Add styled-components as a top level dependency 2023-03-17 14:19:22 +00:00
Charis Kyriakou
50e89ba1a3 Remove primer packages 2023-03-17 13:46:43 +00:00
Charis Kyriakou
7914403da0 Wire up data flow paths view (#2182) 2023-03-17 13:30:32 +00:00
Koen Vlaswinkel
5be08d7c92 Revert change to context menu name 2023-03-17 12:29:25 +01:00
Robert
df24a705b0 Merge pull request #2179 from github/robertbrignull/remove_openVariantAnalysisQueryFile_command
Remove codeQL.openVariantAnalysisQueryFile command
2023-03-17 11:23:31 +00:00
Koen Vlaswinkel
fce51ca9a2 Convert query history commands to typed commands 2023-03-17 10:54:40 +01:00
Charis Kyriakou
8a5273058b Add new data flow paths view (empty) (#2172) 2023-03-17 09:25:03 +00:00
Koen Vlaswinkel
f3274b39d2 Merge pull request #2015 from github/dependabot/npm_and_yarn/extensions/ql-vscode/glob-promise-6.0.2
Bump glob-promise from 4.2.2 to 6.0.2 in /extensions/ql-vscode
2023-03-17 09:49:53 +01:00
Koen Vlaswinkel
38e551bb2a Merge pull request #1930 from github/dependabot/npm_and_yarn/extensions/ql-vscode/tar-stream-3.0.0
Bump tar-stream from 2.2.0 to 3.0.0 in /extensions/ql-vscode
2023-03-17 09:48:52 +01:00
dependabot[bot]
cc53cd54c4 Bump webpack-cli from 4.6.0 to 5.0.1 in /extensions/ql-vscode (#2181)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.6.0 to 5.0.1.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.6.0...webpack-cli@5.0.1)

---
updated-dependencies:
- dependency-name: webpack-cli
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 09:38:00 -07:00
Robert
61cc9191f9 Merge pull request #2139 from github/robertbrignull/webview_error_telemetry
Add listeners for unhandled errors to web views
2023-03-16 11:18:52 +00:00
Robert
f2f1b1d6a3 Collect common messages 2023-03-16 10:59:02 +00:00
Robert
64531f5a6f Check undefined instead of using typeof 2023-03-16 10:54:29 +00:00
Robert
29bb7ce01e Emit telemetry when opening variant analysis query file 2023-03-16 10:39:02 +00:00
Robert
9045253624 Merge pull request #2178 from github/robertbrignull/split-commands/codeQLQueryHistory.openQuery
Split up codeQLQueryHistory.openQuery command
2023-03-15 17:12:06 +00:00
Robert
34ab409050 Remove codeQL.openVariantAnalysisQueryFile command 2023-03-15 17:06:17 +00:00
Robert
e6f543670a Split up codeQLQueryHistory.openQuery command 2023-03-15 16:54:46 +00:00
Robert
04c24d0996 Merge pull request #2177 from github/robertbrignull/split-commands/codeQLQueryHistory.removeHistoryItem
Split up the codeQLQueryHistory.removeHistoryItem command
2023-03-15 16:54:24 +00:00
Koen Vlaswinkel
41ca0ffba6 Merge pull request #2175 from github/dependabot/npm_and_yarn/extensions/ql-vscode/lint-staged-13.2.0
Bump lint-staged from 10.2.11 to 13.2.0 in /extensions/ql-vscode
2023-03-15 17:16:03 +01:00
Robert
84f60ccb8c Avoid duplicate use of the codeQLQueryHistory.removeHistoryItem command 2023-03-15 16:11:40 +00:00
dependabot[bot]
0fa0fa3523 Bump tar-stream from 2.2.0 to 3.0.0 in /extensions/ql-vscode
Bumps [tar-stream](https://github.com/mafintosh/tar-stream) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/mafintosh/tar-stream/releases)
- [Commits](https://github.com/mafintosh/tar-stream/compare/v2.2.0...v3.0.0)

---
updated-dependencies:
- dependency-name: tar-stream
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-15 16:10:41 +00:00
dependabot[bot]
fe554ee2a7 Bump glob-promise from 4.2.2 to 6.0.2 in /extensions/ql-vscode
Bumps [glob-promise](https://github.com/ahmadnassri/node-glob-promise) from 4.2.2 to 6.0.2.
- [Release notes](https://github.com/ahmadnassri/node-glob-promise/releases)
- [Commits](https://github.com/ahmadnassri/node-glob-promise/compare/v4.2.2...v6.0.2)

---
updated-dependencies:
- dependency-name: glob-promise
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-15 16:09:35 +00:00
Koen Vlaswinkel
019af98f40 Merge pull request #2171 from github/koesie10/commands-with-progress
Convert run variant analysis command to new commands package
2023-03-15 16:39:35 +01:00
Koen Vlaswinkel
c853bafb91 Merge remote-tracking branch 'origin/main' into koesie10/commands-with-progress 2023-03-15 15:43:34 +01:00
Robert
c632c38220 Merge pull request #2087 from github/robertbrignull/no-nested-functions
Avoid having functions that aren't at the top level in extension.ts
2023-03-15 14:21:23 +00:00
Robert
fd570abdcd Merge branch 'main' into robertbrignull/no-nested-functions 2023-03-15 13:37:35 +00:00
Robert
e81d585336 Swap arguments so command arg comes last 2023-03-15 13:36:57 +00:00
Anders Starcke Henriksen
dc55ef9985 Merge pull request #2157 from github/starcke/commands-registration
Commands registration
2023-03-15 14:05:25 +01:00
Robert
3fe069975a Avoid reporting errors twice 2023-03-15 12:26:58 +00:00
Anders Starcke Henriksen
5303ec67cb Name updates. 2023-03-15 12:04:52 +01:00
Robert
969fdb6337 Make type checking all use typeof 2023-03-15 10:21:25 +00:00
Robert
47fa752c5c Merge branch 'main' into robertbrignull/webview_error_telemetry 2023-03-15 09:56:58 +00:00
dependabot[bot]
639c8728dd Bump lint-staged from 10.2.11 to 13.2.0 in /extensions/ql-vscode
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.2.11 to 13.2.0.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v10.2.11...v13.2.0)

---
updated-dependencies:
- dependency-name: lint-staged
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-15 09:22:59 +00:00
Koen Vlaswinkel
3d9f55ffea Merge pull request #2174 from github/dependabot/npm_and_yarn/extensions/ql-vscode/webpack-5.76.0
Bump webpack from 5.73.0 to 5.76.0 in /extensions/ql-vscode
2023-03-15 10:20:43 +01:00
dependabot[bot]
ebd18cd245 Bump webpack from 5.73.0 to 5.76.0 in /extensions/ql-vscode
Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.76.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.76.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-15 02:21:22 +00:00
Robert
7571304fb2 Move viewAst to top level 2023-03-14 17:57:23 +00:00
Robert
764830e69d Move runVariantAnalysis to top level 2023-03-14 17:55:35 +00:00
Robert
9d59abd0a8 Move openReferencedFile to top level 2023-03-14 17:53:50 +00:00
Robert
165542d115 Move previewQueryHelp to top level 2023-03-14 17:51:57 +00:00
Robert
d8371708b3 Move compileAndRunQueryOnMultipleDatabases to top level 2023-03-14 17:49:58 +00:00
Robert
c3f4a012a9 Move DatabaseQuickPickItem to top level 2023-03-14 17:49:07 +00:00
Robert
e2e197f4d9 Move compileAndRunQuery to top level 2023-03-14 17:46:37 +00:00
Robert
c366342f95 Move showResultsForCompletedQuery to top level 2023-03-14 17:43:58 +00:00
Robert
b05977516e Move showResultsForComparison to top level 2023-03-14 17:43:07 +00:00
Andrew Eisenberg
444de8cc3d Merge pull request #2072 from github/aeisenberg/mrva-extension-packs
Inject extension pack dependencies into MRVA packs
2023-03-14 10:41:35 -07:00
Robert
ee759abea9 Move installOrUpdateThenTryActivate to top level 2023-03-14 17:40:39 +00:00
Robert
a8f67d72f5 Move getDistributionDisplayingDistributionWarnings to top level 2023-03-14 17:38:36 +00:00
Robert
118be4a19a Move installOrUpdateDistribution to top level 2023-03-14 17:36:30 +00:00
Robert
8d3ae78db2 Move installOrUpdateDistributionWithProgressTitle to top level 2023-03-14 17:34:10 +00:00
Robert
ada5f51e85 Move codeQlVersionRange to top level 2023-03-14 17:31:11 +00:00
Robert
82f4941415 Move shouldUpdateOnNextActivationKey to top level 2023-03-14 17:30:49 +00:00
Robert
23e1715c4a Move DistributionUpdateConfig to top level 2023-03-14 17:27:48 +00:00
Andrew Eisenberg
250dc15fe5 Add extension packs to variant analysis queries
This change allows the `codeQL.runningQueries.useExtensionPacks`
setting to be respected when running variant analysis queries. When
set to `all`, before uploading the generated query pack, all extension
packs in the workspace will be injected as dependencies into the qlpack
file.
2023-03-14 08:55:01 -07:00
Koen Vlaswinkel
c3b023cf4b Convert run variant analysis command to new commands package 2023-03-14 13:09:31 +01:00
Koen Vlaswinkel
52b00fe434 Merge branch 'main' into starcke/commands-registration 2023-03-14 12:55:25 +01:00
Koen Vlaswinkel
64d97aaf7e Merge pull request #2165 from github/koesie10/simpler-progress-task
Simpler `withProgress` calls
2023-03-14 12:06:59 +01:00
Koen Vlaswinkel
964640c757 Remove default location values in withProgress calls 2023-03-14 11:00:59 +01:00
Koen Vlaswinkel
3d354e1fb4 Merge pull request #2164 from github/koesie10/unify-command-runners
Unify `commandRunner` implementations
2023-03-14 09:54:16 +01:00
Koen Vlaswinkel
d92708e4a4 Merge pull request #2166 from github/koesie10/remove-babel-loader
Remove direct dependency on babel-loader
2023-03-14 09:54:06 +01:00
Dave Bartolomeo
6c3698bfb3 Merge pull request #2169 from github/dbartol/tee-logger
Use `TeeLogger` instead of `additionalLogLocation:` option
2023-03-13 17:49:13 -04:00
Dave Bartolomeo
a1d8aac391 Better handling of I/O errors when writing to side log 2023-03-13 17:34:53 -04:00
Dave Bartolomeo
56095d365c Fix test code 2023-03-13 17:28:53 -04:00
Dave Bartolomeo
fc8b13b8be Use TeeLogger instead of additionalLogLocation: option 2023-03-13 15:10:30 -04:00
Koen Vlaswinkel
3aa24ebb2c Remove direct dependency on babel-loader
The extension Webpack config does not use `babel-loader`, so we can
remove it as a direct dependency. `babel-loader` is still included in
our `node_modules` because Storybook depends on it, but the version is
now completely managed by Storybook rather than us.
2023-03-13 16:17:23 +01:00
Andrew Eisenberg
205327d8aa Merge pull request #2162 from github/aeisenberg/variant-analysis-tests 2023-03-13 07:37:26 -07:00
Koen Vlaswinkel
4969a08531 Make progress options optional
This will make the progress options passed to `withProgress` optional by
moving it to be the second argument and setting a default value for the
`location`. This will make it much easier to use from a variety of
commands.
2023-03-13 15:13:55 +01:00
Koen Vlaswinkel
2646716261 Remove args from ProgressTask
This removes the `args` from the `ProgressTask` passed to
`withProgress`. The `args` is only used by the
`commandRunnerWithProgress` and can easily be replaced by an anonymous
function that passes the `args` instead. This will simplify the
`ProgressTask` interface and make it easier to use.
2023-03-13 14:51:32 +01:00
Anders Starcke Henriksen
79d15cc602 Add comments. 2023-03-13 14:36:28 +01:00
Koen Vlaswinkel
a7bb74190f Unify commandRunner implementations
The `commandRunnerWithProgress` implementation isn't actually any
different from `commandRunner`, except for the call to `withProgress`
and support for an `outputLogger` argument. Therefore, this will simply
make `commandRunnerWithProgress` a wrapper around `commandRunner`,
removing quite some duplication in the process.
2023-03-13 14:10:00 +01:00
Anders Starcke Henriksen
d990f316d1 Fix tests. 2023-03-13 12:16:38 +01:00
Andrew Eisenberg
fe123b3187 Inject extension pack dependencies into MRVA packs
If the user requests that extension packs be included in their MRVA run,
then do the following:

1. Search the workspace for all extension packs
2. Add each extension pack as an explicit and direct dependency on
   the generated pack.

It is ok to use `*` as a dependency since we are guaranteed that
exactly one version of each injected extension pack dependency is
available when the pack is being compiled.

If we find multiple paths to an extension pack of the same name, this
is an error since it is ambiguous which path to use.
2023-03-10 11:15:36 -08:00
Andrew Eisenberg
5d85da5526 Refactor generateQueryPack
Creates a handful of new functions and adds documentation. This commit
has no behavioural changes.
2023-03-10 11:15:36 -08:00
Andrew Eisenberg
b3e642a2b4 Remove any references 2023-03-10 11:11:25 -08:00
Andrew Eisenberg
0d8df9ad88 Merge pull request #2065 from github/aeisenberg/run-with-all-data-extensions
Add ability to run query with data extensions
2023-03-10 08:09:19 -08:00
Koen Vlaswinkel
2457d4bd9d Remove command manager argument to variant analysis view 2023-03-10 16:56:06 +01:00
Koen Vlaswinkel
df86adbbfa Split variant analysis and extension commands 2023-03-10 16:49:30 +01:00
Koen Vlaswinkel
e97ffd2f27 Use command manager executeCommand 2023-03-10 16:37:20 +01:00
Koen Vlaswinkel
088e9aa958 Add command manager to app 2023-03-10 16:35:22 +01:00
Robert
e3d8dbc484 Merge pull request #2147 from github/robertbrignull/release-indenting
Fix indenting for sub-lists
2023-03-10 11:41:54 +00:00
Robert
6daa780fbe Merge branch 'main' into robertbrignull/release-indenting 2023-03-10 10:48:11 +00:00
Robert
c0f3adc5ff Merge pull request #2156 from github/robertbrignull/remoteResultsItemWithoutLogs
Introduce cancelledRemoteResultsItemWithoutLogs context value
2023-03-10 10:39:26 +00:00
Robert
0ab482a389 Merge branch 'main' into robertbrignull/remoteResultsItemWithoutLogs 2023-03-10 10:29:47 +00:00
Koen Vlaswinkel
103017d717 Merge pull request #2150 from github/koesie10/no-node-modules-copy
Do not copy `node_modules` when packaging
2023-03-10 10:34:00 +01:00
Koen Vlaswinkel
9048dfd251 Merge pull request #2151 from github/koesie10/packaging-error-message
Add check for error message for packaging.test.ts
2023-03-10 10:33:51 +01:00
Koen Vlaswinkel
807069e0c1 Merge pull request #2148 from github/koesie10/commands-package
Add commands package inside src directory
2023-03-10 10:32:32 +01:00
Nora
1fa976757c Merge pull request #2153 from github/nora/remove-unnecessary-activation-events
Remove unnecessary commands from `activationEvents`
2023-03-10 10:23:10 +01:00
Nora
694dcea49a Merge pull request #2154 from github/nora/split-commands-b
Extension Telemetry: Split `viewCfg`,`quickEval`, `openReferencedFile` command
2023-03-10 10:22:33 +01:00
Nora
83d14501fd Merge pull request #2142 from github/nora/split-commands-a
Extension Telemetry: Split `runVariantAnalysis` and `viewAst` command
2023-03-10 10:22:22 +01:00
Koen Vlaswinkel
555d99ca33 Merge branch 'main' into koesie10/commands-package 2023-03-10 10:19:58 +01:00
Koen Vlaswinkel
9dc3df74bb Merge branch 'main' into koesie10/no-node-modules-copy 2023-03-10 10:19:57 +01:00
Koen Vlaswinkel
877d11dbe6 Merge branch 'main' into koesie10/packaging-error-message 2023-03-10 10:19:41 +01:00
Nora
c0c7574891 Split openReferencedFile command 2023-03-10 08:55:55 +00:00
Nora
4fda4f71dd Split quickEval command 2023-03-10 08:55:54 +00:00
Nora
6356149e54 Split viewCfg command 2023-03-10 08:55:54 +00:00
Nora
7f32439786 split viewAst command 2023-03-10 08:55:33 +00:00
Nora
27434862c3 split runVariantAnalysis command 2023-03-10 08:55:33 +00:00
Nora
275c16d5b0 Remove codeQLDatabases.chooseDatabase command 2023-03-10 08:54:36 +00:00
Nora
5586a02b44 Remove not necessary commands from activation event 2023-03-10 08:54:36 +00:00
Andrew Eisenberg
baba68d0df Add back variant analysis tests
This commit adds back the variant analysis tests that were inadvertently
deleted after this commit

82ada54103/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts (L67)

They are not identical to the removed tests. I refactored them so that
there is a single test function invoked three times with different
parameters.
2023-03-09 15:53:13 -08:00
Andrew Eisenberg
790a152f42 Merge branch 'main' into aeisenberg/run-with-all-data-extensions 2023-03-09 09:42:30 -08:00
Andrew Eisenberg
1db2bc048a Merge pull request #2159 from github/github-action/bump-cli
Bump CLI Version to v2.12.4 for integration tests
2023-03-09 08:17:19 -08:00
Charis Kyriakou
d9917cbe8b Fix date of last release on changelog (#2158) 2023-03-09 15:58:24 +00:00
Andrew Eisenberg
2bd9c8a732 Merge pull request #2149 from github/aeisenberg-patch-1
Add security experimental queries
2023-03-09 07:49:27 -08:00
github-actions[bot]
c8ec661dce Bump CLI version from v2.12.3 to v2.12.4 for integration tests 2023-03-09 15:47:48 +00:00
Charis Kyriakou
1158dfdb89 Merge pull request #2155 from github/version/bump-to-v1.8.1
Bump version to v1.8.1
2023-03-09 15:27:30 +00:00
Anders Starcke Henriksen
61974a7664 Register concrete command from extension. 2023-03-09 16:25:11 +01:00
Robert
155b83b540 Introduce remoteResultsItemWithoutLogs context value 2023-03-09 14:35:10 +00:00
github-actions[bot]
bc8d07bc33 Bump version to v1.8.1 2023-03-09 14:22:57 +00:00
Anders Starcke Henriksen
d87911a803 Basic implementation of command manager. 2023-03-09 14:15:27 +01:00
Koen Vlaswinkel
2d5c339e0e Add check for error message for packaging.test.ts
This will add a check to ensure that `showAndLogExceptionWithTelemetry`
is not called when downloading packs. This expectation is placed before
the check for `showAndLogInformationMessage` so that when the test
fails, the error message will be shown.
2023-03-09 11:16:04 +01:00
Koen Vlaswinkel
d151b2f5f9 Do not copy node_modules when packaging
We are no longer including our dependencies in the VSIX package, so we
can tell VSCE that we don't want it to look at dependencies using
`--no-dependencies`. If we do this, VSCE doesn't require the
`node_modules` directory anymore and we can skip that step, which will
make building significantly faster.

I've confirmed that there are no changes between the two options by
building the extension both without and with the change. This is the
diff of the two outputs (using `diff -r`):

```diff
diff --color -r vscode-codeql-1.8.0-dev.2023.3.8.15.10.13/extension/package.json vscode-codeql-old/extension/package.json
7c7
<   "version": "1.8.0-dev.2023.3.8.15.10.13",
---
>   "version": "1.8.0-dev.2023.3.8.15.6.51",
diff --color -r vscode-codeql-1.8.0-dev.2023.3.8.15.10.13/extension.vsixmanifest vscode-codeql-old/extension.vsixmanifest
4c4
< 			<Identity Language="en-US" Id="vscode-codeql" Version="1.8.0-dev.2023.3.8.15.10.13" Publisher="GitHub" />
---
> 			<Identity Language="en-US" Id="vscode-codeql" Version="1.8.0-dev.2023.3.8.15.6.51" Publisher="GitHub" />
```

The only difference is the version number, which is expected.
2023-03-08 16:18:55 +01:00
Andrew Eisenberg
7bc6276115 Update .github/codeql/codeql-config.yml
Co-authored-by: Charis Kyriakou <charisk@users.noreply.github.com>
2023-03-08 07:10:53 -08:00
Andrew Eisenberg
a5021dc4c9 Add security experimental queries 2023-03-08 07:08:30 -08:00
Koen Vlaswinkel
77dd9bff94 Add commands package inside src directory 2023-03-08 15:35:14 +01:00
Charis Kyriakou
56111b39fc Merge pull request #2146 from github/v1.8.0
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.8.0
2023-03-08 13:48:14 +00:00
Robert
0b6d828fa0 Fix indenting for sub-lists 2023-03-08 13:25:38 +00:00
Charis Kyriakou
d22be729be v1.8.0 2023-03-08 13:22:40 +00:00
Charis Kyriakou
9490642522 Merge pull request #2144 from github/charisk/mrva-public-beta
Prepare for MRVA public beta
2023-03-08 12:21:40 +00:00
Charis Kyriakou
643c106fbd Add controller repo 'learn more' link 2023-03-07 16:41:04 +00:00
Charis Kyriakou
9a308f6602 Update CHANGELOG to include MRVA 2023-03-07 16:41:04 +00:00
Charis Kyriakou
582c917541 Update README to include MRVA 2023-03-07 16:41:04 +00:00
Charis Kyriakou
e3625c982f MRVA execution not behind canary 2023-03-07 16:41:04 +00:00
Charis Kyriakou
6f37f176e4 MRVA repositories panel not behind canary 2023-03-07 16:41:04 +00:00
Charis Kyriakou
90b0911ed3 Update release process to lock/unlock the main branch (#2143) 2023-03-07 15:06:43 +00:00
Charis Kyriakou
84506d7340 Merge pull request #2141 from github/charisk/docs-tidy
Tidy up docs
2023-03-07 15:06:03 +00:00
Robert
d8c2562bb1 Move listener registration to webview.tsx 2023-03-07 15:05:49 +00:00
Charis Kyriakou
d480056c68 Move test plan near other docs 2023-03-07 11:40:12 +00:00
Charis Kyriakou
c3d28e395c Move testing docs to separate file 2023-03-07 11:37:00 +00:00
Charis Kyriakou
840cfbf3f6 Move releasing docs to separate doc file 2023-03-07 11:33:46 +00:00
Koen Vlaswinkel
d3466c3a72 Merge pull request #2136 from github/koesie10/upgrade-to-husky-8
Upgrade to Husky 8
2023-03-07 10:16:36 +01:00
Andrew Eisenberg
0ecde78d6e Fix test comments 2023-03-06 18:05:38 -08:00
Andrew Eisenberg
e07208b089 Merge remote-tracking branch 'upstream/main' into aeisenberg/run-with-all-data-extensions 2023-03-06 15:10:19 -08:00
Robert
7176f690f3 Add listeners for unhandled errors to web views 2023-03-06 17:22:06 +00:00
Charis Kyriakou
22aa77ff4c Fix VS Marketplace badge (#2138) 2023-03-06 16:00:49 +00:00
Koen Vlaswinkel
a6f189b144 Upgrade to Husky 8 2023-03-06 11:21:19 +01:00
Alexander Eyers-Taylor
2217d3f21f Give the LanguageClient an easier id to use. (#2135) 2023-03-03 17:26:08 +00:00
Koen Vlaswinkel
9703d10b32 Merge pull request #2122 from github/koesie10/remove-as-unknown-as-quickpickitem
Remove `as unknown as QuickPickItem`
2023-03-03 16:40:21 +01:00
Robert
53492b5202 Merge pull request #2115 from github/robertbrignull/cleanup_new_telemetry
Cleanup config to enabling new telemetry
2023-03-03 14:13:47 +00:00
Robert
082a00e81b Cleanup config to enabling new telemetry 2023-03-03 11:55:12 +00:00
Robert
e9bbf112f3 Merge pull request #2125 from github/robertbrignull/error_listener
Report unhandled errors, but only those that are from our extension
2023-03-03 11:34:31 +00:00
Robert
9386817727 Report unhandled errors from our extension 2023-03-02 17:19:40 +00:00
dependabot[bot]
68ce7c3b53 Bump actions/upload-artifact from 2 to 3 (#2133)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-02 15:03:58 +00:00
Koen Vlaswinkel
a0ba1126cb Merge pull request #2131 from github/koesie10/fix-delete-local-query
Fix error while deleting query history item
2023-03-02 14:08:26 +01:00
Koen Vlaswinkel
d574f3d94c Fix error while deleting query history item
When deleting a query history item and the "next" query is still
running, the `completedQuery` is `undefined`. This commit fixes it by
using optional chaining to ensure that the `completedQuery` is defined
before accessing its `successful` property.
2023-03-02 12:32:45 +01:00
Koen Vlaswinkel
b0cc4c28ed Merge remote-tracking branch 'origin/main' into koesie10/remove-as-unknown-as-quickpickitem 2023-03-02 12:27:31 +01:00
Koen Vlaswinkel
11b63f39b4 Merge pull request #2123 from github/koesie10/remove-unknown-database-item
Remove additional `as unknown as` for database items
2023-03-02 12:23:03 +01:00
Koen Vlaswinkel
216413e5d7 Merge pull request #2124 from github/koesie10/move-tests
Move variant analysis tests out from the CLI integration test suite
2023-03-02 12:22:39 +01:00
Charis Kyriakou
5555fcaded Merge pull request #2118 from github/charisk/variant-analysis-scrubbing
Clean up variant analyses from query history
2023-03-02 09:26:49 +00:00
Koen Vlaswinkel
68fb744eab Merge pull request #2085 from github/koesie10/fix-graphviz-wasm
Fix Graphviz WASM module not loading for graph viewer
2023-03-02 10:00:18 +01:00
Andrew Eisenberg
3de6a110ce Merge pull request #2130 from github/version/bump-to-v1.7.12
Bump version to v1.7.12
2023-03-01 13:06:10 -08:00
github-actions[bot]
dd19ebdfdb Bump version to v1.7.12 2023-03-01 19:07:38 +00:00
Koen Vlaswinkel
304330074d Add some additional safety to allowWasmEval 2023-03-01 17:17:27 +01:00
Koen Vlaswinkel
074229e2a0 Clarify some comments 2023-03-01 17:17:06 +01:00
Koen Vlaswinkel
b679c18b0b Move variant analysis results zip to be next to tests 2023-03-01 14:09:09 +01:00
Koen Vlaswinkel
c3799bdb5a Move variant analysis results manager tests to activated extension suite 2023-03-01 14:07:49 +01:00
Koen Vlaswinkel
daaeb5be3f Move some variant analysis manager test to activated extension suite 2023-03-01 14:03:00 +01:00
Koen Vlaswinkel
2b346b6873 Move variant analysis monitor tests to activated extension suite 2023-03-01 13:54:54 +01:00
Koen Vlaswinkel
1c6ecf4a5c Remove as unknown as FullDatabaseOptions 2023-03-01 13:11:32 +01:00
Koen Vlaswinkel
59cc93f94f Remove as unknown as DatabaseItem 2023-03-01 13:11:32 +01:00
Koen Vlaswinkel
db0fea3af5 Remove as unknown as QuickPickItem
This is definitely not a perfect solution since we're essentially just
moving the place where we're casting. However, because we have manually
made the types similar, this provides some type assurances where there
were none before. This also has the cast in only one place, which makes
it easier to find and fix in the future.
2023-03-01 12:21:14 +01:00
Koen Vlaswinkel
b3d9804842 Only enable graph viewer in canary mode 2023-03-01 09:52:56 +01:00
Andrew Eisenberg
5843c40a37 Remove mention of search path in coment
Co-authored-by: Robert <robertbrignull@github.com>
2023-02-28 08:29:00 -08:00
Koen Vlaswinkel
d3e64539d0 Only allow WASM execution in results view 2023-02-28 15:22:23 +01:00
Charis Kyriakou
06463a25e6 Clean up variant analyses directory 2023-02-28 13:02:37 +00:00
Charis Kyriakou
7b2ef6bf76 Pass both local queries and variant analyses dirs in query history scrubber 2023-02-28 13:02:01 +00:00
Koen Vlaswinkel
bc51e7462b Fix Graphviz WASM module not loading for graph viewer
It seems that when we added the CSP policy to the webview, we did not
take into account that `d3-graphviz` uses `@hpcc-js/wasm` to load
Graphviz as a WASM module. This commit adds `'wasm-unsafe-eval'` to the
CSP policy to allow this.
2023-02-15 15:41:36 +01:00
Andrew Eisenberg
256b806cd4 Remove skipIfTrue
Also, add a comment to the vscode settings file and describe how to use
it.
2023-02-14 14:47:39 -08:00
Andrew Eisenberg
c0c30b48af Merge remote-tracking branch 'upstream/main' into aeisenberg/run-with-all-data-extensions 2023-02-14 18:39:35 +00:00
Andrew Eisenberg
39e6b27676 Make linter happy 2023-02-13 21:11:14 -08:00
Andrew Eisenberg
d8435d113a Fix skipIfTrue
Also, update jsdoc for `resolveQlpacks`.
2023-02-13 14:30:29 -08:00
Andrew Eisenberg
6f435300e8 Add ability to run query with data extensions
- Add a new config that toggles between using all data extensions or
  none.
- If using all data extensions, then before a query evaluation, run a
  `codeql resolve qlpacks` command with the new `--kind` option. This
  will return a list of extension packs in the workspace.
- Pass these packs to the cli before evaluation/
- This will only work with CLI v2.12.3 (not yet released) or later.
- Also include some CLI tests to ensure this works.
2023-02-10 15:51:07 -08:00
152 changed files with 6625 additions and 5827 deletions

View File

@@ -2,6 +2,8 @@ name: "CodeQL config"
queries:
- name: Run standard queries
uses: security-and-quality
- name: Experimental queries
uses: security-experimental
- name: Run custom javascript queries
uses: ./.github/codeql/queries
paths:

View File

@@ -54,7 +54,7 @@ jobs:
echo "ref_name=$REF_NAME" >> "$GITHUB_OUTPUT"
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: vscode-codeql-extension
path: artifacts

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd extensions/ql-vscode && npm run format-staged

4
.husky/pre-push Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd extensions/ql-vscode && ./scripts/forbid-test-only

15
.vscode/settings.json vendored
View File

@@ -42,22 +42,29 @@
"LANG": "en-US",
"TZ": "UTC"
},
// These options are used by the `jestrunner.debug` command.
// They are not used by the `jestrunner.run` command.
// After clicking "debug" over a test, continually invoke the
// "Debug: Attach to Node Process" command until you see a
// process named "Code Helper (Plugin)". Then click "attach".
// This will attach the debugger to the test process.
"jestrunner.debugOptions": {
// Uncomment to debug integration tests
// "attachSimplePort": 9223,
"attachSimplePort": 9223,
"env": {
"LANG": "en-US",
"TZ": "UTC",
// Uncomment to set a custom path to a CodeQL checkout.
// "TEST_CODEQL_PATH": "../codeql",
// "TEST_CODEQL_PATH": "/absolute/path/to/checkout/of/codeql",
// Uncomment to set a custom path to a CodeQL CLI executable.
// This is the CodeQL version that will be used in the tests.
// "CLI_PATH": "/path/to/customg/codeql",
// "CLI_PATH": "/absolute/path/to/custom/codeql",
// Uncomment to debug integration tests
// "VSCODE_WAIT_FOR_DEBUGGER": "true",
"VSCODE_WAIT_FOR_DEBUGGER": "true",
}
},
"terminal.integrated.env.linux": {

View File

@@ -2,7 +2,7 @@
[fork]: https://github.com/github/vscode-codeql/fork
[pr]: https://github.com/github/vscode-codeql/compare
[style]: https://primer.style
[style]: https://github.com/microsoft/vscode-webview-ui-toolkit
[code-of-conduct]: CODE_OF_CONDUCT.md
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
@@ -93,214 +93,7 @@ More information about Storybook can be found inside the **Overview** page once
### Testing
We have several types of tests:
* Unit tests: these live in the `tests/unit-tests/` directory
* View tests: these live in `src/view/variant-analysis/__tests__/`
* VSCode integration tests:
* `test/vscode-tests/activated-extension` tests: These are intended to cover functionality that require the full extension to be activated but don't require the CLI. This suite is not run against multiple versions of the CLI in CI.
* `test/vscode-tests/no-workspace` tests: These are intended to cover functionality around not having a workspace. The extension is not activated in these tests.
* `test/vscode-tests/minimal-workspace` tests: These are intended to cover functionality that need a workspace but don't require the full extension to be activated.
* CLI integration tests: these live in `test/vscode-tests/cli-integration`
* These tests are intended to cover functionality that is related to the integration between the CodeQL CLI and the extension. These tests are run against each supported versions of the CLI in CI.
The CLI integration tests require an instance of the CodeQL CLI to run so they will require some extra setup steps. When adding new tests to our test suite, please be mindful of whether they need to be in the cli-integration folder. If the tests don't depend on the CLI, they are better suited to being a VSCode integration test.
Any test data you're using (sample projects, config files, etc.) must go in a `test/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`.
#### Running the tests
Pre-requisites:
1. Run `npm run build`.
2. You will need to have `npm run watch` running in the background.
##### 1. From the terminal
Then, from the `extensions/ql-vscode` directory, use the appropriate command to run the tests:
* Unit tests: `npm run test:unit`
* View Tests: `npm test:view`
* VSCode integration tests: `npm run test:vscode-integration`
###### CLI integration tests
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
2. Run your test command:
```shell
cd extensions/ql-vscode && npm run test:cli-integration
```
##### 2. From VSCode
Alternatively, you can run the tests inside of VSCode. There are several VSCode launch configurations defined that run the unit and integration tests.
You will need to run tests using a task from inside of VS Code, under the "Run and Debug" view:
* Unit tests: run the _Launch Unit Tests - React_ task
* View Tests: run the _Launch Unit Tests_ task
* VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
###### CLI integration tests
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
2. Set the codeql path in VSCode's launch configuration: open `launch.json` and under the _Launch Integration Tests - With CLI_ section, uncomment the `"${workspaceRoot}/../codeql"` line. If you've cloned the `github/codeql` repo to a different path, replace the value with the correct path.
3. Run the VSCode task from the "Run and Debug" view called _Launch Integration Tests - With CLI_.
#### Running a single test
##### 1. From the terminal
The easiest way to run a single test is to change the `it` of the test to `it.only` and then run the test command with some additional options
to only run tests for this specific file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
```shell
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts
```
You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
```shell
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo"
```
##### 2. From VSCode
Alternatively, you can run a single test inside VSCode. To do so, install the [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) extension. Then,
you will have quicklinks to run a single test from within test files. To run a single unit or integration test, click the "Run" button. Debugging a single test is currently only supported
for unit tests by default. To debug integration tests, open the `.vscode/settings.json` file and uncomment the `jestrunner.debugOptions` lines. This will allow you to debug integration tests.
Please make sure to revert this change before committing; with this setting enabled, it is not possible to debug unit tests.
Without the Jest Runner extension, you can also use the "Launch Selected Unit Test (vscode-codeql)" launch configuration to run a single unit test.
#### Using a mock GitHub API server
Multi-Repo Variant Analyses (MRVA) rely on the GitHub API. In order to make development and testing easy, we have functionality that allows us to intercept requests to the GitHub API and provide mock responses.
##### Using a pre-recorded test scenario
To run a mock MRVA scenario, follow these steps:
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
```json
"codeQL.mockGitHubApiServer": {
"enabled": true
}
```
1. Run the `CodeQL: Mock GitHub API Server: Load Scenario` command from the command pallet, and choose one of the scenarios to load.
1. Execute a normal MRVA. At this point you should see the scenario being played out, rather than an actual MRVA running.
1. Once you're done, you can stop using the mock scenario with `CodeQL: Mock GitHub API Server: Unload Scenario`
If you want to replay the same scenario you should unload and reload it so requests are replayed from the start.
##### Recording a new test scenario
To record a new mock MRVA scenario, follow these steps:
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
```json
"codeQL.mockGitHubApiServer": {
"enabled": true
}
```
1. Run the `CodeQL: Mock GitHub API Server: Start Scenario Recording` VS Code command from the command pallet.
1. Execute a normal MRVA.
1. Once what you wanted to record is done (e.g. the MRVA has finished), then run the `CodeQL: Mock GitHub API Server: Save Scenario` command from the command pallet.
1. The scenario should then be available for replaying.
If you want to cancel recording, run the `CodeQL: Mock GitHub API Server: Cancel Scenario Recording` command.
Once the scenario has been recorded, it's often useful to remove some of the requests to speed up the replay, particularly ones that fetch the variant analysis status. Once some of the request files have manually been removed, the [fix-scenario-file-numbering script](./extensions/ql-vscode/scripts/fix-scenario-file-numbering.ts) can be used to update the number of the files. See the script file for details on how to use.
#### Scenario data location
Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's possible to configure the location, by setting the `codeQL.mockGitHubApiServer.scenariosPath` configuration property in the VS Code user settings.
## Releasing (write access required)
1. Go through [our test plan](/extensions/ql-vscode/docs/test-plan.md) to ensure that the extension is working as expected.
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
* Go through all recent PRs and make sure they are properly accounted for.
* Make sure all changelog entries have links back to their PR(s) if appropriate.
* For picking the new version number, we default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
* Making substantial new features available to all users. This can include lifting a feature flag.
* Breakage in compatibility with recent versions of the CLI.
* Minimum required version of VS Code is increased.
* New telemetry events are added.
* Deprecation or removal of commands.
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
* `.github/workflows/main.yml` - all the "node-version: <version>" settings
* `.github/workflows/release.yml` - the "node-version: <version>" setting
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
1. Create a PR for this release:
* This PR will contain any missing bits from steps 1 and 2. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
* Create a new branch for the release named after the new version. For example: `v1.3.6`
* Create a new commit with a message the same as the branch name.
* Create a PR for this branch.
* Wait for the PR to be merged into `main`
1. Switch to `main` and add a new tag on the `main` branch with your new version (named after the release), e.g.
```bash
git checkout main
git tag v1.3.6
```
If you've accidentally created a badly named tag, you can delete it via
```bash
git tag -d badly-named-tag
```
1. Push the new tag up:
a. If you're using a fork of the repo:
```bash
git push upstream refs/tags/v1.3.6
```
b. If you're working straight in this repo:
```bash
git push origin refs/tags/v1.3.6
```
This will trigger [a release build](https://github.com/github/vscode-codeql/releases) on Actions.
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
* DO NOT approve the "publish" stages of the workflow yet.
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
or look at the source if there's any doubt the right code is being shipped.
1. Install the `.vsix` file into your vscode IDE and ensure the extension can load properly. Run a single command (like run query, or add database).
1. Go to the actions tab of the vscode-codeql repository and select the [Release workflow](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease).
- If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
1. Approve the deployments of the correct Release workflow. This will automatically publish to Open VSX and VS Code Marketplace.
1. Go to the draft GitHub release in [the releases tab of the repository](https://github.com/github/vscode-codeql/releases), click 'Edit', add some summary description, and publish it.
1. Confirm the new release is marked as the latest release at <https://github.com/github/vscode-codeql/releases>.
1. If documentation changes need to be published, notify documentation team that release has been made.
1. Review and merge the version bump PR that is automatically created by Actions.
## Secrets and authentication for publishing
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token. The VS Code marketplace token expires yearly.
To regenerate the Open VSX token:
1. Log in to the [user settings page on Open VSX](https://open-vsx.org/user-settings/namespaces).
1. Make sure you are a member of the GitHub namespace.
1. Go to the [Access Tokens](https://open-vsx.org/user-settings/tokens) page and generate a new token.
1. Update the secret in the `publish-open-vsx` environment in the project settings.
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 90 days and must be regenerated.
Information about testing can be found [here](./docs/testing.md).
## Resources

View File

@@ -7,7 +7,7 @@ The extension is released. You can download it from the [Visual Studio Marketpla
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md).
[![CI status badge](https://github.com/github/vscode-codeql/workflows/Build%20Extension/badge.svg)](https://github.com/github/vscode-codeql/actions?query=workflow%3A%22Build+Extension%22+branch%3Amaster)
[![VS Marketplace badge](https://vsmarketplacebadge.apphb.com/version/github.vscode-codeql.svg)](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
[![VS Marketplace badge](https://vsmarketplacebadges.dev/version/github.vscode-codeql.svg)](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
## Features
@@ -15,6 +15,7 @@ To see what has changed in the last few versions of the extension, see the [Chan
* Shows the flow of data through the results of path queries, which is essential for triaging security results.
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/github/codeql).
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.
* Supports you running CodeQL queries against thousands of repositories on GitHub using multi-repository variant analysis.
## Project goals and scope

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

89
docs/releasing.md Normal file
View File

@@ -0,0 +1,89 @@
# Releasing (write access required)
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
* Go through all recent PRs and make sure they are properly accounted for.
* Make sure all changelog entries have links back to their PR(s) if appropriate.
* For picking the new version number, we default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
* Making substantial new features available to all users. This can include lifting a feature flag.
* Breakage in compatibility with recent versions of the CLI.
* Minimum required version of VS Code is increased.
* New telemetry events are added.
* Deprecation or removal of commands.
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
* `.github/workflows/main.yml` - all the "node-version: <version>" settings
* `.github/workflows/release.yml` - the "node-version: <version>" setting
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
1. Create a PR for this release:
* This PR will contain any missing bits from steps 1, 2 and 3. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
* Create a new branch for the release named after the new version. For example: `v1.3.6`
* Create a new commit with a message the same as the branch name.
* Create a PR for this branch.
* Wait for the PR to be merged into `main`
1. Switch to `main` branch and pull latest changes
1. Lock the `main` branch.
* Go to the [branch protection rules for the `main` branch](https://github.com/github/vscode-codeql/settings/branch_protection_rules/16447115)
* Select "Lock branch"
* Click "Save changes"
1. Ensure that no PRs have been merged since the release PR that you merged. If there were, you might need to unlock `main` temporarily and update the CHANGELOG again.
1. Build the extension `npm run build` and install it on your VS Code using "Install from VSIX".
1. Go through [our test plan](./test-plan.md) to ensure that the extension is working as expected.
1. Switch to `main` and add a new tag on the `main` branch with your new version (named after the release), e.g.
```bash
git checkout main
git tag v1.3.6
```
If you've accidentally created a badly named tag, you can delete it via
```bash
git tag -d badly-named-tag
```
1. Unlock the main branch
* Go to the [branch protection rules for the `main` branch](https://github.com/github/vscode-codeql/settings/branch_protection_rules/16447115)
* Deselect "Lock branch"
* Click "Save changes"
1. Push the new tag up:
a. If you're using a fork of the repo:
```bash
git push upstream refs/tags/v1.3.6
```
b. If you're working straight in this repo:
```bash
git push origin refs/tags/v1.3.6
```
This will trigger [a release build](https://github.com/github/vscode-codeql/releases) on Actions.
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
* DO NOT approve the "publish" stages of the workflow yet.
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
or look at the source if there's any doubt the right code is being shipped.
1. Install the `.vsix` file into your vscode IDE and ensure the extension can load properly. Run a single command (like run query, or add database).
1. Go to the actions tab of the vscode-codeql repository and select the [Release workflow](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease).
- If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
1. Approve the deployments of the correct Release workflow. This will automatically publish to Open VSX and VS Code Marketplace.
1. Go to the draft GitHub release in [the releases tab of the repository](https://github.com/github/vscode-codeql/releases), click 'Edit', add some summary description, and publish it.
1. Confirm the new release is marked as the latest release at <https://github.com/github/vscode-codeql/releases>.
1. If documentation changes need to be published, notify documentation team that release has been made.
1. Review and merge the version bump PR that is automatically created by Actions.
## Secrets and authentication for publishing
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token. The VS Code marketplace token expires yearly.
To regenerate the Open VSX token:
1. Log in to the [user settings page on Open VSX](https://open-vsx.org/user-settings/namespaces).
1. Make sure you are a member of the GitHub namespace.
1. Go to the [Access Tokens](https://open-vsx.org/user-settings/tokens) page and generate a new token.
1. Update the secret in the `publish-open-vsx` environment in the project settings.
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 90 days and must be regenerated.

View File

@@ -18,10 +18,6 @@ choose to go through some of the Optional Test Cases.
## Required Test Cases
### Pre-requisites
- Flip the `codeQL.canary` flag. This will enable MRVA in the extension.
### Test Case 1: MRVA - Running a problem path query and viewing results
1. Open the [UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql).

131
docs/testing.md Normal file
View File

@@ -0,0 +1,131 @@
# Testing
We have several types of tests:
* Unit tests: these live in the `tests/unit-tests/` directory
* View tests: these live in `src/view/variant-analysis/__tests__/`
* VSCode integration tests:
* `test/vscode-tests/activated-extension` tests: These are intended to cover functionality that require the full extension to be activated but don't require the CLI. This suite is not run against multiple versions of the CLI in CI.
* `test/vscode-tests/no-workspace` tests: These are intended to cover functionality around not having a workspace. The extension is not activated in these tests.
* `test/vscode-tests/minimal-workspace` tests: These are intended to cover functionality that need a workspace but don't require the full extension to be activated.
* CLI integration tests: these live in `test/vscode-tests/cli-integration`
* These tests are intended to cover functionality that is related to the integration between the CodeQL CLI and the extension. These tests are run against each supported versions of the CLI in CI.
The CLI integration tests require an instance of the CodeQL CLI to run so they will require some extra setup steps. When adding new tests to our test suite, please be mindful of whether they need to be in the cli-integration folder. If the tests don't depend on the CLI, they are better suited to being a VSCode integration test.
Any test data you're using (sample projects, config files, etc.) must go in a `test/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`.
## Running the tests
Pre-requisites:
1. Run `npm run build`.
2. You will need to have `npm run watch` running in the background.
### 1. From the terminal
Then, from the `extensions/ql-vscode` directory, use the appropriate command to run the tests:
* Unit tests: `npm run test:unit`
* View Tests: `npm test:view`
* VSCode integration tests: `npm run test:vscode-integration`
#### CLI integration tests
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
2. Run your test command:
```shell
cd extensions/ql-vscode && npm run test:cli-integration
```
### 2. From VSCode
Alternatively, you can run the tests inside of VSCode. There are several VSCode launch configurations defined that run the unit and integration tests.
You will need to run tests using a task from inside of VS Code, under the "Run and Debug" view:
* Unit tests: run the _Launch Unit Tests - React_ task
* View Tests: run the _Launch Unit Tests_ task
* VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
#### CLI integration tests
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
2. Set the codeql path in VSCode's launch configuration: open `launch.json` and under the _Launch Integration Tests - With CLI_ section, uncomment the `"${workspaceRoot}/../codeql"` line. If you've cloned the `github/codeql` repo to a different path, replace the value with the correct path.
3. Run the VSCode task from the "Run and Debug" view called _Launch Integration Tests - With CLI_.
## Running a single test
### 1. From the terminal
The easiest way to run a single test is to change the `it` of the test to `it.only` and then run the test command with some additional options
to only run tests for this specific file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
```shell
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts
```
You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
```shell
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo"
```
### 2. From VSCode
Alternatively, you can run a single test inside VSCode. To do so, install the [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) extension. Then,
you will have quicklinks to run a single test from within test files. To run a single unit or integration test, click the "Run" button. Debugging a single test is currently only supported
for unit tests by default. To debug integration tests, open the `.vscode/settings.json` file and uncomment the `jestrunner.debugOptions` lines. This will allow you to debug integration tests.
Please make sure to revert this change before committing; with this setting enabled, it is not possible to debug unit tests.
Without the Jest Runner extension, you can also use the "Launch Selected Unit Test (vscode-codeql)" launch configuration to run a single unit test.
## Using a mock GitHub API server
Multi-Repo Variant Analyses (MRVA) rely on the GitHub API. In order to make development and testing easy, we have functionality that allows us to intercept requests to the GitHub API and provide mock responses.
### Using a pre-recorded test scenario
To run a mock MRVA scenario, follow these steps:
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
```json
"codeQL.mockGitHubApiServer": {
"enabled": true
}
```
1. Run the `CodeQL: Mock GitHub API Server: Load Scenario` command from the command pallet, and choose one of the scenarios to load.
1. Execute a normal MRVA. At this point you should see the scenario being played out, rather than an actual MRVA running.
1. Once you're done, you can stop using the mock scenario with `CodeQL: Mock GitHub API Server: Unload Scenario`
If you want to replay the same scenario you should unload and reload it so requests are replayed from the start.
### Recording a new test scenario
To record a new mock MRVA scenario, follow these steps:
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
```json
"codeQL.mockGitHubApiServer": {
"enabled": true
}
```
1. Run the `CodeQL: Mock GitHub API Server: Start Scenario Recording` VS Code command from the command pallet.
1. Execute a normal MRVA.
1. Once what you wanted to record is done (e.g. the MRVA has finished), then run the `CodeQL: Mock GitHub API Server: Save Scenario` command from the command pallet.
1. The scenario should then be available for replaying.
If you want to cancel recording, run the `CodeQL: Mock GitHub API Server: Cancel Scenario Recording` command.
Once the scenario has been recorded, it's often useful to remove some of the requests to speed up the replay, particularly ones that fetch the variant analysis status. Once some of the request files have manually been removed, the [fix-scenario-file-numbering script](../extensions/ql-vscode/scripts/fix-scenario-file-numbering.ts) can be used to update the number of the files. See the script file for details on how to use.
### Scenario data location
Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's possible to configure the location, by setting the `codeQL.mockGitHubApiServer.scenariosPath` configuration property in the VS Code user settings.

View File

@@ -1,5 +1,15 @@
# CodeQL for Visual Studio Code: Changelog
## 1.8.1 - 23 March 2023
- Show data flow paths of a variant analysis in a new tab. [#2172](https://github.com/github/vscode-codeql/pull/2172) & [#2182](https://github.com/github/vscode-codeql/pull/2182)
- Show labels of entities in exported CSV results. [#2170](https://github.com/github/vscode-codeql/pull/2170)
## 1.8.0 - 9 March 2023
- Send telemetry about unhandled errors happening within the extension. [#2125](https://github.com/github/vscode-codeql/pull/2125)
- Enable multi-repository variant analysis. [#2144](https://github.com/github/vscode-codeql/pull/2144)
## 1.7.11 - 1 March 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)

View File

@@ -93,12 +93,6 @@ export async function deployPackage(
);
await copyPackage(sourcePath, distPath);
// This is necessary for vsce to know the dependencies
await copyDirectory(
resolve(sourcePath, "node_modules"),
resolve(distPath, "node_modules"),
);
return {
distPath,
name: packageJson.name,

View File

@@ -17,6 +17,7 @@ export async function packageExtension(): Promise<void> {
"..",
`${deployedPackage.name}-${deployedPackage.version}.vsix`,
),
"--no-dependencies",
];
const proc = spawn(resolve(__dirname, "../node_modules/.bin/vsce"), args, {
cwd: deployedPackage.distPath,

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.7.11",
"version": "1.8.1",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -44,11 +44,6 @@
"onView:test-explorer",
"onCommand:codeQL.checkForUpdatesToCLI",
"onCommand:codeQL.authenticateToGitHub",
"onCommand:codeQLDatabases.chooseDatabaseFolder",
"onCommand:codeQLDatabases.chooseDatabaseArchive",
"onCommand:codeQLDatabases.chooseDatabaseInternet",
"onCommand:codeQLDatabases.chooseDatabaseGithub",
"onCommand:codeQL.setCurrentDatabase",
"onCommand:codeQL.viewAst",
"onCommand:codeQL.viewCfg",
"onCommand:codeQL.openReferencedFile",
@@ -57,20 +52,11 @@
"onCommand:codeQL.chooseDatabaseArchive",
"onCommand:codeQL.chooseDatabaseInternet",
"onCommand:codeQL.chooseDatabaseGithub",
"onCommand:codeQLDatabases.chooseDatabase",
"onCommand:codeQLDatabases.setCurrentDatabase",
"onCommand:codeQLVariantAnalysisRepositories.openConfigFile",
"onCommand:codeQLVariantAnalysisRepositories.addNewDatabase",
"onCommand:codeQLVariantAnalysisRepositories.addNewList",
"onCommand:codeQLVariantAnalysisRepositories.setSelectedItem",
"onCommand:codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
"onCommand:codeQLVariantAnalysisRepositories.renameItemContextMenu",
"onCommand:codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
"onCommand:codeQLVariantAnalysisRepositories.removeItemContextMenu",
"onCommand:codeQL.quickQuery",
"onCommand:codeQL.restartQueryServer",
"onWebviewPanel:resultsView",
"onWebviewPanel:codeQL.variantAnalysis",
"onWebviewPanel:codeQL.dataFlowPaths",
"onFileSystem:codeql-zip-archive"
],
"main": "./out/extension",
@@ -239,6 +225,19 @@
"default": true,
"description": "Enable the 'Quick Evaluation' CodeLens."
},
"codeQL.runningQueries.useExtensionPacks": {
"type": "string",
"default": "none",
"enum": [
"none",
"all"
],
"enumDescriptions": [
"Do not use extension packs.",
"Use all extension packs found in the workspace."
],
"description": "Choose whether or not to run queries using extension packs. Requires CodeQL CLI v2.12.3 or later."
},
"codeQL.resultsDisplay.pageSize": {
"type": "integer",
"default": 200,
@@ -322,6 +321,10 @@
"command": "codeQL.runVariantAnalysis",
"title": "CodeQL: Run Variant Analysis"
},
{
"command": "codeQL.runVariantAnalysisContextEditor",
"title": "CodeQL: Run Variant Analysis"
},
{
"command": "codeQL.exportSelectedVariantAnalysisResults",
"title": "CodeQL: Export Variant Analysis Results"
@@ -334,10 +337,22 @@
"command": "codeQL.quickEval",
"title": "CodeQL: Quick Evaluation"
},
{
"command": "codeQL.quickEvalContextEditor",
"title": "CodeQL: Quick Evaluation"
},
{
"command": "codeQL.openReferencedFile",
"title": "CodeQL: Open Referenced File"
},
{
"command": "codeQL.openReferencedFileContextEditor",
"title": "CodeQL: Open Referenced File"
},
{
"command": "codeQL.openReferencedFileContextExplorer",
"title": "CodeQL: Open Referenced File"
},
{
"command": "codeQL.previewQueryHelp",
"title": "CodeQL: Preview Query Help"
@@ -433,10 +448,26 @@
"command": "codeQL.viewAst",
"title": "CodeQL: View AST"
},
{
"command": "codeQL.viewAstContextExplorer",
"title": "CodeQL: View AST"
},
{
"command": "codeQL.viewAstContextEditor",
"title": "CodeQL: View AST"
},
{
"command": "codeQL.viewCfg",
"title": "CodeQL: View CFG"
},
{
"command": "codeQL.viewCfgContextExplorer",
"title": "CodeQL: View CFG"
},
{
"command": "codeQL.viewCfgContextEditor",
"title": "CodeQL: View CFG"
},
{
"command": "codeQL.upgradeCurrentDatabase",
"title": "CodeQL: Upgrade Current Database"
@@ -514,7 +545,12 @@
"title": "CodeQL: Check for CLI Updates"
},
{
"command": "codeQLQueryHistory.openQuery",
"command": "codeQLQueryHistory.openQueryTitleMenu",
"title": "View Query",
"icon": "$(edit)"
},
{
"command": "codeQLQueryHistory.openQueryContextMenu",
"title": "View Query",
"icon": "$(edit)"
},
@@ -524,7 +560,17 @@
"icon": "$(preview)"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
"title": "Delete",
"icon": "$(trash)"
},
{
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
"title": "Delete",
"icon": "$(trash)"
},
{
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
"title": "Delete",
"icon": "$(trash)"
},
@@ -711,7 +757,7 @@
"group": "navigation"
},
{
"command": "codeQLQueryHistory.openQuery",
"command": "codeQLQueryHistory.openQueryTitleMenu",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
@@ -721,7 +767,7 @@
"group": "navigation"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
@@ -818,19 +864,19 @@
"group": "inline"
},
{
"command": "codeQLQueryHistory.openQuery",
"command": "codeQLQueryHistory.openQueryContextMenu",
"group": "2_queryHistory@0",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
"group": "7_queryHistory@0",
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
"group": "inline",
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
},
{
"command": "codeQLQueryHistory.renameItem",
@@ -930,12 +976,12 @@
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
},
{
"command": "codeQL.viewAst",
"command": "codeQL.viewAstContextExplorer",
"group": "9_qlCommands",
"when": "resourceScheme == codeql-zip-archive && !explorerResourceIsFolder && !listMultiSelection"
},
{
"command": "codeQL.viewCfg",
"command": "codeQL.viewCfgContextExplorer",
"group": "9_qlCommands",
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
},
@@ -945,7 +991,7 @@
"when": "resourceScheme != codeql-zip-archive"
},
{
"command": "codeQL.openReferencedFile",
"command": "codeQL.openReferencedFileContextExplorer",
"group": "9_qlCommands",
"when": "resourceExtname == .qlref"
},
@@ -978,11 +1024,11 @@
},
{
"command": "codeQL.runVariantAnalysis",
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
"when": "editorLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.exportSelectedVariantAnalysisResults",
"when": "config.codeQL.canary"
"command": "codeQL.runVariantAnalysisContextEditor",
"when": "false"
},
{
"command": "codeQL.runQueries",
@@ -992,10 +1038,22 @@
"command": "codeQL.quickEval",
"when": "editorLangId == ql"
},
{
"command": "codeQL.quickEvalContextEditor",
"when": "false"
},
{
"command": "codeQL.openReferencedFile",
"when": "resourceExtname == .qlref"
},
{
"command": "codeQL.openReferencedFileContextEditor",
"when": "false"
},
{
"command": "codeQL.openReferencedFileContextExplorer",
"when": "false"
},
{
"command": "codeQL.previewQueryHelp",
"when": "resourceExtname == .qhelp && isWorkspaceTrusted"
@@ -1008,10 +1066,26 @@
"command": "codeQL.viewAst",
"when": "resourceScheme == codeql-zip-archive"
},
{
"command": "codeQL.viewAstContextEditor",
"when": "false"
},
{
"command": "codeQL.viewAstContextExplorer",
"when": "false"
},
{
"command": "codeQL.viewCfg",
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
},
{
"command": "codeQL.viewCfgContextExplorer",
"when": "false"
},
{
"command": "codeQL.viewCfgContextEditor",
"when": "false"
},
{
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
"when": "false"
@@ -1097,11 +1171,23 @@
"when": "false"
},
{
"command": "codeQLQueryHistory.openQuery",
"command": "codeQLQueryHistory.openQueryTitleMenu",
"when": "false"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"command": "codeQLQueryHistory.openQueryContextMenu",
"when": "false"
},
{
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
"when": "false"
},
{
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
"when": "false"
},
{
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
"when": "false"
},
{
@@ -1235,23 +1321,23 @@
"when": "editorLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.runVariantAnalysis",
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
"command": "codeQL.runVariantAnalysisContextEditor",
"when": "editorLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.viewAst",
"command": "codeQL.viewAstContextEditor",
"when": "resourceScheme == codeql-zip-archive"
},
{
"command": "codeQL.viewCfg",
"command": "codeQL.viewCfgContextEditor",
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
},
{
"command": "codeQL.quickEval",
"command": "codeQL.quickEvalContextEditor",
"when": "editorLangId == ql"
},
{
"command": "codeQL.openReferencedFile",
"command": "codeQL.openReferencedFileContextEditor",
"when": "resourceExtname == .qlref"
},
{
@@ -1281,8 +1367,7 @@
},
{
"id": "codeQLVariantAnalysisRepositories",
"name": "Variant Analysis Repositories",
"when": "config.codeQL.canary"
"name": "Variant Analysis Repositories"
},
{
"id": "codeQLQueryHistory",
@@ -1318,7 +1403,7 @@
},
{
"view": "codeQLVariantAnalysisRepositories",
"contents": "Set up a controller repository to start using variant analysis.\n[Set up controller repository](command:codeQLVariantAnalysisRepositories.setupControllerRepository)",
"contents": "Set up a controller repository to start using variant analysis. [Learn more](https://codeql.github.com/docs/codeql-for-visual-studio-code/running-codeql-queries-at-scale-with-mrva#controller-repository) about controller repositories. \n[Set up controller repository](command:codeQLVariantAnalysisRepositories.setupControllerRepository)",
"when": "!config.codeQL.variantAnalysis.controllerRepo"
}
]
@@ -1342,13 +1427,12 @@
"build-storybook": "build-storybook",
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
"postinstall": "patch-package"
"postinstall": "patch-package",
"prepare": "cd ../.. && husky install"
},
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/rest": "^19.0.4",
"@primer/octicons-react": "^17.6.0",
"@primer/react": "^35.0.0",
"@vscode/codicons": "^0.0.31",
"@vscode/webview-ui-toolkit": "^1.0.1",
"ajv": "^8.11.0",
@@ -1358,7 +1442,7 @@
"d3": "^7.6.1",
"d3-graphviz": "^5.0.2",
"fs-extra": "^10.0.1",
"glob-promise": "^4.2.2",
"glob-promise": "^6.0.2",
"immutable": "^4.0.0",
"js-yaml": "^4.1.0",
"minimist": "~1.2.6",
@@ -1426,6 +1510,7 @@
"@types/semver": "~7.2.0",
"@types/stream-chain": "~2.0.1",
"@types/stream-json": "~1.7.1",
"@types/styled-components": "^5.1.11",
"@types/tar-stream": "^2.2.2",
"@types/through2": "^2.0.36",
"@types/tmp": "^0.1.0",
@@ -1440,7 +1525,6 @@
"@vscode/vsce": "^2.15.0",
"ansi-colors": "^4.1.1",
"applicationinsights": "^2.3.5",
"babel-loader": "^8.2.5",
"cross-env": "^7.0.3",
"css-loader": "~3.1.0",
"del": "^6.0.0",
@@ -1461,16 +1545,16 @@
"gulp-replace": "^1.1.3",
"gulp-sourcemaps": "^3.0.0",
"gulp-typescript": "^5.0.1",
"husky": "~4.3.8",
"husky": "^8.0.0",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"jest-runner-vscode": "^3.0.1",
"lint-staged": "~10.2.2",
"lint-staged": "~13.2.0",
"mini-css-extract-plugin": "^2.6.1",
"npm-run-all": "^4.1.5",
"patch-package": "^6.5.0",
"prettier": "^2.7.1",
"tar-stream": "^2.2.0",
"tar-stream": "^3.0.0",
"through2": "^4.0.2",
"ts-jest": "^29.0.1",
"ts-json-schema-generator": "^1.1.2",
@@ -1478,14 +1562,8 @@
"ts-node": "^10.7.0",
"ts-protoc-gen": "^0.9.0",
"typescript": "^4.5.5",
"webpack": "^5.62.2",
"webpack-cli": "^4.6.0"
},
"husky": {
"hooks": {
"pre-commit": "npm run format-staged",
"pre-push": "scripts/forbid-test-only"
}
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1"
},
"lint-staged": {
"./**/*.{json,css,scss}": [

View File

@@ -24,6 +24,7 @@ export type WebviewPanelConfig = {
view: WebviewView;
preserveFocus?: boolean;
additionalOptions?: WebviewPanelOptions & WebviewOptions;
allowWasmEval?: boolean;
};
export abstract class AbstractWebview<
@@ -116,6 +117,7 @@ export abstract class AbstractWebview<
config.view,
{
allowInlineStyles: true,
allowWasmEval: !!config.allowWasmEval,
},
);
this.push(

View File

@@ -0,0 +1,90 @@
import { Uri, window } from "vscode";
import { withProgress } from "./progress";
import { AstViewer } from "./astViewer";
import {
TemplatePrintAstProvider,
TemplatePrintCfgProvider,
} from "./contextual/templateProvider";
import { compileAndRunQuery } from "./local-queries";
import { QueryRunner } from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { AstCfgCommands } from "./common/commands";
type AstCfgOptions = {
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
astViewer: AstViewer;
astTemplateProvider: TemplatePrintAstProvider;
cfgTemplateProvider: TemplatePrintCfgProvider;
};
export function getAstCfgCommands({
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
astViewer,
astTemplateProvider,
cfgTemplateProvider,
}: AstCfgOptions): AstCfgCommands {
const viewAst = async (selectedFile: Uri) =>
withProgress(
async (progress, token) => {
const ast = await astTemplateProvider.provideAst(
progress,
token,
selectedFile ?? window.activeTextEditor?.document.uri,
);
if (ast) {
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
}
},
{
cancellable: true,
title: "Calculate AST",
},
);
const viewCfg = async () =>
withProgress(
async (progress, token) => {
const res = await cfgTemplateProvider.provideCfgUri(
window.activeTextEditor?.document,
);
if (res) {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
res[0],
progress,
token,
undefined,
);
}
},
{
title: "Calculating Control Flow Graph",
cancellable: true,
},
);
return {
"codeQL.viewAst": viewAst,
"codeQL.viewAstContextExplorer": viewAst,
"codeQL.viewAstContextEditor": viewAst,
"codeQL.viewCfg": viewCfg,
"codeQL.viewCfgContextExplorer": viewCfg,
"codeQL.viewCfgContextEditor": viewCfg,
};
}

View File

@@ -23,11 +23,11 @@ import {
isWholeFileLoc,
isLineColumnLoc,
} from "./pure/bqrs-utils";
import { commandRunner } from "./commandRunner";
import { DisposableObject } from "./pure/disposable-object";
import { showAndLogExceptionWithTelemetry } from "./helpers";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { redactableError } from "./pure/errors";
import { AstViewerCommands } from "./common/commands";
export interface AstItem {
id: BqrsId;
@@ -55,15 +55,6 @@ class AstViewerDataProvider
readonly onDidChangeTreeData: Event<AstItem | undefined> =
this._onDidChangeTreeData.event;
constructor() {
super();
this.push(
commandRunner("codeQLAstViewer.gotoCode", async (item: AstItem) => {
await showLocation(item.fileLocation);
}),
);
}
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
@@ -126,16 +117,20 @@ export class AstViewer extends DisposableObject {
this.push(this.treeView);
this.push(this.treeDataProvider);
this.push(
commandRunner("codeQLAstViewer.clear", async () => {
this.clear();
}),
);
this.push(
window.onDidChangeTextEditorSelection(this.updateTreeSelection, this),
);
}
getCommands(): AstViewerCommands {
return {
"codeQLAstViewer.clear": async () => this.clear(),
"codeQLAstViewer.gotoCode": async (item: AstItem) => {
await showLocation(item.fileLocation);
},
};
}
updateRoots(roots: AstItem[], db: DatabaseItem, fileUri: Uri) {
this.treeDataProvider.roots = roots;
this.treeDataProvider.db = db;

View File

@@ -1163,24 +1163,32 @@ export class CodeQLCliServer implements Disposable {
/**
* Gets information about available qlpacks
* @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`.
* @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined,
* the default CLI search path is used.
* @param additionalPacks A list of directories to search for qlpacks.
* @param extensionPacksOnly Whether to only search for extension packs. If true, only extension packs will
* be returned. If false, all packs will be returned.
* @returns A dictionary mapping qlpack name to the directory it comes from
*/
resolveQlpacks(
async resolveQlpacks(
additionalPacks: string[],
searchPath?: string[],
extensionPacksOnly = false,
): Promise<QlpacksInfo> {
const args = this.getAdditionalPacksArg(additionalPacks);
if (searchPath?.length) {
args.push("--search-path", join(...searchPath));
if (extensionPacksOnly) {
if (!(await this.cliConstraints.supportsQlpacksKind())) {
void this.logger.log(
"Warning: Running with extension packs is only supported by CodeQL CLI v2.12.3 or later.",
);
return {};
}
args.push("--kind", "extension", "--no-recursive");
}
return this.runJsonCodeQlCliCommand<QlpacksInfo>(
["resolve", "qlpacks"],
args,
"Resolving qlpack information",
`Resolving qlpack information${
extensionPacksOnly ? " (extension packs only)" : ""
}`,
);
}
@@ -1276,11 +1284,25 @@ export class CodeQLCliServer implements Disposable {
);
}
async packInstall(dir: string, forceUpdate = false) {
async packInstall(
dir: string,
{ forceUpdate = false, workspaceFolders = [] as string[] } = {},
) {
const args = [dir];
if (forceUpdate) {
args.push("--mode", "update");
}
if (workspaceFolders?.length > 0) {
if (await this.cliConstraints.supportsAdditionalPacksInstall()) {
args.push(
// Allow prerelease packs from the ql submodule.
"--allow-prerelease",
// Allow the use of --additional-packs argument without issueing a warning
"--no-strict-mode",
...this.getAdditionalPacksArg(workspaceFolders),
);
}
}
return this.runJsonCodeQlCliCommandWithAuthentication(
["pack", "install"],
args,
@@ -1380,6 +1402,17 @@ export class CodeQLCliServer implements Disposable {
private getAdditionalPacksArg(paths: string[]): string[] {
return paths.length ? ["--additional-packs", paths.join(delimiter)] : [];
}
public async useExtensionPacks(): Promise<boolean> {
return (
this.cliConfig.useExtensionPacks &&
(await this.cliConstraints.supportsQlpacksKind())
);
}
public async setUseExtensionPacks(useExtensionPacks: boolean) {
await this.cliConfig.setUseExtensionPacks(useExtensionPacks);
}
}
/**
@@ -1668,6 +1701,18 @@ export class CliVersionConstraint {
*/
public static CLI_VERSION_WITH_WORKSPACE_RFERENCES = new SemVer("2.11.3");
/**
* CLI version that supports the `--kind` option for the `resolve qlpacks` command.
*/
public static CLI_VERSION_WITH_QLPACKS_KIND = new SemVer("2.12.3");
/**
* CLI version that supports the `--additional-packs` option for the `pack install` command.
*/
public static CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL = new SemVer(
"2.12.4",
);
constructor(private readonly cli: CodeQLCliServer) {
/**/
}
@@ -1725,4 +1770,16 @@ export class CliVersionConstraint {
CliVersionConstraint.CLI_VERSION_WITH_WORKSPACE_RFERENCES,
);
}
async supportsQlpacksKind() {
return this.isVersionAtLeast(
CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND,
);
}
async supportsAdditionalPacksInstall() {
return this.isVersionAtLeast(
CliVersionConstraint.CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL,
);
}
}

View File

@@ -1,11 +1,4 @@
import {
CancellationToken,
ProgressOptions,
window as Window,
commands,
Disposable,
ProgressLocation,
} from "vscode";
import { commands, Disposable } from "vscode";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
@@ -14,54 +7,7 @@ import { extLogger } from "./common";
import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure";
import { telemetryListener } from "./telemetry";
import { redactableError } from "./pure/errors";
export class UserCancellationException extends Error {
/**
* @param message The error message
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
*/
constructor(message?: string, public readonly silent = false) {
super(message);
}
}
export interface ProgressUpdate {
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
export type ProgressCallback = (p: ProgressUpdate) => void;
/**
* A task that handles command invocations from `commandRunner`
* and includes a progress monitor.
*
*
* Arguments passed to the command handler are passed along,
* untouched to this `ProgressTask` instance.
*
* @param progress a progress handler function. Call this
* function with a `ProgressUpdate` instance in order to
* denote some progress being achieved on this task.
* @param token a cencellation token
* @param args arguments passed to this task passed on from
* `commands.registerCommand`.
*/
export type ProgressTask<R> = (
progress: ProgressCallback,
token: CancellationToken,
...args: any[]
) => Thenable<R>;
import { UserCancellationException } from "./progress";
/**
* A task that handles command invocations from `commandRunner`.
@@ -71,43 +17,7 @@ export type ProgressTask<R> = (
* @param args arguments passed to this task passed on from
* `commands.registerCommand`.
*/
type NoProgressTask = (...args: any[]) => Promise<any>;
/**
* This mediates between the kind of progress callbacks we want to
* write (where we *set* current progress position and give
* `maxSteps`) and the kind vscode progress api expects us to write
* (which increment progress by a certain amount out of 100%).
*
* Where possible, the `commandRunner` function below should be used
* instead of this function. The commandRunner is meant for wrapping
* top-level commands and provides error handling and other support
* automatically.
*
* Only use this function if you need a progress monitor and the
* control flow does not always come from a command (eg- during
* extension activation, or from an internal language server
* request).
*/
export function withProgress<R>(
options: ProgressOptions,
task: ProgressTask<R>,
...args: any[]
): Thenable<R> {
let progressAchieved = 0;
return Window.withProgress(options, (progress, token) => {
return task(
(p) => {
const { message, step, maxStep } = p;
const increment = (100 * (step - progressAchieved)) / maxStep;
progressAchieved = step;
progress.report({ message, increment });
},
token,
...args,
);
});
}
export type NoProgressTask = (...args: any[]) => Promise<any>;
/**
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
@@ -121,6 +31,7 @@ export function withProgress<R>(
export function commandRunner(
commandId: string,
task: NoProgressTask,
outputLogger = extLogger,
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
const startTime = Date.now();
@@ -134,64 +45,6 @@ export function commandRunner(
getErrorMessage(e) || e
} (${commandId})`;
const errorStack = getErrorStack(e);
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
void extLogger.log(errorMessage.fullMessage);
} else {
void showAndLogWarningMessage(errorMessage.fullMessage);
}
} else {
// Include the full stack in the error log only.
const fullMessage = errorStack
? `${errorMessage.fullMessage}\n${errorStack}`
: errorMessage.fullMessage;
void showAndLogExceptionWithTelemetry(errorMessage, {
fullMessage,
extraTelemetryProperties: {
command: commandId,
},
});
}
return undefined;
} finally {
const executionTime = Date.now() - startTime;
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
}
});
}
/**
* A generic wrapper for command registration. This wrapper adds uniform error handling,
* progress monitoring, and cancellation for commands.
*
* @param commandId The ID of the command to register.
* @param task The task to run. It is passed directly to `commands.registerCommand`. Any
* arguments to the command handler are passed on to the task after the progress callback
* and cancellation token.
* @param progressOptions Progress options to be sent to the progress monitor.
*/
export function commandRunnerWithProgress<R>(
commandId: string,
task: ProgressTask<R>,
progressOptions: Partial<ProgressOptions>,
outputLogger = extLogger,
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
const startTime = Date.now();
let error: Error | undefined;
const progressOptionsWithDefaults = {
location: ProgressLocation.Notification,
...progressOptions,
};
try {
return await withProgress(progressOptionsWithDefaults, task, ...args);
} catch (e) {
error = asError(e);
const errorMessage = redactableError`${
getErrorMessage(e) || e
} (${commandId})`;
const errorStack = getErrorStack(e);
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
@@ -221,48 +74,3 @@ export function commandRunnerWithProgress<R>(
}
});
}
/**
* Displays a progress monitor that indicates how much progess has been made
* reading from a stream.
*
* @param readable The stream to read progress from
* @param messagePrefix A prefix for displaying the message
* @param totalNumBytes Total number of bytes in this stream
* @param progress The progress callback used to set messages
*/
export function reportStreamProgress(
readable: NodeJS.ReadableStream,
messagePrefix: string,
totalNumBytes?: number,
progress?: ProgressCallback,
) {
if (progress && totalNumBytes) {
let numBytesDownloaded = 0;
const bytesToDisplayMB = (numBytes: number): string =>
`${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
progress({
step: numBytesDownloaded,
maxStep: totalNumBytes,
message: `${messagePrefix} [${bytesToDisplayMB(
numBytesDownloaded,
)} of ${bytesToDisplayMB(totalNumBytes)}]`,
});
};
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();
readable.on("data", (data) => {
numBytesDownloaded += data.length;
updateProgress();
});
} else if (progress) {
progress({
step: 1,
maxStep: 2,
message: `${messagePrefix} (Size unknown)`,
});
}
}

View File

@@ -3,10 +3,10 @@ import { Disposable } from "../pure/disposable-object";
import { AppEventEmitter } from "./events";
import { Logger } from "./logging";
import { Memento } from "./memento";
import { AppCommandManager } from "./commands";
export interface App {
createEventEmitter<T>(): AppEventEmitter<T>;
executeCommand(command: string, ...args: any): Thenable<void>;
readonly mode: AppMode;
readonly logger: Logger;
readonly subscriptions: Disposable[];
@@ -15,6 +15,7 @@ export interface App {
readonly workspaceStoragePath?: string;
readonly workspaceState: Memento;
readonly credentials: Credentials;
readonly commands: AppCommandManager;
}
export enum AppMode {

View File

@@ -0,0 +1,278 @@
import type { CommandManager } from "../packages/commands";
import type { Uri, Range } from "vscode";
import type { AstItem } from "../astViewer";
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
import type { DatabaseItem } from "../local-databases";
import type { QueryHistoryInfo } from "../query-history/query-history-info";
import type { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
import type { TestTreeNode } from "../test-tree-node";
import type {
VariantAnalysis,
VariantAnalysisScannedRepository,
VariantAnalysisScannedRepositoryResult,
} from "../variant-analysis/shared/variant-analysis";
// A command function matching the signature that VS Code calls when
// a command on a selection is invoked.
export type SelectionCommandFunction<Item> = (
singleItem: Item,
multiSelect: Item[],
) => Promise<void>;
// A command function matching the signature that VS Code calls when
// a command on a selection is invoked when canSelectMany is false.
export type SingleSelectionCommandFunction<Item> = (
singleItem: Item,
) => Promise<void>;
/**
* Contains type definitions for all commands used by the extension.
*
* To add a new command first define its type here, then provide
* the implementation in the corresponding `getCommands` function.
*/
// Builtin commands where the implementation is provided by VS Code and not by this extension.
// See https://code.visualstudio.com/api/references/commands
export type BuiltInVsCodeCommands = {
"markdown.showPreviewToSide": (uri: Uri) => Promise<void>;
setContext: (
key: `${"codeql" | "codeQL"}${string}`,
value: unknown,
) => Promise<void>;
"workbench.action.reloadWindow": () => Promise<void>;
};
// Commands that are available before the extension is fully activated.
// These commands are *not* registered using the command manager, but can
// be invoked using the command manager.
export type PreActivationCommands = {
"codeQL.checkForUpdatesToCLI": () => Promise<void>;
};
// Base commands not tied directly to a module like e.g. variant analysis.
export type BaseCommands = {
"codeQL.openDocumentation": () => Promise<void>;
"codeQL.showLogs": () => Promise<void>;
"codeQL.authenticateToGitHub": () => Promise<void>;
"codeQL.copyVersion": () => Promise<void>;
"codeQL.restartQueryServer": () => Promise<void>;
};
// Commands used when working with queries in the editor
export type QueryEditorCommands = {
"codeQL.openReferencedFile": (selectedQuery: Uri) => Promise<void>;
"codeQL.openReferencedFileContextEditor": (
selectedQuery: Uri,
) => Promise<void>;
"codeQL.openReferencedFileContextExplorer": (
selectedQuery: Uri,
) => Promise<void>;
"codeQL.previewQueryHelp": (selectedQuery: Uri) => Promise<void>;
};
// Commands used for running local queries
export type LocalQueryCommands = {
"codeQL.runQuery": (uri?: Uri) => Promise<void>;
"codeQL.runQueryContextEditor": (uri?: Uri) => Promise<void>;
"codeQL.runQueryOnMultipleDatabases": (uri?: Uri) => Promise<void>;
"codeQL.runQueryOnMultipleDatabasesContextEditor": (
uri?: Uri,
) => Promise<void>;
"codeQL.runQueries": SelectionCommandFunction<Uri>;
"codeQL.quickEval": (uri: Uri) => Promise<void>;
"codeQL.quickEvalContextEditor": (uri: Uri) => Promise<void>;
"codeQL.codeLensQuickEval": (uri: Uri, range: Range) => Promise<void>;
"codeQL.quickQuery": () => Promise<void>;
};
export type ResultsViewCommands = {
"codeQLQueryResults.up": () => Promise<void>;
"codeQLQueryResults.down": () => Promise<void>;
"codeQLQueryResults.left": () => Promise<void>;
"codeQLQueryResults.right": () => Promise<void>;
"codeQLQueryResults.nextPathStep": () => Promise<void>;
"codeQLQueryResults.previousPathStep": () => Promise<void>;
};
// Commands used for the query history panel
export type QueryHistoryCommands = {
// Commands in the "navigation" group
"codeQLQueryHistory.sortByName": () => Promise<void>;
"codeQLQueryHistory.sortByDate": () => Promise<void>;
"codeQLQueryHistory.sortByCount": () => Promise<void>;
// Commands in the context menu or in the hover menu
"codeQLQueryHistory.openQueryTitleMenu": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.openQueryContextMenu": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.removeHistoryItemTitleMenu": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.removeHistoryItemContextMenu": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.removeHistoryItemContextInline": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.renameItem": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.compareWith": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.showEvalLog": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.showEvalLogSummary": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.showEvalLogViewer": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.showQueryLog": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.showQueryText": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.openQueryDirectory": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.cancel": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.exportResults": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.viewCsvResults": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.viewCsvAlerts": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.viewSarifAlerts": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.viewDil": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.itemClicked": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.openOnGithub": SelectionCommandFunction<QueryHistoryInfo>;
"codeQLQueryHistory.copyRepoList": SelectionCommandFunction<QueryHistoryInfo>;
// Commands in the command palette
"codeQL.exportSelectedVariantAnalysisResults": () => Promise<void>;
};
// Commands used for the local databases panel
export type LocalDatabasesCommands = {
// Command palette commands
"codeQL.chooseDatabaseFolder": () => Promise<void>;
"codeQL.chooseDatabaseArchive": () => Promise<void>;
"codeQL.chooseDatabaseInternet": () => Promise<void>;
"codeQL.chooseDatabaseGithub": () => Promise<void>;
"codeQL.upgradeCurrentDatabase": () => Promise<void>;
"codeQL.clearCache": () => Promise<void>;
// Explorer context menu
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
// Database panel view title commands
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;
"codeQLDatabases.chooseDatabaseArchive": () => Promise<void>;
"codeQLDatabases.chooseDatabaseInternet": () => Promise<void>;
"codeQLDatabases.chooseDatabaseGithub": () => Promise<void>;
"codeQLDatabases.sortByName": () => Promise<void>;
"codeQLDatabases.sortByDateAdded": () => Promise<void>;
// Database panel context menu
"codeQLDatabases.setCurrentDatabase": (
databaseItem: DatabaseItem,
) => Promise<void>;
// Database panel selection commands
"codeQLDatabases.removeDatabase": SelectionCommandFunction<DatabaseItem>;
"codeQLDatabases.upgradeDatabase": SelectionCommandFunction<DatabaseItem>;
"codeQLDatabases.renameDatabase": SelectionCommandFunction<DatabaseItem>;
"codeQLDatabases.openDatabaseFolder": SelectionCommandFunction<DatabaseItem>;
"codeQLDatabases.addDatabaseSource": SelectionCommandFunction<DatabaseItem>;
// Codespace template commands
"codeQL.setDefaultTourDatabase": () => Promise<void>;
// Internal commands
"codeQLDatabases.removeOrphanedDatabases": () => Promise<void>;
};
// Commands tied to variant analysis
export type VariantAnalysisCommands = {
"codeQL.autoDownloadVariantAnalysisResult": (
scannedRepo: VariantAnalysisScannedRepository,
variantAnalysisSummary: VariantAnalysis,
) => Promise<void>;
"codeQL.copyVariantAnalysisRepoList": (
variantAnalysisId: number,
filterSort?: RepositoriesFilterSortStateWithIds,
) => Promise<void>;
"codeQL.loadVariantAnalysisRepoResults": (
variantAnalysisId: number,
repositoryFullName: string,
) => Promise<VariantAnalysisScannedRepositoryResult>;
"codeQL.monitorVariantAnalysis": (
variantAnalysis: VariantAnalysis,
) => Promise<void>;
"codeQL.openVariantAnalysisLogs": (
variantAnalysisId: number,
) => Promise<void>;
"codeQL.openVariantAnalysisView": (
variantAnalysisId: number,
) => Promise<void>;
"codeQL.runVariantAnalysis": (uri?: Uri) => Promise<void>;
"codeQL.runVariantAnalysisContextEditor": (uri?: Uri) => Promise<void>;
};
export type DatabasePanelCommands = {
"codeQLVariantAnalysisRepositories.openConfigFile": () => Promise<void>;
"codeQLVariantAnalysisRepositories.addNewDatabase": () => Promise<void>;
"codeQLVariantAnalysisRepositories.addNewList": () => Promise<void>;
"codeQLVariantAnalysisRepositories.setupControllerRepository": () => Promise<void>;
"codeQLVariantAnalysisRepositories.setSelectedItem": SingleSelectionCommandFunction<DbTreeViewItem>;
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
"codeQLVariantAnalysisRepositories.renameItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
"codeQLVariantAnalysisRepositories.removeItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
};
export type AstCfgCommands = {
"codeQL.viewAst": (selectedFile: Uri) => Promise<void>;
"codeQL.viewAstContextExplorer": (selectedFile: Uri) => Promise<void>;
"codeQL.viewAstContextEditor": (selectedFile: Uri) => Promise<void>;
"codeQL.viewCfg": () => Promise<void>;
"codeQL.viewCfgContextExplorer": () => Promise<void>;
"codeQL.viewCfgContextEditor": () => Promise<void>;
};
export type AstViewerCommands = {
"codeQLAstViewer.clear": () => Promise<void>;
"codeQLAstViewer.gotoCode": (item: AstItem) => Promise<void>;
};
export type PackagingCommands = {
"codeQL.installPackDependencies": () => Promise<void>;
"codeQL.downloadPacks": () => Promise<void>;
};
export type EvalLogViewerCommands = {
"codeQLEvalLogViewer.clear": () => Promise<void>;
};
export type SummaryLanguageSupportCommands = {
"codeQL.gotoQL": () => Promise<void>;
};
export type TestUICommands = {
"codeQLTests.showOutputDifferences": (node: TestTreeNode) => Promise<void>;
"codeQLTests.acceptOutput": (node: TestTreeNode) => Promise<void>;
};
export type MockGitHubApiServerCommands = {
"codeQL.mockGitHubApiServer.startRecording": () => Promise<void>;
"codeQL.mockGitHubApiServer.saveScenario": () => Promise<void>;
"codeQL.mockGitHubApiServer.cancelRecording": () => Promise<void>;
"codeQL.mockGitHubApiServer.loadScenario": () => Promise<void>;
"codeQL.mockGitHubApiServer.unloadScenario": () => Promise<void>;
};
// All commands where the implementation is provided by this activated extension.
export type AllExtensionCommands = BaseCommands &
QueryEditorCommands &
ResultsViewCommands &
QueryHistoryCommands &
LocalDatabasesCommands &
VariantAnalysisCommands &
DatabasePanelCommands &
AstCfgCommands &
AstViewerCommands &
PackagingCommands &
EvalLogViewerCommands &
SummaryLanguageSupportCommands &
Partial<TestUICommands> &
MockGitHubApiServerCommands;
export type AllCommands = AllExtensionCommands &
PreActivationCommands &
BuiltInVsCodeCommands;
export type AppCommandManager = CommandManager<AllCommands>;
// Separate command manager because it uses a different logger
export type QueryServerCommands = LocalQueryCommands;
export type QueryServerCommandManager = CommandManager<QueryServerCommands>;

View File

@@ -1,3 +1,4 @@
export * from "./logger";
export * from "./tee-logger";
export * from "./vscode/loggers";
export * from "./vscode/output-channel-logger";

View File

@@ -1,9 +1,6 @@
export interface LogOptions {
// If false, don't output a trailing newline for the log entry. Default true.
trailingNewline?: boolean;
// If specified, add this log entry to the log file at the specified location.
additionalLogLocation?: string;
}
export interface Logger {
@@ -25,11 +22,4 @@ export interface Logger {
* @param preserveFocus When `true` the channel will not take focus.
*/
show(preserveFocus?: boolean): void;
/**
* Remove the log at the specified location.
*
* @param location log to remove
*/
removeAdditionalLogLocation(location: string | undefined): void;
}

View File

@@ -0,0 +1,68 @@
import { appendFile, ensureFile } from "fs-extra";
import { isAbsolute } from "path";
import { getErrorMessage } from "../../pure/helpers-pure";
import { Logger, LogOptions } from "./logger";
/**
* An implementation of {@link Logger} that sends the output both to another {@link Logger}
* and to a file.
*
* The first time a message is written, an additional banner is written to the underlying logger
* pointing the user to the "side log" file.
*/
export class TeeLogger implements Logger {
private emittedRedirectMessage = false;
private error = false;
public constructor(
private readonly logger: Logger,
private readonly location: string,
) {
if (!isAbsolute(location)) {
throw new Error(
`Additional Log Location must be an absolute path: ${location}`,
);
}
}
async log(message: string, options = {} as LogOptions): Promise<void> {
if (!this.emittedRedirectMessage) {
this.emittedRedirectMessage = true;
const msg = `| Log being saved to ${this.location} |`;
const separator = new Array(msg.length).fill("-").join("");
await this.logger.log(separator);
await this.logger.log(msg);
await this.logger.log(separator);
}
if (!this.error) {
try {
const trailingNewline = options.trailingNewline ?? true;
await ensureFile(this.location);
await appendFile(
this.location,
message + (trailingNewline ? "\n" : ""),
{
encoding: "utf8",
},
);
} catch (e) {
// Write an error message to the primary log, and stop trying to write to the side log.
this.error = true;
const errorMessage = getErrorMessage(e);
await this.logger.log(
`Error writing to additional log file: ${errorMessage}`,
);
}
}
if (!this.error) {
await this.logger.log(message, options);
}
}
show(preserveFocus?: boolean): void {
this.logger.show(preserveFocus);
}
}

View File

@@ -1,6 +1,4 @@
import { window as Window, OutputChannel, Progress } from "vscode";
import { ensureFile, appendFile } from "fs-extra";
import { isAbsolute } from "path";
import { Logger, LogOptions } from "../logger";
import { DisposableObject } from "../../../pure/disposable-object";
@@ -9,10 +7,6 @@ import { DisposableObject } from "../../../pure/disposable-object";
*/
export class OutputChannelLogger extends DisposableObject implements Logger {
public readonly outputChannel: OutputChannel;
private readonly additionalLocations = new Map<
string,
AdditionalLogLocation
>();
isCustomLogDirectory: boolean;
constructor(title: string) {
@@ -32,27 +26,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
} else {
this.outputChannel.append(message);
}
if (options.additionalLogLocation) {
if (!isAbsolute(options.additionalLogLocation)) {
throw new Error(
`Additional Log Location must be an absolute path: ${options.additionalLogLocation}`,
);
}
const logPath = options.additionalLogLocation;
let additional = this.additionalLocations.get(logPath);
if (!additional) {
const msg = `| Log being saved to ${logPath} |`;
const separator = new Array(msg.length).fill("-").join("");
this.outputChannel.appendLine(separator);
this.outputChannel.appendLine(msg);
this.outputChannel.appendLine(separator);
additional = new AdditionalLogLocation(logPath);
this.additionalLocations.set(logPath, additional);
}
await additional.log(message, options);
}
} catch (e) {
if (e instanceof Error && e.message === "Channel has been closed") {
// Output channel is closed logging to console instead
@@ -69,31 +42,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
show(preserveFocus?: boolean): void {
this.outputChannel.show(preserveFocus);
}
removeAdditionalLogLocation(location: string | undefined): void {
if (location) {
this.additionalLocations.delete(location);
}
}
}
class AdditionalLogLocation {
constructor(private location: string) {}
async log(message: string, options = {} as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
await ensureFile(this.location);
await appendFile(
this.location,
message + (options.trailingNewline ? "\n" : ""),
{
encoding: "utf8",
},
);
}
}
export type ProgressReporter = Progress<{ message: string }>;

View File

@@ -0,0 +1,35 @@
import { commands } from "vscode";
import { commandRunner, NoProgressTask } from "../../commandRunner";
import { CommandFunction, CommandManager } from "../../packages/commands";
import { OutputChannelLogger } from "../logging";
/**
* Create a command manager for VSCode, wrapping the commandRunner
* and vscode.executeCommand.
*/
export function createVSCodeCommandManager<
Commands extends Record<string, CommandFunction>,
>(outputLogger?: OutputChannelLogger): CommandManager<Commands> {
return new CommandManager((commandId, task: NoProgressTask) => {
return commandRunner(commandId, task, outputLogger);
}, wrapExecuteCommand);
}
/**
* wrapExecuteCommand wraps commands.executeCommand to satisfy that the
* type is a Promise. Type script does not seem to be smart enough
* to figure out that `ReturnType<Commands[CommandName]>` is actually
* a Promise, so we need to add a second layer of wrapping and unwrapping
* (The `Promise<Awaited<` part) to get the right types.
*/
async function wrapExecuteCommand<
Commands extends Record<string, CommandFunction>,
CommandName extends keyof Commands & string = keyof Commands & string,
>(
commandName: CommandName,
...args: Parameters<Commands[CommandName]>
): Promise<Awaited<ReturnType<Commands[CommandName]>>> {
return await commands.executeCommand<
Awaited<ReturnType<Commands[CommandName]>>
>(commandName, ...args);
}

View File

@@ -3,17 +3,24 @@ import { VSCodeCredentials } from "../../authentication";
import { Disposable } from "../../pure/disposable-object";
import { App, AppMode } from "../app";
import { AppEventEmitter } from "../events";
import { extLogger, Logger } from "../logging";
import { extLogger, Logger, queryServerLogger } from "../logging";
import { Memento } from "../memento";
import { VSCodeAppEventEmitter } from "./events";
import { AppCommandManager, QueryServerCommandManager } from "../commands";
import { createVSCodeCommandManager } from "./commands";
export class ExtensionApp implements App {
public readonly credentials: VSCodeCredentials;
public readonly commands: AppCommandManager;
public readonly queryServerCommands: QueryServerCommandManager;
public constructor(
public readonly extensionContext: vscode.ExtensionContext,
) {
this.credentials = new VSCodeCredentials();
this.commands = createVSCodeCommandManager();
this.queryServerCommands = createVSCodeCommandManager(queryServerLogger);
extensionContext.subscriptions.push(this.commands);
}
public get extensionPath(): string {
@@ -54,8 +61,4 @@ export class ExtensionApp implements App {
public createEventEmitter<T>(): AppEventEmitter<T> {
return new VSCodeAppEventEmitter<T>();
}
public executeCommand(command: string, ...args: any): Thenable<void> {
return vscode.commands.executeCommand(command, ...args);
}
}

View File

@@ -20,6 +20,8 @@ import { assertNever, getErrorMessage } from "../pure/helpers-pure";
import { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
import { telemetryListener } from "../telemetry";
import { redactableError } from "../pure/errors";
import { showAndLogExceptionWithTelemetry } from "../helpers";
interface ComparePair {
from: CompletedLocalQueryInfo;
@@ -139,6 +141,14 @@ export class CompareView extends AbstractWebview<
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in result comparison view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}

View File

@@ -83,10 +83,6 @@ export const GLOBAL_ENABLE_TELEMETRY = new Setting(
GLOBAL_TELEMETRY_SETTING,
);
export function newTelemetryEnabled(): boolean {
return true;
}
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);
export const CUSTOM_CODEQL_PATH_SETTING = new Setting(
@@ -141,6 +137,10 @@ const DEBUG_SETTING = new Setting("debug", RUNNING_QUERIES_SETTING);
const MAX_PATHS = new Setting("maxPaths", RUNNING_QUERIES_SETTING);
const RUNNING_TESTS_SETTING = new Setting("runningTests", ROOT_SETTING);
const RESULTS_DISPLAY_SETTING = new Setting("resultsDisplay", ROOT_SETTING);
const USE_EXTENSION_PACKS = new Setting(
"useExtensionPacks",
RUNNING_QUERIES_SETTING,
);
export const ADDITIONAL_TEST_ARGUMENTS_SETTING = new Setting(
"additionalTestArguments",
@@ -200,6 +200,7 @@ const CLI_SETTINGS = [
NUMBER_OF_TEST_THREADS_SETTING,
NUMBER_OF_THREADS_SETTING,
MAX_PATHS,
USE_EXTENSION_PACKS,
];
export interface CliConfig {
@@ -207,7 +208,9 @@ export interface CliConfig {
numberTestThreads: number;
numberThreads: number;
maxPaths: number;
useExtensionPacks: boolean;
onDidChangeConfiguration?: Event<void>;
setUseExtensionPacks: (useExtensionPacks: boolean) => Promise<void>;
}
export abstract class ConfigListener extends DisposableObject {
@@ -404,6 +407,19 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
return MAX_PATHS.getValue<number>();
}
public get useExtensionPacks(): boolean {
// currently, we are restricting the values of this setting to 'all' or 'none'.
return USE_EXTENSION_PACKS.getValue() === "all";
}
// Exposed for testing only
public async setUseExtensionPacks(newUseExtensionPacks: boolean) {
await USE_EXTENSION_PACKS.updateValue(
newUseExtensionPacks ? "all" : "none",
ConfigurationTarget.Global,
);
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
}

View File

@@ -11,7 +11,7 @@ import {
import { CodeQLCliServer } from "../cli";
import { DatabaseManager, DatabaseItem } from "../local-databases";
import fileRangeFromURI from "./fileRangeFromURI";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { KeyType } from "./keyType";
import {
qlpackOfDatabase,

View File

@@ -16,7 +16,7 @@ import { DatabaseItem } from "../local-databases";
import { extLogger } from "../common";
import { createInitialQueryInfo } from "../run-queries-shared";
import { CancellationToken, Uri } from "vscode";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { QueryRunner } from "../queryRunner";
import { redactableError } from "../pure/errors";
import { QLPACK_FILENAMES } from "../pure/ql";

View File

@@ -4,7 +4,6 @@ import {
Location,
LocationLink,
Position,
ProgressLocation,
ReferenceContext,
ReferenceProvider,
TextDocument,
@@ -19,7 +18,7 @@ import {
import { CodeQLCliServer } from "../cli";
import { DatabaseManager } from "../local-databases";
import { CachedOperation } from "../helpers";
import { ProgressCallback, withProgress } from "../commandRunner";
import { ProgressCallback, withProgress } from "../progress";
import AstBuilder from "./astBuilder";
import { KeyType } from "./keyType";
import {
@@ -73,11 +72,6 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
return withProgress(
{
location: ProgressLocation.Notification,
cancellable: true,
title: "Finding definitions",
},
async (progress, token) => {
return getLocationsForUriString(
this.cli,
@@ -91,6 +85,10 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
(src, _dest) => src === uriString,
);
},
{
cancellable: true,
title: "Finding definitions",
},
);
}
}
@@ -136,11 +134,6 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
return withProgress(
{
location: ProgressLocation.Notification,
cancellable: true,
title: "Finding references",
},
async (progress, token) => {
return getLocationsForUriString(
this.cli,
@@ -154,6 +147,10 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
(src, _dest) => src === uriString,
);
},
{
cancellable: true,
title: "Finding references",
},
);
}
}

View File

@@ -18,7 +18,7 @@ import { retry } from "@octokit/plugin-retry";
import { DatabaseManager, DatabaseItem } from "./local-databases";
import { showAndLogInformationMessage, tmpDir } from "./helpers";
import { reportStreamProgress, ProgressCallback } from "./commandRunner";
import { reportStreamProgress, ProgressCallback } from "./progress";
import { extLogger } from "./common";
import { getErrorMessage } from "./pure/helpers-pure";
import {

View File

@@ -391,14 +391,14 @@ export class DbConfigStore extends DisposableObject {
if (this.configErrors.length === 0) {
this.config = newConfig;
await this.app.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQLVariantAnalysisRepositories.configError",
false,
);
} else {
this.config = undefined;
await this.app.executeCommand(
await this.app.commands.execute(
"setContext",
"codeQLVariantAnalysisRepositories.configError",
true,
@@ -426,14 +426,14 @@ export class DbConfigStore extends DisposableObject {
if (this.configErrors.length === 0) {
this.config = newConfig;
void this.app.executeCommand(
void this.app.commands.execute(
"setContext",
"codeQLVariantAnalysisRepositories.configError",
false,
);
} else {
this.config = undefined;
void this.app.executeCommand(
void this.app.commands.execute(
"setContext",
"codeQLVariantAnalysisRepositories.configError",
true,

View File

@@ -1,16 +1,17 @@
import { window } from "vscode";
import { App, AppMode } from "../common/app";
import { App } from "../common/app";
import { extLogger } from "../common";
import { DisposableObject } from "../pure/disposable-object";
import { DbConfigStore } from "./config/db-config-store";
import { DbManager } from "./db-manager";
import { DbPanel } from "./ui/db-panel";
import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider";
import { isCanary } from "../config";
import { DatabasePanelCommands } from "../common/commands";
export class DbModule extends DisposableObject {
public readonly dbManager: DbManager;
private readonly dbConfigStore: DbConfigStore;
private dbPanel: DbPanel | undefined;
private constructor(app: App) {
super();
@@ -19,24 +20,22 @@ export class DbModule extends DisposableObject {
this.dbManager = new DbManager(app, this.dbConfigStore);
}
public static async initialize(app: App): Promise<DbModule | undefined> {
if (DbModule.shouldEnableModule(app.mode)) {
const dbModule = new DbModule(app);
app.subscriptions.push(dbModule);
public static async initialize(app: App): Promise<DbModule> {
const dbModule = new DbModule(app);
app.subscriptions.push(dbModule);
await dbModule.initialize(app);
return dbModule;
}
return undefined;
await dbModule.initialize(app);
return dbModule;
}
private static shouldEnableModule(app: AppMode): boolean {
if (app === AppMode.Development || app === AppMode.Test) {
return true;
public getCommands(): DatabasePanelCommands {
if (!this.dbPanel) {
throw new Error("Database panel not initialized");
}
return isCanary();
return {
...this.dbPanel.getCommands(),
};
}
private async initialize(app: App): Promise<void> {
@@ -44,10 +43,9 @@ export class DbModule extends DisposableObject {
await this.dbConfigStore.initialize();
const dbPanel = new DbPanel(this.dbManager, app.credentials);
await dbPanel.initialize();
this.dbPanel = new DbPanel(this.dbManager, app.credentials);
this.push(dbPanel);
this.push(this.dbPanel);
this.push(this.dbConfigStore);
const dbSelectionDecorationProvider = new DbSelectionDecorationProvider();

View File

@@ -7,7 +7,7 @@ import {
window,
workspace,
} from "vscode";
import { commandRunner, UserCancellationException } from "../../commandRunner";
import { UserCancellationException } from "../../progress";
import {
getNwoFromGitHubUrl,
isValidGitHubNwo,
@@ -32,6 +32,7 @@ import { getGitHubUrl } from "./db-tree-view-item-action";
import { getControllerRepo } from "../../variant-analysis/run-remote-query";
import { getErrorMessage } from "../../pure/helpers-pure";
import { Credentials } from "../../common/authentication";
import { DatabasePanelCommands } from "../../common/commands";
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
kind: string;
@@ -72,58 +73,28 @@ export class DbPanel extends DisposableObject {
this.push(this.treeView);
}
public async initialize(): Promise<void> {
this.push(
commandRunner("codeQLVariantAnalysisRepositories.openConfigFile", () =>
this.openConfigFile(),
),
);
this.push(
commandRunner("codeQLVariantAnalysisRepositories.addNewDatabase", () =>
this.addNewRemoteDatabase(),
),
);
this.push(
commandRunner("codeQLVariantAnalysisRepositories.addNewList", () =>
this.addNewList(),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.setSelectedItem",
(treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
(treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
(treeViewItem: DbTreeViewItem) => this.openOnGitHub(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.renameItemContextMenu",
(treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.removeItemContextMenu",
(treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLVariantAnalysisRepositories.setupControllerRepository",
() => this.setupControllerRepository(),
),
);
public getCommands(): DatabasePanelCommands {
return {
"codeQLVariantAnalysisRepositories.openConfigFile":
this.openConfigFile.bind(this),
"codeQLVariantAnalysisRepositories.addNewDatabase":
this.addNewRemoteDatabase.bind(this),
"codeQLVariantAnalysisRepositories.addNewList":
this.addNewList.bind(this),
"codeQLVariantAnalysisRepositories.setupControllerRepository":
this.setupControllerRepository.bind(this),
"codeQLVariantAnalysisRepositories.setSelectedItem":
this.setSelectedItem.bind(this),
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu":
this.setSelectedItem.bind(this),
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu":
this.openOnGitHub.bind(this),
"codeQLVariantAnalysisRepositories.renameItemContextMenu":
this.renameItem.bind(this),
"codeQLVariantAnalysisRepositories.removeItemContextMenu":
this.removeItem.bind(this),
};
}
private async openConfigFile(): Promise<void> {

View File

@@ -14,7 +14,7 @@ import {
} from "./helpers";
import { extLogger } from "./common";
import { getCodeQlCliVersion } from "./cli-version";
import { ProgressCallback, reportStreamProgress } from "./commandRunner";
import { ProgressCallback, reportStreamProgress } from "./progress";
import {
codeQlLauncherName,
deprecatedCodeQlLauncherName,

View File

@@ -8,11 +8,11 @@ import {
EventEmitter,
TreeItemCollapsibleState,
} from "vscode";
import { commandRunner } from "./commandRunner";
import { DisposableObject } from "./pure/disposable-object";
import { showAndLogExceptionWithTelemetry } from "./helpers";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { redactableError } from "./pure/errors";
import { EvalLogViewerCommands } from "./common/commands";
export interface EvalLogTreeItem {
label?: string;
@@ -80,11 +80,12 @@ export class EvalLogViewer extends DisposableObject {
this.push(this.treeView);
this.push(this.treeDataProvider);
this.push(
commandRunner("codeQLEvalLogViewer.clear", async () => {
this.clear();
}),
);
}
public getCommands(): EvalLogViewerCommands {
return {
"codeQLEvalLogViewer.clear": async () => this.clear(),
};
}
private clear(): void {

File diff suppressed because it is too large Load Diff

View File

@@ -16,15 +16,17 @@ import {
window as Window,
workspace,
env,
commands,
} from "vscode";
import { CodeQLCliServer, QlpacksInfo } from "./cli";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import { extLogger, OutputChannelLogger } from "./common";
import { QueryMetadata } from "./pure/interface-types";
import { telemetryListener } from "./telemetry";
import { RedactableError } from "./pure/errors";
import { getQlPackPath } from "./pure/ql";
import { dbSchemeToLanguage } from "./common/query-language";
import { isCodespacesTemplate } from "./config";
// Shared temporary folder for the extension.
export const tmpDir = dirSync({
@@ -266,6 +268,51 @@ export function isFolderAlreadyInWorkspace(folderName: string) {
);
}
/** Check if the current workspace is the CodeTour and open the workspace folder.
* Without this, we can't run the code tour correctly.
**/
export async function prepareCodeTour(): Promise<void> {
if (workspace.workspaceFolders?.length) {
const currentFolder = workspace.workspaceFolders[0].uri.fsPath;
const tutorialWorkspacePath = join(
currentFolder,
"tutorial.code-workspace",
);
const toursFolderPath = join(currentFolder, ".tours");
/** We're opening the tutorial workspace, if we detect it.
* This will only happen if the following three conditions are met:
* - the .tours folder exists
* - the tutorial.code-workspace file exists
* - the CODESPACES_TEMPLATE setting doesn't exist (it's only set if the user has already opened
* the tutorial workspace so it's a good indicator that the user is in the folder but has ignored
* the prompt to open the workspace)
*/
if (
(await pathExists(tutorialWorkspacePath)) &&
(await pathExists(toursFolderPath)) &&
!isCodespacesTemplate()
) {
const answer = await showBinaryChoiceDialog(
"We've detected you're in the CodeQL Tour repo. We will need to open the workspace file to continue. Reload?",
);
if (!answer) {
return;
}
const tutorialWorkspaceUri = Uri.parse(tutorialWorkspacePath);
void extLogger.log(
`In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`,
);
await commands.executeCommand("vscode.openFolder", tutorialWorkspaceUri);
}
}
}
/**
* Provides a utility method to invoke a function only if a minimum time interval has elapsed since
* the last invocation of that function.

View File

@@ -109,7 +109,11 @@ export function tryResolveLocation(
}
}
export type WebviewView = "results" | "compare" | "variant-analysis";
export type WebviewView =
| "results"
| "compare"
| "variant-analysis"
| "data-flow-paths";
export interface WebviewMessage {
t: string;
@@ -125,10 +129,13 @@ export function getHtmlForWebview(
view: WebviewView,
{
allowInlineStyles,
allowWasmEval,
}: {
allowInlineStyles?: boolean;
allowWasmEval?: boolean;
} = {
allowInlineStyles: false,
allowWasmEval: false,
},
): string {
const scriptUriOnDisk = Uri.file(ctx.asAbsolutePath("out/webview.js"));
@@ -159,7 +166,9 @@ export function getHtmlForWebview(
/*
* Content security policy:
* default-src: allow nothing by default.
* script-src: allow only the given script, using the nonce.
* script-src:
* - allow the given script, using the nonce.
* - 'wasm-unsafe-eval: allow loading WebAssembly modules if necessary.
* style-src: allow only the given stylesheet, using the nonce.
* connect-src: only allow fetch calls to webview resource URIs
* (this is used to load BQRS result files).
@@ -168,7 +177,9 @@ export function getHtmlForWebview(
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
content="default-src 'none'; script-src 'nonce-${nonce}'${
allowWasmEval ? " 'wasm-unsafe-eval'" : ""
}; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
webview.cspSource
};">
${stylesheetsHtmlLines.join(` ${EOL}`)}

View File

@@ -42,7 +42,6 @@ import {
ParsedResultSets,
} from "./pure/interface-types";
import { Logger } from "./common";
import { commandRunner } from "./commandRunner";
import {
CompletedQueryInfo,
interpretResultsSarif,
@@ -68,10 +67,11 @@ import {
ResultSetSchema,
} from "./pure/bqrs-cli-types";
import { AbstractWebview, WebviewPanelConfig } from "./abstract-webview";
import { PAGE_SIZE } from "./config";
import { isCanary, PAGE_SIZE } from "./config";
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";
import { telemetryListener } from "./telemetry";
import { redactableError } from "./pure/errors";
import { ResultsViewCommands } from "./common/commands";
/**
* interface.ts
@@ -179,21 +179,6 @@ export class ResultsView extends AbstractWebview<
this.handleSelectionChange.bind(this),
),
);
const navigationCommands = {
"codeQLQueryResults.up": NavigationDirection.up,
"codeQLQueryResults.down": NavigationDirection.down,
"codeQLQueryResults.left": NavigationDirection.left,
"codeQLQueryResults.right": NavigationDirection.right,
// For backwards compatibility with keybindings set using an earlier version of the extension.
"codeQLQueryResults.nextPathStep": NavigationDirection.down,
"codeQLQueryResults.previousPathStep": NavigationDirection.up,
};
void logger.log("Registering result view navigation commands.");
for (const [commandId, direction] of Object.entries(navigationCommands)) {
this.push(
commandRunner(commandId, this.navigateResultView.bind(this, direction)),
);
}
this.push(
this.databaseManager.onDidChangeDatabaseItem(({ kind }) => {
@@ -209,6 +194,36 @@ export class ResultsView extends AbstractWebview<
);
}
public getCommands(): ResultsViewCommands {
return {
"codeQLQueryResults.up": this.navigateResultView.bind(
this,
NavigationDirection.up,
),
"codeQLQueryResults.down": this.navigateResultView.bind(
this,
NavigationDirection.down,
),
"codeQLQueryResults.left": this.navigateResultView.bind(
this,
NavigationDirection.left,
),
"codeQLQueryResults.right": this.navigateResultView.bind(
this,
NavigationDirection.right,
),
// For backwards compatibility with keybindings set using an earlier version of the extension.
"codeQLQueryResults.nextPathStep": this.navigateResultView.bind(
this,
NavigationDirection.down,
),
"codeQLQueryResults.previousPathStep": this.navigateResultView.bind(
this,
NavigationDirection.up,
),
};
}
async navigateResultView(direction: NavigationDirection): Promise<void> {
if (!this.panel?.visible) {
return;
@@ -225,6 +240,8 @@ export class ResultsView extends AbstractWebview<
viewColumn: this.chooseColumnForWebview(),
preserveFocus: true,
view: "results",
// Required for the graph viewer which is using d3-graphviz WASM module. Only supported in canary mode.
allowWasmEval: isCanary(),
};
}
@@ -293,6 +310,13 @@ export class ResultsView extends AbstractWebview<
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in results view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}
@@ -397,7 +421,7 @@ export class ResultsView extends AbstractWebview<
forceReveal: WebviewReveal,
shouldKeepOldResultsWhileRendering = false,
): Promise<void> {
if (!fullQuery.completedQuery.successful) {
if (!fullQuery.completedQuery?.successful) {
return;
}
@@ -660,7 +684,8 @@ export class ResultsView extends AbstractWebview<
}
let data;
let numTotalResults;
if (metadata?.kind === GRAPH_TABLE_NAME) {
// Graph results are only supported in canary mode because the graph viewer is not actively supported
if (metadata?.kind === GRAPH_TABLE_NAME && isCanary()) {
data = await interpretGraphResults(
this.cliServer,
metadata,

View File

@@ -1,5 +1,5 @@
import { CancellationToken } from "vscode";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
Dataset,

View File

@@ -1,4 +1,3 @@
import { dirname } from "path";
import { ensureFile } from "fs-extra";
import { DisposableObject } from "../pure/disposable-object";
@@ -13,10 +12,8 @@ import {
progress,
ProgressMessage,
WithProgressId,
compileQuery,
} from "../pure/legacy-messages";
import { ProgressCallback, ProgressTask } from "../commandRunner";
import { findQueryLogFile } from "../run-queries-shared";
import { ProgressCallback, ProgressTask } from "../progress";
import { ServerProcess } from "../json-rpc-server";
type WithProgressReporting = (
@@ -56,7 +53,7 @@ export class QueryServerClient extends DisposableObject {
this.queryServerStartListeners.push(e);
};
public activeQueryLogFile: string | undefined;
public activeQueryLogger: Logger;
constructor(
readonly config: QueryServerConfig,
@@ -65,6 +62,9 @@ export class QueryServerClient extends DisposableObject {
withProgressReporting: WithProgressReporting,
) {
super();
// Since no query is active when we initialize, just point the "active query logger" to the
// default logger.
this.activeQueryLogger = this.logger;
// When the query server configuration changes, restart the query server.
if (config.onDidChangeConfiguration !== undefined) {
this.push(
@@ -177,9 +177,8 @@ export class QueryServerClient extends DisposableObject {
args,
this.logger,
(data) =>
this.logger.log(data.toString(), {
this.activeQueryLogger.log(data.toString(), {
trailingNewline: false,
additionalLogLocation: this.activeQueryLogFile,
}),
undefined, // no listener for stdout
progressReporter,
@@ -240,8 +239,6 @@ export class QueryServerClient extends DisposableObject {
): Promise<R> {
const id = this.nextProgress++;
this.progressCallbacks[id] = progress;
this.updateActiveQuery(type.method, parameter);
try {
if (this.serverProcess === undefined) {
throw new Error("No query server process found.");
@@ -255,18 +252,4 @@ export class QueryServerClient extends DisposableObject {
delete this.progressCallbacks[id];
}
}
/**
* Updates the active query every time there is a new request to compile.
* The active query is used to specify the side log.
*
* This isn't ideal because in situations where there are queries running
* in parallel, each query's log messages are interleaved. Fixing this
* properly will require a change in the query server.
*/
private updateActiveQuery(method: string, parameter: any): void {
if (method === compileQuery.method) {
this.activeQueryLogFile = findQueryLogFile(dirname(parameter.resultPath));
}
}
}

View File

@@ -13,9 +13,9 @@ import {
tryGetQueryMetadata,
upgradesTmpDir,
} from "../helpers";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { QueryMetadata } from "../pure/interface-types";
import { extLogger } from "../common";
import { extLogger, Logger, TeeLogger } from "../common";
import * as messages from "../pure/legacy-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
import * as qsClient from "./queryserver-client";
@@ -66,7 +66,8 @@ export class QueryInProgress {
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
queryInfo?: LocalQueryInfo,
logger: Logger,
queryInfo: LocalQueryInfo | undefined,
): Promise<messages.EvaluationResult> {
if (!dbItem.contents || dbItem.error) {
throw new Error("Can't run query on invalid database.");
@@ -137,7 +138,7 @@ export class QueryInProgress {
await this.queryEvalInfo.addQueryLogs(
queryInfo,
qs.cliServer,
qs.logger,
logger,
);
} else {
void showAndLogWarningMessage(
@@ -162,6 +163,7 @@ export class QueryInProgress {
program: messages.QlProgram,
progress: ProgressCallback,
token: CancellationToken,
logger: Logger,
): Promise<messages.CompilationMessage[]> {
let compiled: messages.CheckQueryResult | undefined;
try {
@@ -190,6 +192,11 @@ export class QueryInProgress {
target,
};
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
compiled = await qs.sendRequest(
messages.compileQuery,
params,
@@ -197,9 +204,7 @@ export class QueryInProgress {
progress,
);
} finally {
void qs.logger.log(" - - - COMPILATION DONE - - - ", {
additionalLogLocation: this.queryEvalInfo.logPath,
});
void logger.log(" - - - COMPILATION DONE - - - ");
}
return (compiled?.messages || []).filter(
(msg) => msg.severity === messages.Severity.ERROR,
@@ -386,6 +391,8 @@ export async function compileAndRunQueryAgainstDatabase(
metadata,
templates,
);
const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath);
await query.queryEvalInfo.createTimestampFile();
let upgradeDir: tmp.DirectoryResult | undefined;
@@ -402,7 +409,7 @@ export async function compileAndRunQueryAgainstDatabase(
);
let errors;
try {
errors = await query.compile(qs, qlProgram, progress, token);
errors = await query.compile(qs, qlProgram, progress, token, logger);
} catch (e) {
if (
e instanceof ResponseError &&
@@ -422,6 +429,7 @@ export async function compileAndRunQueryAgainstDatabase(
dbItem,
progress,
token,
logger,
queryInfo,
);
if (result.resultType !== messages.QueryResultType.SUCCESS) {
@@ -439,18 +447,14 @@ export async function compileAndRunQueryAgainstDatabase(
result,
successful: result.resultType === messages.QueryResultType.SUCCESS,
logFileLocation: result.logFileLocation,
dispose: () => {
qs.logger.removeAdditionalLogLocation(result.logFileLocation);
},
};
} else {
// Error dialogs are limited in size and scrollability,
// so we include a general description of the problem,
// and direct the user to the output window for the detailed compilation messages.
// However we don't show quick eval errors there so we need to display them anyway.
void qs.logger.log(
void logger.log(
`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
{ additionalLogLocation: query.queryEvalInfo.logPath },
);
const formattedMessages: string[] = [];
@@ -459,9 +463,7 @@ export async function compileAndRunQueryAgainstDatabase(
const message = error.message || "[no error message available]";
const formatted = `ERROR: ${message} (${error.position.fileName}:${error.position.line}:${error.position.column}:${error.position.endLine}:${error.position.endColumn})`;
formattedMessages.push(formatted);
void qs.logger.log(formatted, {
additionalLogLocation: query.queryEvalInfo.logPath,
});
void logger.log(formatted);
}
if (initialInfo.isQuickEval && formattedMessages.length <= 2) {
// If there are more than 2 error messages, they will not be displayed well in a popup
@@ -484,9 +486,8 @@ export async function compileAndRunQueryAgainstDatabase(
try {
await upgradeDir?.cleanup();
} catch (e) {
void qs.logger.log(
void logger.log(
`Could not clean up the upgrades dir. Reason: ${getErrorMessage(e)}`,
{ additionalLogLocation: query.queryEvalInfo.logPath },
);
}
}
@@ -535,9 +536,6 @@ function createSyntheticResult(
runId: 0,
},
successful: false,
dispose: () => {
/**/
},
};
}

View File

@@ -4,7 +4,7 @@ import {
showAndLogExceptionWithTelemetry,
tmpDir,
} from "../helpers";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { extLogger } from "../common";
import * as messages from "../pure/legacy-messages";
import * as qsClient from "./queryserver-client";

View File

@@ -21,11 +21,7 @@ import {
DatabaseItem,
DatabaseManager,
} from "./local-databases";
import {
commandRunner,
commandRunnerWithProgress,
ProgressCallback,
} from "./commandRunner";
import { ProgressCallback, withProgress } from "./progress";
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
@@ -42,8 +38,8 @@ import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure";
import { QueryRunner } from "./queryRunner";
import { isCanary } from "./config";
import { App } from "./common/app";
import { Credentials } from "./common/authentication";
import { redactableError } from "./pure/errors";
import { LocalDatabasesCommands } from "./common/commands";
enum SortOrder {
NameAsc = "NameAsc",
@@ -73,12 +69,12 @@ class DatabaseTreeDataProvider
this.push(
this.databaseManager.onDidChangeDatabaseItem(
this.handleDidChangeDatabaseItem,
this.handleDidChangeDatabaseItem.bind(this),
),
);
this.push(
this.databaseManager.onDidChangeCurrentDatabaseItem(
this.handleDidChangeCurrentDatabaseItem,
this.handleDidChangeCurrentDatabaseItem.bind(this),
),
);
}
@@ -87,18 +83,18 @@ class DatabaseTreeDataProvider
return this._onDidChangeTreeData.event;
}
private handleDidChangeDatabaseItem = (event: DatabaseChangedEvent): void => {
private handleDidChangeDatabaseItem(event: DatabaseChangedEvent): void {
// Note that events from the database manager are instances of DatabaseChangedEvent
// and events fired by the UI are instances of DatabaseItem
// When event.item is undefined, then the entire tree is refreshed.
// When event.item is a db item, then only that item is refreshed.
this._onDidChangeTreeData.fire(event.item);
};
}
private handleDidChangeCurrentDatabaseItem = (
private handleDidChangeCurrentDatabaseItem(
event: DatabaseChangedEvent,
): void => {
): void {
if (this.currentDatabaseItem) {
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
}
@@ -106,7 +102,7 @@ class DatabaseTreeDataProvider
if (this.currentDatabaseItem) {
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
}
};
}
public getTreeItem(element: DatabaseItem): TreeItem {
const item = new TreeItem(element.name);
@@ -210,149 +206,53 @@ export class DatabaseUI extends DisposableObject {
);
}
init() {
void extLogger.log("Registering database panel commands.");
this.push(
commandRunnerWithProgress(
"codeQL.setCurrentDatabase",
this.handleSetCurrentDatabase,
{
title: "Importing database from archive",
},
),
);
this.push(
commandRunnerWithProgress(
"codeQL.setDefaultTourDatabase",
this.handleSetDefaultTourDatabase,
{
title: "Set Default Database for Codespace CodeQL Tour",
},
),
);
this.push(
commandRunnerWithProgress(
"codeQL.upgradeCurrentDatabase",
this.handleUpgradeCurrentDatabase,
{
title: "Upgrading current database",
cancellable: true,
},
),
);
this.push(
commandRunnerWithProgress("codeQL.clearCache", this.handleClearCache, {
title: "Clearing Cache",
}),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.chooseDatabaseFolder",
this.handleChooseDatabaseFolder,
{
title: "Adding database from folder",
},
),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.chooseDatabaseArchive",
this.handleChooseDatabaseArchive,
{
title: "Adding database from archive",
},
),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.chooseDatabaseInternet",
this.handleChooseDatabaseInternet,
{
title: "Adding database from URL",
},
),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.chooseDatabaseGithub",
async (progress: ProgressCallback, token: CancellationToken) => {
const credentials = isCanary() ? this.app.credentials : undefined;
await this.handleChooseDatabaseGithub(credentials, progress, token);
},
{
title: "Adding database from GitHub",
},
),
);
this.push(
commandRunner(
"codeQLDatabases.setCurrentDatabase",
this.handleMakeCurrentDatabase,
),
);
this.push(
commandRunner("codeQLDatabases.sortByName", this.handleSortByName),
);
this.push(
commandRunner(
"codeQLDatabases.sortByDateAdded",
this.handleSortByDateAdded,
),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.removeDatabase",
this.handleRemoveDatabase,
{
title: "Removing database",
cancellable: false,
},
),
);
this.push(
commandRunnerWithProgress(
"codeQLDatabases.upgradeDatabase",
this.handleUpgradeDatabase,
{
title: "Upgrading database",
cancellable: true,
},
),
);
this.push(
commandRunner(
"codeQLDatabases.renameDatabase",
this.handleRenameDatabase,
),
);
this.push(
commandRunner(
"codeQLDatabases.openDatabaseFolder",
this.handleOpenFolder,
),
);
this.push(
commandRunner("codeQLDatabases.addDatabaseSource", this.handleAddSource),
);
this.push(
commandRunner(
"codeQLDatabases.removeOrphanedDatabases",
this.handleRemoveOrphanedDatabases,
),
);
public getCommands(): LocalDatabasesCommands {
return {
"codeQL.chooseDatabaseFolder":
this.handleChooseDatabaseFolderFromPalette.bind(this),
"codeQL.chooseDatabaseArchive":
this.handleChooseDatabaseArchiveFromPalette.bind(this),
"codeQL.chooseDatabaseInternet":
this.handleChooseDatabaseInternet.bind(this),
"codeQL.chooseDatabaseGithub": this.handleChooseDatabaseGithub.bind(this),
"codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this),
"codeQL.setDefaultTourDatabase":
this.handleSetDefaultTourDatabase.bind(this),
"codeQL.upgradeCurrentDatabase":
this.handleUpgradeCurrentDatabase.bind(this),
"codeQL.clearCache": this.handleClearCache.bind(this),
"codeQLDatabases.chooseDatabaseFolder":
this.handleChooseDatabaseFolder.bind(this),
"codeQLDatabases.chooseDatabaseArchive":
this.handleChooseDatabaseArchive.bind(this),
"codeQLDatabases.chooseDatabaseInternet":
this.handleChooseDatabaseInternet.bind(this),
"codeQLDatabases.chooseDatabaseGithub":
this.handleChooseDatabaseGithub.bind(this),
"codeQLDatabases.setCurrentDatabase":
this.handleMakeCurrentDatabase.bind(this),
"codeQLDatabases.sortByName": this.handleSortByName.bind(this),
"codeQLDatabases.sortByDateAdded": this.handleSortByDateAdded.bind(this),
"codeQLDatabases.removeDatabase": this.handleRemoveDatabase.bind(this),
"codeQLDatabases.upgradeDatabase": this.handleUpgradeDatabase.bind(this),
"codeQLDatabases.renameDatabase": this.handleRenameDatabase.bind(this),
"codeQLDatabases.openDatabaseFolder": this.handleOpenFolder.bind(this),
"codeQLDatabases.addDatabaseSource": this.handleAddSource.bind(this),
"codeQLDatabases.removeOrphanedDatabases":
this.handleRemoveOrphanedDatabases.bind(this),
};
}
private handleMakeCurrentDatabase = async (
private async handleMakeCurrentDatabase(
databaseItem: DatabaseItem,
): Promise<void> => {
): Promise<void> {
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
};
}
handleChooseDatabaseFolder = async (
private async chooseDatabaseFolder(
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
): Promise<void> {
try {
await this.chooseAndSetDatabase(true, progress, token);
} catch (e) {
@@ -362,47 +262,73 @@ export class DatabaseUI extends DisposableObject {
)`Failed to choose and set database: ${getErrorMessage(e)}`,
);
}
};
}
private handleSetDefaultTourDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
try {
if (!workspace.workspaceFolders?.length) {
throw new Error("No workspace folder is open.");
} else {
// This specifically refers to the database folder in
// https://github.com/github/codespaces-codeql
const uri = Uri.parse(
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
);
private async handleChooseDatabaseFolder(): Promise<void> {
return withProgress(
async (progress, token) => {
await this.chooseDatabaseFolder(progress, token);
},
{
title: "Adding database from folder",
},
);
}
let databaseItem = this.databaseManager.findDatabaseItem(uri);
const isTutorialDatabase = true;
if (databaseItem === undefined) {
databaseItem = await this.databaseManager.openDatabase(
progress,
token,
uri,
"CodeQL Tutorial Database",
isTutorialDatabase,
private async handleChooseDatabaseFolderFromPalette(): Promise<void> {
return withProgress(
async (progress, token) => {
await this.chooseDatabaseFolder(progress, token);
},
{
title: "Choose a Database from a Folder",
},
);
}
private async handleSetDefaultTourDatabase(): Promise<void> {
return withProgress(
async (progress, token) => {
try {
if (!workspace.workspaceFolders?.length) {
throw new Error("No workspace folder is open.");
} else {
// This specifically refers to the database folder in
// https://github.com/github/codespaces-codeql
const uri = Uri.parse(
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
);
let databaseItem = this.databaseManager.findDatabaseItem(uri);
const isTutorialDatabase = true;
if (databaseItem === undefined) {
databaseItem = await this.databaseManager.openDatabase(
progress,
token,
uri,
"CodeQL Tutorial Database",
isTutorialDatabase,
);
}
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
await this.handleTourDependencies();
}
} catch (e) {
// rethrow and let this be handled by default error handling.
throw new Error(
`Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage(
e,
)}`,
);
}
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
await this.handleTourDependencies();
}
} catch (e) {
// rethrow and let this be handled by default error handling.
throw new Error(
`Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage(
e,
)}`,
);
}
};
},
{
title: "Set Default Database for Codespace CodeQL Tour",
},
);
}
private handleTourDependencies = async (): Promise<void> => {
private async handleTourDependencies(): Promise<void> {
if (!workspace.workspaceFolders?.length) {
throw new Error("No workspace folder is open.");
} else {
@@ -416,9 +342,10 @@ export class DatabaseUI extends DisposableObject {
}
await cli.packInstall(tutorialQueriesPath);
}
};
}
handleRemoveOrphanedDatabases = async (): Promise<void> => {
// Public because it's used in tests
public async handleRemoveOrphanedDatabases(): Promise<void> {
void extLogger.log("Removing orphaned databases from workspace storage.");
let dbDirs = undefined;
@@ -481,12 +408,12 @@ export class DatabaseUI extends DisposableObject {
)}).\nTo delete unused databases, please remove them manually from the storage folder ${dirname}.`,
);
}
};
}
handleChooseDatabaseArchive = async (
private async chooseDatabaseArchive(
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
): Promise<void> {
try {
await this.chooseAndSetDatabase(false, progress, token);
} catch (e: unknown) {
@@ -496,81 +423,130 @@ export class DatabaseUI extends DisposableObject {
)`Failed to choose and set database: ${getErrorMessage(e)}`,
);
}
};
handleChooseDatabaseInternet = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<DatabaseItem | undefined> => {
return await promptImportInternetDatabase(
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
};
handleChooseDatabaseGithub = async (
credentials: Credentials | undefined,
progress: ProgressCallback,
token: CancellationToken,
): Promise<DatabaseItem | undefined> => {
return await promptImportGithubDatabase(
this.databaseManager,
this.storagePath,
credentials,
progress,
token,
this.queryServer?.cliServer,
);
};
async tryUpgradeCurrentDatabase(
progress: ProgressCallback,
token: CancellationToken,
) {
await this.handleUpgradeCurrentDatabase(progress, token);
}
private handleSortByName = async () => {
private async handleChooseDatabaseArchive(): Promise<void> {
return withProgress(
async (progress, token) => {
await this.chooseDatabaseArchive(progress, token);
},
{
title: "Adding database from archive",
},
);
}
private async handleChooseDatabaseArchiveFromPalette(): Promise<void> {
return withProgress(
async (progress, token) => {
await this.chooseDatabaseArchive(progress, token);
},
{
title: "Choose a Database from an Archive",
},
);
}
private async handleChooseDatabaseInternet(): Promise<void> {
return withProgress(
async (progress, token) => {
await promptImportInternetDatabase(
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
},
{
title: "Adding database from URL",
},
);
}
private async handleChooseDatabaseGithub(): Promise<void> {
return withProgress(
async (progress, token) => {
const credentials = isCanary() ? this.app.credentials : undefined;
await promptImportGithubDatabase(
this.databaseManager,
this.storagePath,
credentials,
progress,
token,
this.queryServer?.cliServer,
);
},
{
title: "Adding database from GitHub",
},
);
}
private async handleSortByName() {
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) {
this.treeDataProvider.sortOrder = SortOrder.NameDesc;
} else {
this.treeDataProvider.sortOrder = SortOrder.NameAsc;
}
};
}
private handleSortByDateAdded = async () => {
private async handleSortByDateAdded() {
if (this.treeDataProvider.sortOrder === SortOrder.DateAddedAsc) {
this.treeDataProvider.sortOrder = SortOrder.DateAddedDesc;
} else {
this.treeDataProvider.sortOrder = SortOrder.DateAddedAsc;
}
};
}
private handleUpgradeCurrentDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
await this.handleUpgradeDatabase(
progress,
token,
this.databaseManager.currentDatabaseItem,
[],
private async handleUpgradeCurrentDatabase(): Promise<void> {
return withProgress(
async (progress, token) => {
await this.handleUpgradeDatabaseInternal(
progress,
token,
this.databaseManager.currentDatabaseItem,
[],
);
},
{
title: "Upgrading current database",
cancellable: true,
},
);
};
}
private handleUpgradeDatabase = async (
private async handleUpgradeDatabase(
databaseItem: DatabaseItem | undefined,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> {
return withProgress(
async (progress, token) => {
return await this.handleUpgradeDatabaseInternal(
progress,
token,
databaseItem,
multiSelect,
);
},
{
title: "Upgrading database",
cancellable: true,
},
);
}
private async handleUpgradeDatabaseInternal(
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
): Promise<void> {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) =>
this.handleUpgradeDatabase(progress, token, dbItem, []),
this.handleUpgradeDatabaseInternal(progress, token, dbItem, []),
),
);
}
@@ -602,78 +578,91 @@ export class DatabaseUI extends DisposableObject {
progress,
token,
);
};
}
private handleClearCache = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
if (
this.queryServer !== undefined &&
this.databaseManager.currentDatabaseItem !== undefined
) {
await this.queryServer.clearCacheInDatabase(
this.databaseManager.currentDatabaseItem,
progress,
token,
);
}
};
private async handleClearCache(): Promise<void> {
return withProgress(
async (progress, token) => {
if (
this.queryServer !== undefined &&
this.databaseManager.currentDatabaseItem !== undefined
) {
await this.queryServer.clearCacheInDatabase(
this.databaseManager.currentDatabaseItem,
progress,
token,
);
}
},
{
title: "Clearing cache",
},
);
}
private handleSetCurrentDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
uri: Uri,
): Promise<void> => {
try {
// Assume user has selected an archive if the file has a .zip extension
if (uri.path.endsWith(".zip")) {
await importArchiveDatabase(
uri.toString(true),
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
} else {
await this.setCurrentDatabase(progress, token, uri);
}
} catch (e) {
// rethrow and let this be handled by default error handling.
throw new Error(
`Could not set database to ${basename(
uri.fsPath,
)}. Reason: ${getErrorMessage(e)}`,
);
}
};
private async handleSetCurrentDatabase(uri: Uri): Promise<void> {
return withProgress(
async (progress, token) => {
try {
// Assume user has selected an archive if the file has a .zip extension
if (uri.path.endsWith(".zip")) {
await importArchiveDatabase(
uri.toString(true),
this.databaseManager,
this.storagePath,
progress,
token,
this.queryServer?.cliServer,
);
} else {
await this.setCurrentDatabase(progress, token, uri);
}
} catch (e) {
// rethrow and let this be handled by default error handling.
throw new Error(
`Could not set database to ${basename(
uri.fsPath,
)}. Reason: ${getErrorMessage(e)}`,
);
}
},
{
title: "Importing database from archive",
},
);
}
private handleRemoveDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
private async handleRemoveDatabase(
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) =>
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
),
);
} else {
await this.databaseManager.removeDatabaseItem(
progress,
token,
databaseItem,
);
}
};
): Promise<void> {
return withProgress(
async (progress, token) => {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) =>
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
),
);
} else {
await this.databaseManager.removeDatabaseItem(
progress,
token,
databaseItem,
);
}
},
{
title: "Removing database",
cancellable: false,
},
);
}
private handleRenameDatabase = async (
private async handleRenameDatabase(
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
): Promise<void> {
this.assertSingleDatabase(multiSelect);
const newName = await window.showInputBox({
@@ -684,12 +673,12 @@ export class DatabaseUI extends DisposableObject {
if (newName) {
await this.databaseManager.renameDatabaseItem(databaseItem, newName);
}
};
}
private handleOpenFolder = async (
private async handleOpenFolder(
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
): Promise<void> {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri)),
@@ -697,17 +686,17 @@ export class DatabaseUI extends DisposableObject {
} else {
await env.openExternal(databaseItem.databaseUri);
}
};
}
/**
* Adds the source folder of a CodeQL database to the workspace.
* When a database is first added in the "Databases" view, its source folder is added to the workspace.
* If the source folder is removed from the workspace for some reason, we want to be able to re-add it if need be.
*/
private handleAddSource = async (
private async handleAddSource(
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
): Promise<void> {
if (multiSelect?.length) {
for (const dbItem of multiSelect) {
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
@@ -715,7 +704,7 @@ export class DatabaseUI extends DisposableObject {
} else {
await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem);
}
};
}
/**
* Return the current database directory. If we don't already have a

View File

@@ -12,7 +12,7 @@ import {
isFolderAlreadyInWorkspace,
showBinaryChoiceDialog,
} from "./helpers";
import { ProgressCallback, withProgress } from "./commandRunner";
import { ProgressCallback, withProgress } from "./progress";
import {
zipArchiveScheme,
encodeArchiveBasePath,
@@ -794,74 +794,66 @@ export class DatabaseManager extends DisposableObject {
}
public async loadPersistedState(): Promise<void> {
return withProgress(
{
location: vscode.ProgressLocation.Notification,
},
async (progress, token) => {
const currentDatabaseUri =
this.ctx.workspaceState.get<string>(CURRENT_DB);
const databases = this.ctx.workspaceState.get<PersistedDatabaseItem[]>(
DB_LIST,
[],
return withProgress(async (progress, token) => {
const currentDatabaseUri =
this.ctx.workspaceState.get<string>(CURRENT_DB);
const databases = this.ctx.workspaceState.get<PersistedDatabaseItem[]>(
DB_LIST,
[],
);
let step = 0;
progress({
maxStep: databases.length,
message: "Loading persisted databases",
step,
});
try {
void this.logger.log(
`Found ${databases.length} persisted databases: ${databases
.map((db) => db.uri)
.join(", ")}`,
);
let step = 0;
progress({
maxStep: databases.length,
message: "Loading persisted databases",
step,
});
try {
void this.logger.log(
`Found ${databases.length} persisted databases: ${databases
.map((db) => db.uri)
.join(", ")}`,
);
for (const database of databases) {
progress({
maxStep: databases.length,
message: `Loading ${
database.options?.displayName || "databases"
}`,
step: ++step,
});
for (const database of databases) {
progress({
maxStep: databases.length,
message: `Loading ${database.options?.displayName || "databases"}`,
step: ++step,
});
const databaseItem =
await this.createDatabaseItemFromPersistedState(
progress,
token,
database,
);
try {
await databaseItem.refresh();
await this.registerDatabase(progress, token, databaseItem);
if (currentDatabaseUri === database.uri) {
await this.setCurrentDatabaseItem(databaseItem, true);
}
void this.logger.log(
`Loaded database ${databaseItem.name} at URI ${database.uri}.`,
);
} catch (e) {
// When loading from persisted state, leave invalid databases in the list. They will be
// marked as invalid, and cannot be set as the current database.
void this.logger.log(
`Error loading database ${database.uri}: ${e}.`,
);
const databaseItem = await this.createDatabaseItemFromPersistedState(
progress,
token,
database,
);
try {
await databaseItem.refresh();
await this.registerDatabase(progress, token, databaseItem);
if (currentDatabaseUri === database.uri) {
await this.setCurrentDatabaseItem(databaseItem, true);
}
void this.logger.log(
`Loaded database ${databaseItem.name} at URI ${database.uri}.`,
);
} catch (e) {
// When loading from persisted state, leave invalid databases in the list. They will be
// marked as invalid, and cannot be set as the current database.
void this.logger.log(
`Error loading database ${database.uri}: ${e}.`,
);
}
await this.updatePersistedDatabaseList();
} catch (e) {
// database list had an unexpected type - nothing to be done?
void showAndLogExceptionWithTelemetry(
redactableError(
asError(e),
)`Database list loading failed: ${getErrorMessage(e)}`,
);
}
await this.updatePersistedDatabaseList();
} catch (e) {
// database list had an unexpected type - nothing to be done?
void showAndLogExceptionWithTelemetry(
redactableError(
asError(e),
)`Database list loading failed: ${getErrorMessage(e)}`,
);
}
void this.logger.log("Finished loading persisted databases.");
},
);
void this.logger.log("Finished loading persisted databases.");
});
}
public get databaseItems(): readonly DatabaseItem[] {

View File

@@ -0,0 +1,390 @@
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
import {
CancellationToken,
CancellationTokenSource,
QuickPickItem,
Range,
Uri,
window,
} from "vscode";
import { extLogger } from "./common";
import { MAX_QUERIES } from "./config";
import { gatherQlFiles } from "./pure/files";
import { basename } from "path";
import {
findLanguage,
showAndLogErrorMessage,
showAndLogWarningMessage,
showBinaryChoiceDialog,
} from "./helpers";
import { displayQuickQuery } from "./quick-query";
import { QueryRunner } from "./queryRunner";
import { QueryHistoryManager } from "./query-history/query-history-manager";
import { DatabaseUI } from "./local-databases-ui";
import { ResultsView } from "./interface";
import { DatabaseItem, DatabaseManager } from "./local-databases";
import { createInitialQueryInfo } from "./run-queries-shared";
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
import { WebviewReveal } from "./interface-utils";
import { asError, getErrorMessage } from "./pure/helpers-pure";
import { CodeQLCliServer } from "./cli";
import { LocalQueryCommands } from "./common/commands";
import { App } from "./common/app";
type LocalQueryOptions = {
app: App;
queryRunner: QueryRunner;
queryHistoryManager: QueryHistoryManager;
databaseManager: DatabaseManager;
cliServer: CodeQLCliServer;
databaseUI: DatabaseUI;
localQueryResultsView: ResultsView;
queryStorageDir: string;
};
export function getLocalQueryCommands({
app,
queryRunner,
queryHistoryManager,
databaseManager,
cliServer,
databaseUI,
localQueryResultsView,
queryStorageDir,
}: LocalQueryOptions): LocalQueryCommands {
const runQuery = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
progress,
token,
undefined,
);
},
{
title: "Running query",
cancellable: true,
},
);
const runQueryOnMultipleDatabases = async (uri: Uri | undefined) =>
withProgress(
async (progress, token) =>
await compileAndRunQueryOnMultipleDatabases(
cliServer,
queryRunner,
queryHistoryManager,
databaseManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
progress,
token,
uri,
),
{
title: "Running query on selected databases",
cancellable: true,
},
);
const runQueries = async (_: Uri | undefined, multi: Uri[]) =>
withProgress(
async (progress, token) => {
const maxQueryCount = MAX_QUERIES.getValue() as number;
const [files, dirFound] = await gatherQlFiles(
multi.map((uri) => uri.fsPath),
);
if (files.length > maxQueryCount) {
throw new Error(
`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`,
);
}
// warn user and display selected files when a directory is selected because some ql
// files may be hidden from the user.
if (dirFound) {
const fileString = files.map((file) => basename(file)).join(", ");
const res = await showBinaryChoiceDialog(
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`,
);
if (!res) {
return;
}
}
const queryUris = files.map((path) => Uri.parse(`file:${path}`, true));
// Use a wrapped progress so that messages appear with the queries remaining in it.
let queriesRemaining = queryUris.length;
function wrappedProgress(update: ProgressUpdate) {
const message =
queriesRemaining > 1
? `${queriesRemaining} remaining. ${update.message}`
: update.message;
progress({
...update,
message,
});
}
wrappedProgress({
maxStep: queryUris.length,
step: queryUris.length - queriesRemaining,
message: "",
});
await Promise.all(
queryUris.map(async (uri) =>
compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
false,
uri,
wrappedProgress,
token,
undefined,
).then(() => queriesRemaining--),
),
);
},
{
title: "Running queries",
cancellable: true,
},
);
const quickEval = async (uri: Uri) =>
withProgress(
async (progress, token) => {
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
true,
uri,
progress,
token,
undefined,
);
},
{
title: "Running query",
cancellable: true,
},
);
const codeLensQuickEval = async (uri: Uri, range: Range) =>
withProgress(
async (progress, token) =>
await compileAndRunQuery(
queryRunner,
queryHistoryManager,
databaseUI,
localQueryResultsView,
queryStorageDir,
true,
uri,
progress,
token,
undefined,
range,
),
{
title: "Running query",
cancellable: true,
},
);
const quickQuery = async () =>
withProgress(
async (progress, token) =>
displayQuickQuery(app, cliServer, databaseUI, progress, token),
{
title: "Run Quick Query",
},
);
return {
"codeQL.runQuery": runQuery,
"codeQL.runQueryContextEditor": runQuery,
"codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases,
"codeQL.runQueryOnMultipleDatabasesContextEditor":
runQueryOnMultipleDatabases,
"codeQL.runQueries": runQueries,
"codeQL.quickEval": quickEval,
"codeQL.quickEvalContextEditor": quickEval,
"codeQL.codeLensQuickEval": codeLensQuickEval,
"codeQL.quickQuery": quickQuery,
};
}
export async function compileAndRunQuery(
qs: QueryRunner,
qhm: QueryHistoryManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
quickEval: boolean,
selectedQuery: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
range?: Range,
): Promise<void> {
if (qs !== undefined) {
// If no databaseItem is specified, use the database currently selected in the Databases UI
databaseItem =
databaseItem || (await databaseUI.getDatabaseItem(progress, token));
if (databaseItem === undefined) {
throw new Error("Can't run query without a selected database");
}
const databaseInfo = {
name: databaseItem.name,
databaseUri: databaseItem.databaseUri.toString(),
};
// handle cancellation from the history view.
const source = new CancellationTokenSource();
token.onCancellationRequested(() => source.cancel());
const initialInfo = await createInitialQueryInfo(
selectedQuery,
databaseInfo,
quickEval,
range,
);
const item = new LocalQueryInfo(initialInfo, source);
qhm.addQuery(item);
try {
const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase(
databaseItem,
initialInfo,
queryStorageDir,
progress,
source.token,
undefined,
item,
);
qhm.completeQuery(item, completedQueryInfo);
await showResultsForCompletedQuery(
localQueryResultsView,
item as CompletedLocalQueryInfo,
WebviewReveal.Forced,
);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
} catch (e) {
const err = asError(e);
err.message = `Error running query: ${err.message}`;
item.failureReason = err.message;
throw e;
} finally {
await qhm.refreshTreeView();
source.dispose();
}
}
}
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
}
async function compileAndRunQueryOnMultipleDatabases(
cliServer: CodeQLCliServer,
qs: QueryRunner,
qhm: QueryHistoryManager,
dbm: DatabaseManager,
databaseUI: DatabaseUI,
localQueryResultsView: ResultsView,
queryStorageDir: string,
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(
qs,
qhm,
databaseUI,
localQueryResultsView,
queryStorageDir,
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.");
}
}
export async function showResultsForCompletedQuery(
localQueryResultsView: ResultsView,
query: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
): Promise<void> {
await localQueryResultsView.showResults(query, forceReveal, false);
}

View File

@@ -13,9 +13,9 @@ import {
workspace,
} from "vscode";
import { DisposableObject } from "../pure/disposable-object";
import { commandRunner } from "../commandRunner";
import { extLogger } from "../common";
import { getErrorMessage } from "../pure/helpers-pure";
import { SummaryLanguageSupportCommands } from "../common/commands";
/** A `Position` within a specified file on disk. */
interface PositionInFile {
@@ -73,8 +73,12 @@ export class SummaryLanguageSupport extends DisposableObject {
this.handleDidCloseTextDocument.bind(this),
),
);
}
this.push(commandRunner("codeQL.gotoQL", this.handleGotoQL.bind(this)));
public getCommands(): SummaryLanguageSupportCommands {
return {
"codeQL.gotoQL": this.handleGotoQL.bind(this),
};
}
/**

View File

@@ -15,6 +15,7 @@ import {
} from "../config";
import { DisposableObject } from "../pure/disposable-object";
import { MockGitHubApiServer } from "./mock-gh-api-server";
import { MockGitHubApiServerCommands } from "../common/commands";
/**
* "Interface" to the mock GitHub API server which implements VSCode interactions, such as
@@ -34,6 +35,19 @@ export class VSCodeMockGitHubApiServer extends DisposableObject {
this.setupConfigListener();
}
public getCommands(): MockGitHubApiServerCommands {
return {
"codeQL.mockGitHubApiServer.startRecording":
this.startRecording.bind(this),
"codeQL.mockGitHubApiServer.saveScenario": this.saveScenario.bind(this),
"codeQL.mockGitHubApiServer.cancelRecording":
this.cancelRecording.bind(this),
"codeQL.mockGitHubApiServer.loadScenario": this.loadScenario.bind(this),
"codeQL.mockGitHubApiServer.unloadScenario":
this.unloadScenario.bind(this),
};
}
public async startServer(): Promise<void> {
this.server.startServer();
}

View File

@@ -0,0 +1,70 @@
/**
* Contains a generic implementation of typed commands.
*
* This allows different parts of the extension to register commands with a certain type,
* and then allow other parts to call those commands in a well-typed manner.
*/
import { Disposable } from "./Disposable";
/**
* A command function is a completely untyped command.
*/
export type CommandFunction = (...args: any[]) => Promise<unknown>;
/**
* The command manager basically takes a single input, the type
* of all the known commands. The second parameter is provided by
* default (and should not be needed by the caller) it is a
* technicality to allow the type system to look up commands.
*/
export class CommandManager<
Commands extends Record<string, CommandFunction>,
CommandName extends keyof Commands & string = keyof Commands & string,
> implements Disposable
{
// TODO: should this be a map?
// TODO: handle multiple command names
private commands: Disposable[] = [];
constructor(
private readonly commandRegister: <T extends CommandName>(
commandName: T,
fn: NonNullable<Commands[T]>,
) => Disposable,
private readonly commandExecute: <T extends CommandName>(
commandName: T,
...args: Parameters<Commands[T]>
) => Promise<Awaited<ReturnType<Commands[T]>>>,
) {}
/**
* Register a command with the specified name and implementation.
*/
register<T extends CommandName>(
commandName: T,
definition: NonNullable<Commands[T]>,
): void {
this.commands.push(this.commandRegister(commandName, definition));
}
/**
* Execute a command with the specified name and the provided arguments.
*/
execute<T extends CommandName>(
commandName: T,
...args: Parameters<Commands[T]>
): Promise<Awaited<ReturnType<Commands[T]>>> {
return this.commandExecute(commandName, ...args);
}
/**
* Dispose the manager, disposing all the registered commands.
*/
dispose(): void {
this.commands.forEach((cmd) => {
cmd.dispose();
});
this.commands = [];
}
}

View File

@@ -0,0 +1,7 @@
/**
* This interface mirrors the vscode.Disaposable class, so that
* the command manager does not depend on vscode directly.
*/
export interface Disposable {
dispose(): void;
}

View File

@@ -0,0 +1 @@
export * from "./CommandManager";

View File

@@ -5,11 +5,43 @@ import {
showAndLogInformationMessage,
} from "./helpers";
import { QuickPickItem, window } from "vscode";
import { ProgressCallback, UserCancellationException } from "./commandRunner";
import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "./progress";
import { extLogger } from "./common";
import { asError, getErrorStack } from "./pure/helpers-pure";
import { redactableError } from "./pure/errors";
import { PACKS_BY_QUERY_LANGUAGE } from "./common/query-language";
import { PackagingCommands } from "./common/commands";
type PackagingOptions = {
cliServer: CodeQLCliServer;
};
export function getPackagingCommands({
cliServer,
}: PackagingOptions): PackagingCommands {
return {
"codeQL.installPackDependencies": async () =>
withProgress(
async (progress: ProgressCallback) =>
await handleInstallPackDependencies(cliServer, progress),
{
title: "Installing pack dependencies",
},
),
"codeQL.downloadPacks": async () =>
withProgress(
async (progress: ProgressCallback) =>
await handleDownloadPacks(cliServer, progress),
{
title: "Downloading packs",
},
),
};
}
/**
* Prompts user to choose packs to download, and downloads them.

View File

@@ -0,0 +1,128 @@
import {
CancellationToken,
ProgressLocation,
ProgressOptions as VSCodeProgressOptions,
window as Window,
} from "vscode";
export class UserCancellationException extends Error {
/**
* @param message The error message
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
*/
constructor(message?: string, public readonly silent = false) {
super(message);
}
}
export interface ProgressUpdate {
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
export type ProgressCallback = (p: ProgressUpdate) => void;
// Make certain properties within a type optional
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
export type ProgressOptions = Optional<VSCodeProgressOptions, "location">;
/**
* A task that reports progress.
*
* @param progress a progress handler function. Call this
* function with a `ProgressUpdate` instance in order to
* denote some progress being achieved on this task.
* @param token a cancellation token
*/
export type ProgressTask<R> = (
progress: ProgressCallback,
token: CancellationToken,
) => Thenable<R>;
/**
* This mediates between the kind of progress callbacks we want to
* write (where we *set* current progress position and give
* `maxSteps`) and the kind vscode progress api expects us to write
* (which increment progress by a certain amount out of 100%).
*/
export function withProgress<R>(
task: ProgressTask<R>,
{
location = ProgressLocation.Notification,
title,
cancellable,
}: ProgressOptions = {},
): Thenable<R> {
let progressAchieved = 0;
return Window.withProgress(
{
location,
title,
cancellable,
},
(progress, token) => {
return task((p) => {
const { message, step, maxStep } = p;
const increment = (100 * (step - progressAchieved)) / maxStep;
progressAchieved = step;
progress.report({ message, increment });
}, token);
},
);
}
/**
* Displays a progress monitor that indicates how much progess has been made
* reading from a stream.
*
* @param readable The stream to read progress from
* @param messagePrefix A prefix for displaying the message
* @param totalNumBytes Total number of bytes in this stream
* @param progress The progress callback used to set messages
*/
export function reportStreamProgress(
readable: NodeJS.ReadableStream,
messagePrefix: string,
totalNumBytes?: number,
progress?: ProgressCallback,
) {
if (progress && totalNumBytes) {
let numBytesDownloaded = 0;
const bytesToDisplayMB = (numBytes: number): string =>
`${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
progress({
step: numBytesDownloaded,
maxStep: totalNumBytes,
message: `${messagePrefix} [${bytesToDisplayMB(
numBytesDownloaded,
)} of ${bytesToDisplayMB(totalNumBytes)}]`,
});
};
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();
readable.on("data", (data) => {
numBytesDownloaded += data.length;
updateProgress();
});
} else if (progress) {
progress({
step: 1,
maxStep: 2,
message: `${messagePrefix} (Size unknown)`,
});
}
}

View File

@@ -1,6 +1,6 @@
export class RedactableError extends Error {
constructor(
cause: Error | undefined,
cause: ErrorLike | undefined,
private readonly strings: TemplateStringsArray,
private readonly values: unknown[],
) {
@@ -54,19 +54,34 @@ export function redactableError(
...values: unknown[]
): RedactableError;
export function redactableError(
error: Error,
error: ErrorLike,
): (strings: TemplateStringsArray, ...values: unknown[]) => RedactableError;
export function redactableError(
errorOrStrings: Error | TemplateStringsArray,
errorOrStrings: ErrorLike | TemplateStringsArray,
...values: unknown[]
):
| ((strings: TemplateStringsArray, ...values: unknown[]) => RedactableError)
| RedactableError {
if (errorOrStrings instanceof Error) {
if (isErrorLike(errorOrStrings)) {
return (strings: TemplateStringsArray, ...values: unknown[]) =>
new RedactableError(errorOrStrings, strings, values);
} else {
return new RedactableError(undefined, errorOrStrings, values);
}
}
export interface ErrorLike {
message: string;
stack?: string;
}
function isErrorLike(error: any): error is ErrorLike {
if (
typeof error.message === "string" &&
(error.stack === undefined || typeof error.stack === "string")
) {
return true;
}
return false;
}

View File

@@ -67,3 +67,8 @@ export function pathsEqual(
}
return path1 === path2;
}
export async function readDirFullPaths(path: string): Promise<string[]> {
const baseNames = await readdir(path);
return baseNames.map((baseName) => join(path, baseName));
}

View File

@@ -12,6 +12,8 @@ import {
VariantAnalysisScannedRepositoryState,
} from "../variant-analysis/shared/variant-analysis";
import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort";
import { ErrorLike } from "./errors";
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
/**
* This module contains types and code that are shared between
@@ -182,14 +184,13 @@ export type IntoResultsViewMsg =
* A message sent from the results view.
*/
export type FromResultsViewMsg =
| CommonFromViewMessages
| ViewSourceFileMsg
| ToggleDiagnostics
| ChangeRawResultsSortMsg
| ChangeInterpretedResultsSortMsg
| ViewLoadedMsg
| ChangePage
| OpenFileMsg
| TelemetryMessage;
| OpenFileMsg;
/**
* Message from the results view to open a database source
@@ -231,6 +232,21 @@ interface ViewLoadedMsg {
viewName: string;
}
interface TelemetryMessage {
t: "telemetry";
action: string;
}
interface UnhandledErrorMessage {
t: "unhandledError";
error: ErrorLike;
}
type CommonFromViewMessages =
| ViewLoadedMsg
| TelemetryMessage
| UnhandledErrorMessage;
/**
* Message from the results view to signal a request to change the
* page.
@@ -287,11 +303,10 @@ interface ChangeInterpretedResultsSortMsg {
* Message from the compare view to the extension.
*/
export type FromCompareViewMessage =
| ViewLoadedMsg
| CommonFromViewMessages
| ChangeCompareMessage
| ViewSourceFileMsg
| OpenQueryMessage
| TelemetryMessage;
| OpenQueryMessage;
/**
* Message from the compare view to request opening a query.
@@ -434,9 +449,9 @@ export interface CancelVariantAnalysisMessage {
t: "cancelVariantAnalysis";
}
export interface TelemetryMessage {
t: "telemetry";
action: string;
export interface ShowDataFlowPathsMessage {
t: "showDataFlowPaths";
dataFlowPaths: DataFlowPaths;
}
export type ToVariantAnalysisMessage =
@@ -445,7 +460,7 @@ export type ToVariantAnalysisMessage =
| SetRepoStatesMessage;
export type FromVariantAnalysisMessage =
| ViewLoadedMsg
| CommonFromViewMessages
| RequestRepositoryResultsMessage
| OpenQueryFileMessage
| OpenQueryTextMessage
@@ -453,4 +468,13 @@ export type FromVariantAnalysisMessage =
| ExportResultsMessage
| OpenLogsMessage
| CancelVariantAnalysisMessage
| TelemetryMessage;
| ShowDataFlowPathsMessage;
export interface SetDataFlowPathsMessage {
t: "setDataFlowPaths";
dataFlowPaths: DataFlowPaths;
}
export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
export type FromDataFlowPathsMessage = CommonFromViewMessages;

View File

@@ -126,6 +126,7 @@ export interface RunQueryParams {
singletonExternalInputs: Record<string, string>;
dilPath?: string;
logPath?: string;
extensionPacks?: string[];
}
export interface RunQueryResult {

View File

@@ -2,6 +2,10 @@ import { join } from "path";
import { pathExists } from "fs-extra";
export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"];
export const QLPACK_LOCK_FILENAMES = [
"qlpack.lock.yml",
"codeql-pack.lock.yml",
];
export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0];
export async function getQlPackPath(

View File

@@ -0,0 +1,79 @@
import { commands, Uri, window } from "vscode";
import { CodeQLCliServer } from "./cli";
import { QueryRunner } from "./queryRunner";
import { basename, join } from "path";
import { getErrorMessage } from "./pure/helpers-pure";
import { redactableError } from "./pure/errors";
import { showAndLogExceptionWithTelemetry } from "./helpers";
import { QueryEditorCommands } from "./common/commands";
type QueryEditorOptions = {
queryRunner: QueryRunner;
cliServer: CodeQLCliServer;
qhelpTmpDir: string;
};
export function getQueryEditorCommands({
queryRunner,
cliServer,
qhelpTmpDir,
}: QueryEditorOptions): QueryEditorCommands {
const openReferencedFileCommand = async (selectedQuery: Uri) =>
await openReferencedFile(queryRunner, cliServer, selectedQuery);
return {
"codeQL.openReferencedFile": openReferencedFileCommand,
// Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command
"codeQL.openReferencedFileContextEditor": openReferencedFileCommand,
// Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command
"codeQL.openReferencedFileContextExplorer": openReferencedFileCommand,
"codeQL.previewQueryHelp": async (selectedQuery: Uri) =>
await previewQueryHelp(cliServer, qhelpTmpDir, selectedQuery),
};
}
async function previewQueryHelp(
cliServer: CodeQLCliServer,
qhelpTmpDir: string,
selectedQuery: Uri,
): Promise<void> {
// selectedQuery is unpopulated when executing through the command palette
const pathToQhelp = selectedQuery
? selectedQuery.fsPath
: window.activeTextEditor?.document.uri.fsPath;
if (pathToQhelp) {
// Create temporary directory
const relativePathToMd = `${basename(pathToQhelp, ".qhelp")}.md`;
const absolutePathToMd = join(qhelpTmpDir, relativePathToMd);
const uri = Uri.file(absolutePathToMd);
try {
await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd);
await commands.executeCommand("markdown.showPreviewToSide", uri);
} catch (e) {
const errorMessage = getErrorMessage(e).includes(
"Generating qhelp in markdown",
)
? redactableError`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.`
: redactableError`Could not open a preview of the generated file (${absolutePathToMd}).`;
void showAndLogExceptionWithTelemetry(errorMessage, {
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`,
});
}
}
}
async function openReferencedFile(
qs: QueryRunner,
cliServer: CodeQLCliServer,
selectedQuery: Uri,
): Promise<void> {
// If no file is selected, the path of the file in the editor is selected
const path =
selectedQuery?.fsPath || window.activeTextEditor?.document.uri.fsPath;
if (qs !== undefined && path) {
const resolved = await cliServer.resolveQlref(path);
const uri = Uri.file(resolved.resolvedPath);
await window.showTextDocument(uri, { preview: false });
}
}

View File

@@ -111,9 +111,14 @@ export class HistoryTreeDataProvider
return "remoteResultsItem";
}
case QueryStatus.Failed:
return element.t === "local"
? "cancelledResultsItem"
: "cancelledRemoteResultsItem";
if (element.t === "local") {
return "cancelledResultsItem";
} else if (element.variantAnalysis.actionsWorkflowRunId === undefined) {
return "cancelledRemoteResultsItemWithoutLogs";
} else {
return "cancelledRemoteResultsItem";
}
default:
assertNever(element.status);
}

View File

@@ -0,0 +1,4 @@
export interface QueryHistoryDirs {
localQueriesDirPath: string;
variantAnalysesDirPath: string;
}

View File

@@ -25,7 +25,6 @@ import {
import { extLogger } from "../common";
import { URLSearchParams } from "url";
import { DisposableObject } from "../pure/disposable-object";
import { commandRunner } from "../commandRunner";
import { ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from "../pure/time";
import {
asError,
@@ -49,7 +48,7 @@ import {
import {
deserializeQueryHistory,
serializeQueryHistory,
} from "../query-serialization";
} from "./store/query-history-store";
import { pathExists } from "fs-extra";
import { CliVersionConstraint } from "../cli";
import { HistoryItemLabelProvider } from "./history-item-label-provider";
@@ -65,6 +64,8 @@ import { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
import { getTotalResultCount } from "../variant-analysis/shared/variant-analysis";
import { HistoryTreeDataProvider } from "./history-tree-data-provider";
import { redactableError } from "../pure/errors";
import { QueryHistoryDirs } from "./query-history-dirs";
import { QueryHistoryCommands } from "../common/commands";
/**
* query-history-manager.ts
@@ -139,7 +140,7 @@ export class QueryHistoryManager extends DisposableObject {
private readonly localQueriesResultsView: ResultsView,
private readonly variantAnalysisManager: VariantAnalysisManager,
private readonly evalLogViewer: EvalLogViewer,
private readonly queryStorageDir: string,
private readonly queryHistoryDirs: QueryHistoryDirs,
ctx: ExtensionContext,
private readonly queryHistoryConfigListener: QueryHistoryConfig,
private readonly labelProvider: HistoryItemLabelProvider,
@@ -200,141 +201,6 @@ export class QueryHistoryManager extends DisposableObject {
}),
);
void extLogger.log("Registering query history panel commands.");
this.push(
commandRunner(
"codeQLQueryHistory.openQuery",
this.handleOpenQuery.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.removeHistoryItem",
this.handleRemoveHistoryItem.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.sortByName",
this.handleSortByName.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.sortByDate",
this.handleSortByDate.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.sortByCount",
this.handleSortByCount.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.renameItem",
this.handleRenameItem.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.compareWith",
this.handleCompareWith.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.showQueryLog",
this.handleShowQueryLog.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.openQueryDirectory",
this.handleOpenQueryDirectory.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.showEvalLog",
this.handleShowEvalLog.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.showEvalLogSummary",
this.handleShowEvalLogSummary.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.showEvalLogViewer",
this.handleShowEvalLogViewer.bind(this),
),
);
this.push(
commandRunner("codeQLQueryHistory.cancel", this.handleCancel.bind(this)),
);
this.push(
commandRunner(
"codeQLQueryHistory.showQueryText",
this.handleShowQueryText.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.exportResults",
this.handleExportResults.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.viewCsvResults",
this.handleViewCsvResults.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.viewCsvAlerts",
this.handleViewCsvAlerts.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.viewSarifAlerts",
this.handleViewSarifAlerts.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.viewDil",
this.handleViewDil.bind(this),
),
);
this.push(
commandRunner(
"codeQLQueryHistory.itemClicked",
async (item: LocalQueryInfo) => {
return this.handleItemClicked(item, [item]);
},
),
);
this.push(
commandRunner(
"codeQLQueryHistory.openOnGithub",
async (item: LocalQueryInfo) => {
return this.handleOpenOnGithub(item, [item]);
},
),
);
this.push(
commandRunner(
"codeQLQueryHistory.copyRepoList",
this.handleCopyRepoList.bind(this),
),
);
// There are two configuration items that affect the query history:
// 1. The ttl for query history items.
// 2. The default label for query history items.
@@ -369,6 +235,48 @@ export class QueryHistoryManager extends DisposableObject {
this.registerToVariantAnalysisEvents();
}
public getCommands(): QueryHistoryCommands {
return {
"codeQLQueryHistory.sortByName": this.handleSortByName.bind(this),
"codeQLQueryHistory.sortByDate": this.handleSortByDate.bind(this),
"codeQLQueryHistory.sortByCount": this.handleSortByCount.bind(this),
"codeQLQueryHistory.openQueryTitleMenu": this.handleOpenQuery.bind(this),
"codeQLQueryHistory.openQueryContextMenu":
this.handleOpenQuery.bind(this),
"codeQLQueryHistory.removeHistoryItemTitleMenu":
this.handleRemoveHistoryItem.bind(this),
"codeQLQueryHistory.removeHistoryItemContextMenu":
this.handleRemoveHistoryItem.bind(this),
"codeQLQueryHistory.removeHistoryItemContextInline":
this.handleRemoveHistoryItem.bind(this),
"codeQLQueryHistory.renameItem": this.handleRenameItem.bind(this),
"codeQLQueryHistory.compareWith": this.handleCompareWith.bind(this),
"codeQLQueryHistory.showEvalLog": this.handleShowEvalLog.bind(this),
"codeQLQueryHistory.showEvalLogSummary":
this.handleShowEvalLogSummary.bind(this),
"codeQLQueryHistory.showEvalLogViewer":
this.handleShowEvalLogViewer.bind(this),
"codeQLQueryHistory.showQueryLog": this.handleShowQueryLog.bind(this),
"codeQLQueryHistory.showQueryText": this.handleShowQueryText.bind(this),
"codeQLQueryHistory.openQueryDirectory":
this.handleOpenQueryDirectory.bind(this),
"codeQLQueryHistory.cancel": this.handleCancel.bind(this),
"codeQLQueryHistory.exportResults": this.handleExportResults.bind(this),
"codeQLQueryHistory.viewCsvResults": this.handleViewCsvResults.bind(this),
"codeQLQueryHistory.viewCsvAlerts": this.handleViewCsvAlerts.bind(this),
"codeQLQueryHistory.viewSarifAlerts":
this.handleViewSarifAlerts.bind(this),
"codeQLQueryHistory.viewDil": this.handleViewDil.bind(this),
"codeQLQueryHistory.itemClicked": this.handleItemClicked.bind(this),
"codeQLQueryHistory.openOnGithub": this.handleOpenOnGithub.bind(this),
"codeQLQueryHistory.copyRepoList": this.handleCopyRepoList.bind(this),
"codeQL.exportSelectedVariantAnalysisResults":
this.exportSelectedVariantAnalysisResults.bind(this),
};
}
public completeQuery(info: LocalQueryInfo, results: QueryWithResults): void {
info.completeThisQuery(results);
this._onDidCompleteQuery.fire(info);
@@ -389,7 +297,7 @@ export class QueryHistoryManager extends DisposableObject {
ONE_HOUR_IN_MS,
TWO_HOURS_IN_MS,
queryHistoryConfigListener.ttlInMillis,
this.queryStorageDir,
this.queryHistoryDirs,
qhm,
ctx,
),
@@ -509,8 +417,7 @@ export class QueryHistoryManager extends DisposableObject {
}
if (finalSingleItem.t === "variant-analysis") {
await commands.executeCommand(
"codeQL.openVariantAnalysisQueryFile",
await this.variantAnalysisManager.openQueryFile(
finalSingleItem.variantAnalysis.id,
);
return;
@@ -555,7 +462,6 @@ export class QueryHistoryManager extends DisposableObject {
!(await pathExists(item.completedQuery?.query.querySaveDir))
) {
this.treeDataProvider.remove(item);
item.completedQuery?.dispose();
}
}),
);
@@ -576,7 +482,6 @@ export class QueryHistoryManager extends DisposableObject {
// Removing in progress local queries is not supported. They must be cancelled first.
if (item.status !== QueryStatus.InProgress) {
this.treeDataProvider.remove(item);
item.completedQuery?.dispose();
// User has explicitly asked for this query to be removed.
// We need to delete it from disk as well.
@@ -992,8 +897,7 @@ export class QueryHistoryManager extends DisposableObject {
if (item.t === "local") {
item.cancel();
} else if (item.t === "variant-analysis") {
await commands.executeCommand(
"codeQL.cancelVariantAnalysis",
await this.variantAnalysisManager.cancelVariantAnalysis(
item.variantAnalysis.id,
);
} else {
@@ -1019,8 +923,7 @@ export class QueryHistoryManager extends DisposableObject {
}
if (finalSingleItem.t === "variant-analysis") {
await commands.executeCommand(
"codeQL.openVariantAnalysisQueryText",
await this.variantAnalysisManager.openQueryText(
finalSingleItem.variantAnalysis.id,
);
return;
@@ -1222,12 +1125,27 @@ export class QueryHistoryManager extends DisposableObject {
return;
}
await commands.executeCommand(
"codeQL.exportVariantAnalysisResults",
await this.variantAnalysisManager.exportResults(
finalSingleItem.variantAnalysis.id,
);
}
/**
* Exports the results of the currently-selected variant analysis.
*/
async exportSelectedVariantAnalysisResults(): Promise<void> {
const queryHistoryItem = this.getCurrentQueryHistoryItem();
if (!queryHistoryItem || queryHistoryItem.t !== "variant-analysis") {
throw new Error(
"No variant analysis results currently open. To open results, click an item in the query history view.",
);
}
await this.variantAnalysisManager.exportResults(
queryHistoryItem.variantAnalysis.id,
);
}
addQuery(item: QueryHistoryInfo) {
this.treeDataProvider.pushQuery(item);
this.updateTreeViewSelectionIfVisible();

View File

@@ -1,8 +1,10 @@
import { pathExists, readdir, stat, remove, readFile } from "fs-extra";
import { pathExists, stat, remove, readFile } from "fs-extra";
import { EOL } from "os";
import { join } from "path";
import { Disposable, ExtensionContext } from "vscode";
import { extLogger } from "../common";
import { readDirFullPaths } from "../pure/files";
import { QueryHistoryDirs } from "./query-history-dirs";
import { QueryHistoryManager } from "./query-history-manager";
const LAST_SCRUB_TIME_KEY = "lastScrubTime";
@@ -23,14 +25,14 @@ type Counter = {
* @param wakeInterval How often to check to see if the job should run.
* @param throttleTime How often to actually run the job.
* @param maxQueryTime The maximum age of a query before is ready for deletion.
* @param queryDirectory The directory containing all queries.
* @param queryHistoryDirs The directories containing all query history information.
* @param ctx The extension context.
*/
export function registerQueryHistoryScrubber(
wakeInterval: number,
throttleTime: number,
maxQueryTime: number,
queryDirectory: string,
queryHistoryDirs: QueryHistoryDirs,
qhm: QueryHistoryManager,
ctx: ExtensionContext,
@@ -42,7 +44,7 @@ export function registerQueryHistoryScrubber(
wakeInterval,
throttleTime,
maxQueryTime,
queryDirectory,
queryHistoryDirs,
qhm,
ctx,
counter,
@@ -58,7 +60,7 @@ export function registerQueryHistoryScrubber(
async function scrubQueries(
throttleTime: number,
maxQueryTime: number,
queryDirectory: string,
queryHistoryDirs: QueryHistoryDirs,
qhm: QueryHistoryManager,
ctx: ExtensionContext,
counter?: Counter,
@@ -74,18 +76,33 @@ async function scrubQueries(
let scrubCount = 0; // total number of directories deleted
try {
counter?.increment();
void extLogger.log("Scrubbing query directory. Removing old queries.");
if (!(await pathExists(queryDirectory))) {
void extLogger.log(
"Cleaning up query history directories. Removing old entries.",
);
if (!(await pathExists(queryHistoryDirs.localQueriesDirPath))) {
void extLogger.log(
`Cannot scrub. Query directory does not exist: ${queryDirectory}`,
`Cannot clean up query history directories. Local queries directory does not exist: ${queryHistoryDirs.localQueriesDirPath}`,
);
return;
}
if (!(await pathExists(queryHistoryDirs.variantAnalysesDirPath))) {
void extLogger.log(
`Cannot clean up query history directories. Variant analyses directory does not exist: ${queryHistoryDirs.variantAnalysesDirPath}`,
);
return;
}
const baseNames = await readdir(queryDirectory);
const localQueryDirPaths = await readDirFullPaths(
queryHistoryDirs.localQueriesDirPath,
);
const variantAnalysisDirPaths = await readDirFullPaths(
queryHistoryDirs.variantAnalysesDirPath,
);
const allDirPaths = [...localQueryDirPaths, ...variantAnalysisDirPaths];
const errors: string[] = [];
for (const baseName of baseNames) {
const dir = join(queryDirectory, baseName);
for (const dir of allDirPaths) {
const scrubResult = await scrubDirectory(dir, now, maxQueryTime);
if (scrubResult.errorMsg) {
errors.push(scrubResult.errorMsg);

View File

@@ -1,18 +1,18 @@
import { pathExists, readFile, remove, mkdir, writeFile } from "fs-extra";
import { dirname } from "path";
import { showAndLogExceptionWithTelemetry } from "./helpers";
import { showAndLogExceptionWithTelemetry } from "../../helpers";
import {
asError,
asyncFilter,
getErrorMessage,
getErrorStack,
} from "./pure/helpers-pure";
import { CompletedQueryInfo, LocalQueryInfo } from "./query-results";
import { QueryHistoryInfo } from "./query-history/query-history-info";
import { QueryEvaluationInfo } from "./run-queries-shared";
import { QueryResultType } from "./pure/legacy-messages";
import { redactableError } from "./pure/errors";
} from "../../pure/helpers-pure";
import { CompletedQueryInfo, LocalQueryInfo } from "../../query-results";
import { QueryHistoryInfo } from "../query-history-info";
import { QueryEvaluationInfo } from "../../run-queries-shared";
import { QueryResultType } from "../../pure/legacy-messages";
import { redactableError } from "../../pure/errors";
export async function deserializeQueryHistory(
fsPath: string,
@@ -55,10 +55,6 @@ export async function deserializeQueryHistory(
q.completedQuery.query,
QueryEvaluationInfo.prototype,
);
// deserialized queries do not need to be disposed
q.completedQuery.dispose = () => {
/**/
};
// Previously, there was a typo in the completedQuery type. There was a field
// `sucessful` and it was renamed to `successful`. We need to handle this case.

View File

@@ -55,11 +55,6 @@ export class CompletedQueryInfo implements QueryWithResults {
readonly logFileLocation?: string;
resultCount: number;
/**
* This dispose method is called when the query is removed from the history view.
*/
dispose: () => void;
/**
* Map from result set name to SortedResultSetInfo.
*/
@@ -85,10 +80,6 @@ export class CompletedQueryInfo implements QueryWithResults {
this.message = evaluation.message;
this.successful = evaluation.successful;
// Use the dispose method from the evaluation.
// The dispose will clean up any additional log locations that this
// query may have created.
this.dispose = evaluation.dispose;
this.sortedResultsInfo = {};
this.resultCount = 0;

View File

@@ -1,5 +1,5 @@
import { CancellationToken } from "vscode";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
clearCache,

View File

@@ -1,4 +1,3 @@
import { dirname } from "path";
import { ensureFile } from "fs-extra";
import { DisposableObject } from "../pure/disposable-object";
@@ -12,9 +11,7 @@ import {
ProgressMessage,
WithProgressId,
} from "../pure/new-messages";
import * as messages from "../pure/new-messages";
import { ProgressCallback, ProgressTask } from "../commandRunner";
import { findQueryLogFile } from "../run-queries-shared";
import { ProgressCallback, ProgressTask } from "../progress";
import { ServerProcess } from "../json-rpc-server";
type ServerOpts = {
@@ -53,7 +50,7 @@ export class QueryServerClient extends DisposableObject {
this.queryServerStartListeners.push(e);
};
public activeQueryLogFile: string | undefined;
public activeQueryLogger: Logger;
constructor(
readonly config: QueryServerConfig,
@@ -62,6 +59,9 @@ export class QueryServerClient extends DisposableObject {
withProgressReporting: WithProgressReporting,
) {
super();
// Since no query is active when we initialize, just point the "active query logger" to the
// default logger.
this.activeQueryLogger = this.logger;
// When the query server configuration changes, restart the query server.
if (config.onDidChangeConfiguration !== undefined) {
this.push(
@@ -167,9 +167,8 @@ export class QueryServerClient extends DisposableObject {
args,
this.logger,
(data) =>
this.logger.log(data.toString(), {
this.activeQueryLogger.log(data.toString(), {
trailingNewline: false,
additionalLogLocation: this.activeQueryLogFile,
}),
undefined, // no listener for stdout
progressReporter,
@@ -210,7 +209,6 @@ export class QueryServerClient extends DisposableObject {
const id = this.nextProgress++;
this.progressCallbacks[id] = progress;
this.updateActiveQuery(type.method, parameter);
try {
if (this.serverProcess === undefined) {
throw new Error("No query server process found.");
@@ -224,20 +222,4 @@ export class QueryServerClient extends DisposableObject {
delete this.progressCallbacks[id];
}
}
/**
* Updates the active query every time there is a new request to compile.
* The active query is used to specify the side log.
*
* This isn't ideal because in situations where there are queries running
* in parallel, each query's log messages are interleaved. Fixing this
* properly will require a change in the query server.
*/
private updateActiveQuery(method: string, parameter: any): void {
if (method === messages.runQuery.method) {
this.activeQueryLogFile = findQueryLogFile(
dirname(dirname((parameter as messages.RunQueryParams).outputPath)),
);
}
}
}

View File

@@ -1,7 +1,7 @@
import { join } from "path";
import { CancellationToken } from "vscode";
import * as cli from "../cli";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
getOnDiskWorkspaceFolders,
@@ -9,7 +9,7 @@ import {
showAndLogWarningMessage,
tryGetQueryMetadata,
} from "../helpers";
import { extLogger } from "../common";
import { extLogger, TeeLogger } from "../common";
import * as messages from "../pure/new-messages";
import { QueryResultType } from "../pure/legacy-messages";
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
@@ -70,6 +70,10 @@ export async function compileAndRunQueryAgainstDatabase(
: { query: {} };
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
const extensionPacks = (await qs.cliServer.useExtensionPacks())
? Object.keys(await qs.cliServer.resolveQlpacks(diskWorkspaceFolders, true))
: undefined;
const db = dbItem.databaseUri.fsPath;
const logPath = queryInfo ? query.evalLogPath : undefined;
const queryToRun: messages.RunQueryParams = {
@@ -82,10 +86,17 @@ export async function compileAndRunQueryAgainstDatabase(
dilPath: query.dilPath,
logPath,
target,
extensionPacks,
};
const logger = new TeeLogger(qs.logger, query.logPath);
await query.createTimestampFile();
let result: messages.RunQueryResult | undefined;
try {
// Update the active query logger every time there is a new request to compile.
// This isn't ideal because in situations where there are queries running
// in parallel, each query's log messages are interleaved. Fixing this
// properly will require a change in the query server.
qs.activeQueryLogger = logger;
result = await qs.sendRequest(
messages.runQuery,
queryToRun,
@@ -100,7 +111,7 @@ export async function compileAndRunQueryAgainstDatabase(
} finally {
if (queryInfo) {
if (await query.hasEvalLog()) {
await query.addQueryLogs(queryInfo, qs.cliServer, qs.logger);
await query.addQueryLogs(queryInfo, qs.cliServer, logger);
} else {
void showAndLogWarningMessage(
`Failed to write structured evaluator log to ${query.evalLogPath}.`,
@@ -155,8 +166,5 @@ export async function compileAndRunQueryAgainstDatabase(
},
message,
successful,
dispose: () => {
qs.logger.removeAdditionalLogLocation(undefined);
},
};
}

View File

@@ -1,6 +1,6 @@
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "./cli";
import { ProgressCallback } from "./commandRunner";
import { ProgressCallback } from "./progress";
import { DatabaseItem } from "./local-databases";
import { InitialQueryInfo, LocalQueryInfo } from "./query-results";
import { QueryWithResults } from "./run-queries-shared";

View File

@@ -1,13 +1,7 @@
import { ensureDir, writeFile, pathExists, readFile } from "fs-extra";
import { dump, load } from "js-yaml";
import { basename, join } from "path";
import {
CancellationToken,
ExtensionContext,
window as Window,
workspace,
Uri,
} from "vscode";
import { CancellationToken, window as Window, workspace, Uri } from "vscode";
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
import { CodeQLCliServer } from "./cli";
import { DatabaseUI } from "./local-databases-ui";
@@ -17,9 +11,10 @@ import {
getQlPackForDbscheme,
showBinaryChoiceDialog,
} from "./helpers";
import { ProgressCallback, UserCancellationException } from "./commandRunner";
import { ProgressCallback, UserCancellationException } from "./progress";
import { getErrorMessage } from "./pure/helpers-pure";
import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "./pure/ql";
import { App } from "./common/app";
const QUICK_QUERIES_DIR_NAME = "quick-queries";
const QUICK_QUERY_QUERY_NAME = "quick-query.ql";
@@ -30,8 +25,8 @@ export function isQuickQueryPath(queryPath: string): boolean {
return basename(queryPath) === QUICK_QUERY_QUERY_NAME;
}
async function getQuickQueriesDir(ctx: ExtensionContext): Promise<string> {
const storagePath = ctx.storagePath;
async function getQuickQueriesDir(app: App): Promise<string> {
const storagePath = app.workspaceStoragePath;
if (storagePath === undefined) {
throw new Error("Workspace storage path is undefined");
}
@@ -57,7 +52,7 @@ function findExistingQuickQueryEditor() {
* Show a buffer the user can enter a simple query into.
*/
export async function displayQuickQuery(
ctx: ExtensionContext,
app: App,
cliServer: CodeQLCliServer,
databaseUI: DatabaseUI,
progress: ProgressCallback,
@@ -73,7 +68,7 @@ export async function displayQuickQuery(
}
const workspaceFolders = workspace.workspaceFolders || [];
const queriesDir = await getQuickQueriesDir(ctx);
const queriesDir = await getQuickQueriesDir(app);
// We need to have a multi-root workspace to make quick query work
// at all. Changing the workspace from single-root to multi-root
@@ -143,7 +138,7 @@ export async function displayQuickQuery(
if (shouldRewrite) {
await cliServer.clearCache();
await cliServer.packInstall(queriesDir, true);
await cliServer.packInstall(queriesDir, { forceUpdate: true });
}
await Window.showTextDocument(await workspace.openTextDocument(qlFile));

View File

@@ -12,7 +12,7 @@ import {
window,
} from "vscode";
import { isCanary, AUTOSAVE_SETTING } from "./config";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import {
pathExists,
readFile,
@@ -30,7 +30,7 @@ import { nanoid } from "nanoid";
import { CodeQLCliServer } from "./cli";
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
import { DatabaseManager } from "./local-databases";
import { DecodedBqrsChunk } from "./pure/bqrs-cli-types";
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
import { extLogger, Logger } from "./common";
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
import { getErrorMessage } from "./pure/helpers-pure";
@@ -298,12 +298,8 @@ export class QueryEvaluationInfo {
this.evalLogEndSummaryPath,
"utf-8",
);
void logger.log(" --- Evaluator Log Summary --- ", {
additionalLogLocation: this.logPath,
});
void logger.log(endSummaryContent, {
additionalLogLocation: this.logPath,
});
void logger.log(" --- Evaluator Log Summary --- ");
void logger.log(endSummaryContent);
} catch (e) {
void showAndLogWarningMessage(
`Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`,
@@ -355,11 +351,17 @@ export class QueryEvaluationInfo {
chunk.tuples.forEach((tuple) => {
out.write(
`${tuple
.map((v, i) =>
chunk.columns[i].kind === "String"
? `"${typeof v === "string" ? v.replaceAll('"', '""') : v}"`
: v,
)
.map((v, i) => {
if (chunk.columns[i].kind === "String") {
return `"${
typeof v === "string" ? v.replaceAll('"', '""') : v
}"`;
} else if (chunk.columns[i].kind === "Entity") {
return (v as EntityValue).label;
} else {
return v;
}
})
.join(",")}\n`,
);
});
@@ -436,7 +438,6 @@ export class QueryEvaluationInfo {
export interface QueryWithResults {
readonly query: QueryEvaluationInfo;
readonly logFileLocation?: string;
readonly dispose: () => void;
readonly successful?: boolean;
readonly message?: string;
readonly result: legacyMessages.EvaluationResult;

View File

@@ -1,7 +1,6 @@
import * as React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { ThemeProvider } from "@primer/react";
import { CodePaths } from "../../view/common";
import type { CodeFlow } from "../../variant-analysis/shared/analysis-result";
@@ -9,13 +8,7 @@ import type { CodeFlow } from "../../variant-analysis/shared/analysis-result";
export default {
title: "Code Paths",
component: CodePaths,
decorators: [
(Story) => (
<ThemeProvider colorMode="auto">
<Story />
</ThemeProvider>
),
],
decorators: [(Story) => <Story />],
} as ComponentMeta<typeof CodePaths>;
const Template: ComponentStory<typeof CodePaths> = (args) => (

View File

@@ -0,0 +1,19 @@
import * as React from "react";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { DataFlowPaths as DataFlowPathsComponent } from "../../view/data-flow-paths/DataFlowPaths";
import { createMockDataFlowPaths } from "../../../test/factories/variant-analysis/shared/data-flow-paths";
export default {
title: "Data Flow Paths/Data Flow Paths",
component: DataFlowPathsComponent,
} as ComponentMeta<typeof DataFlowPathsComponent>;
const Template: ComponentStory<typeof DataFlowPathsComponent> = (args) => (
<DataFlowPathsComponent {...args} />
);
export const PowerShell = Template.bind({});
PowerShell.args = {
dataFlowPaths: createMockDataFlowPaths(),
};

View File

@@ -13,11 +13,10 @@ import {
LOG_TELEMETRY,
isIntegrationTestMode,
isCanary,
newTelemetryEnabled,
} from "./config";
import * as appInsights from "applicationinsights";
import { extLogger } from "./common";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import { showBinaryChoiceWithUrlDialog } from "./helpers";
import { RedactableError } from "./pure/errors";
@@ -174,10 +173,6 @@ export class TelemetryListener extends ConfigListener {
return;
}
if (!newTelemetryEnabled()) {
return;
}
this.reporter.sendTelemetryEvent(
"ui-interaction",
{
@@ -196,10 +191,6 @@ export class TelemetryListener extends ConfigListener {
return;
}
if (!newTelemetryEnabled()) {
return;
}
const properties: { [key: string]: string } = {
isCanary: isCanary().toString(),
message: error.redactedMessage,

View File

@@ -14,9 +14,8 @@ import {
import { showAndLogWarningMessage } from "./helpers";
import { TestTreeNode } from "./test-tree-node";
import { DisposableObject } from "./pure/disposable-object";
import { UIService } from "./vscode-utils/ui-service";
import { QLTestAdapter, getExpectedFile, getActualFile } from "./test-adapter";
import { extLogger } from "./common";
import { TestUICommands } from "./common/commands";
type VSCodeTestEvent =
| TestRunStartedEvent
@@ -42,22 +41,23 @@ class QLTestListener extends DisposableObject {
/**
* Service that implements all UI and commands for QL tests.
*/
export class TestUIService extends UIService implements TestController {
export class TestUIService extends DisposableObject implements TestController {
private readonly listeners: Map<TestAdapter, QLTestListener> = new Map();
constructor(private readonly testHub: TestHub) {
super();
void extLogger.log("Registering CodeQL test panel commands.");
this.registerCommand(
"codeQLTests.showOutputDifferences",
this.showOutputDifferences,
);
this.registerCommand("codeQLTests.acceptOutput", this.acceptOutput);
testHub.registerTestController(this);
}
public getCommands(): TestUICommands {
return {
"codeQLTests.showOutputDifferences":
this.showOutputDifferences.bind(this),
"codeQLTests.acceptOutput": this.acceptOutput.bind(this),
};
}
public dispose(): void {
this.testHub.unregisterTestController(this);

View File

@@ -0,0 +1,68 @@
import { ExtensionContext, ViewColumn } from "vscode";
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
import { assertNever } from "../pure/helpers-pure";
import { telemetryListener } from "../telemetry";
import {
FromDataFlowPathsMessage,
ToDataFlowPathsMessage,
} from "../pure/interface-types";
import { DataFlowPaths } from "./shared/data-flow-paths";
import { showAndLogExceptionWithTelemetry } from "../helpers";
import { redactableError } from "../pure/errors";
export class DataFlowPathsView extends AbstractWebview<
ToDataFlowPathsMessage,
FromDataFlowPathsMessage
> {
public static readonly viewType = "codeQL.dataFlowPaths";
public constructor(ctx: ExtensionContext) {
super(ctx);
}
public async showDataFlows(dataFlowPaths: DataFlowPaths) {
const panel = await this.getPanel();
panel.reveal(undefined, true);
await this.waitForPanelLoaded();
await this.postMessage({
t: "setDataFlowPaths",
dataFlowPaths,
});
}
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
return {
viewId: DataFlowPathsView.viewType,
title: "Data Flow Paths",
viewColumn: ViewColumn.Active,
preserveFocus: true,
view: "data-flow-paths",
};
}
protected onPanelDispose(): void {
// Nothing to dispose
}
protected async onMessage(msg: FromDataFlowPathsMessage): Promise<void> {
switch (msg.t) {
case "viewLoaded":
this.onWebViewLoaded();
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in data flow paths view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}
}
}

View File

@@ -9,10 +9,13 @@ import {
window,
workspace,
} from "vscode";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "../progress";
import { showInformationMessageWithAction } from "../helpers";
import { extLogger } from "../common";
import { QueryHistoryManager } from "../query-history/query-history-manager";
import { createGist } from "./gh-api/gh-api-client";
import {
generateVariantAnalysisMarkdown,
@@ -33,25 +36,6 @@ import {
} from "../pure/variant-analysis-filter-sort";
import { Credentials } from "../common/authentication";
/**
* Exports the results of the currently-selected variant analysis.
*/
export async function exportSelectedVariantAnalysisResults(
queryHistoryManager: QueryHistoryManager,
): Promise<void> {
const queryHistoryItem = queryHistoryManager.getCurrentQueryHistoryItem();
if (!queryHistoryItem || queryHistoryItem.t !== "variant-analysis") {
throw new Error(
"No variant analysis results currently open. To open results, click an item in the query history view.",
);
}
return commands.executeCommand(
"codeQL.exportVariantAnalysisResults",
queryHistoryItem.variantAnalysis.id,
);
}
const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2;
/**
@@ -63,108 +47,117 @@ export async function exportVariantAnalysisResults(
variantAnalysisId: number,
filterSort: RepositoriesFilterSortStateWithIds | undefined,
credentials: Credentials,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
const variantAnalysis = await variantAnalysisManager.getVariantAnalysis(
variantAnalysisId,
);
if (!variantAnalysis) {
void extLogger.log(
`Could not find variant analysis with id ${variantAnalysisId}`,
);
throw new Error(
"There was an error when trying to retrieve variant analysis information",
);
}
await withProgress(
async (progress: ProgressCallback, token: CancellationToken) => {
const variantAnalysis = await variantAnalysisManager.getVariantAnalysis(
variantAnalysisId,
);
if (!variantAnalysis) {
void extLogger.log(
`Could not find variant analysis with id ${variantAnalysisId}`,
);
throw new Error(
"There was an error when trying to retrieve variant analysis information",
);
}
if (token.isCancellationRequested) {
throw new UserCancellationException("Cancelled");
}
if (token.isCancellationRequested) {
throw new UserCancellationException("Cancelled");
}
const repoStates = await variantAnalysisManager.getRepoStates(
variantAnalysisId,
);
void extLogger.log(
`Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`,
);
progress({
maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS,
step: 0,
message: "Determining export format",
});
const exportFormat = await determineExportFormat();
if (!exportFormat) {
return;
}
if (token.isCancellationRequested) {
throw new UserCancellationException("Cancelled");
}
const repositories = filterAndSortRepositoriesWithResults(
variantAnalysis.scannedRepos,
filterSort,
)?.filter(
(repo) =>
repo.resultCount &&
repoStates.find((r) => r.repositoryId === repo.repository.id)
?.downloadStatus ===
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
);
async function* getAnalysesResults(): AsyncGenerator<
[VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult]
> {
if (!variantAnalysis) {
return;
}
if (!repositories) {
return;
}
for (const repo of repositories) {
const result = await variantAnalysisManager.loadResults(
variantAnalysis.id,
repo.repository.fullName,
{
skipCacheStore: true,
},
const repoStates = await variantAnalysisManager.getRepoStates(
variantAnalysisId,
);
yield [repo, result];
}
}
void extLogger.log(
`Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`,
);
const exportDirectory =
variantAnalysisManager.getVariantAnalysisStorageLocation(
variantAnalysis.id,
);
progress({
maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS,
step: 0,
message: "Determining export format",
});
// The date will be formatted like the following: 20221115T123456Z. The time is in UTC.
const formattedDate = new Date()
.toISOString()
.replace(/[-:]/g, "")
.replace(/\.\d+Z$/, "Z");
const exportedResultsDirectory = join(
exportDirectory,
"exported-results",
`results_${formattedDate}`,
);
const exportFormat = await determineExportFormat();
if (!exportFormat) {
return;
}
await exportVariantAnalysisAnalysisResults(
exportedResultsDirectory,
variantAnalysis,
getAnalysesResults(),
repositories?.length ?? 0,
exportFormat,
credentials,
progress,
token,
if (token.isCancellationRequested) {
throw new UserCancellationException("Cancelled");
}
const repositories = filterAndSortRepositoriesWithResults(
variantAnalysis.scannedRepos,
filterSort,
)?.filter(
(repo) =>
repo.resultCount &&
repoStates.find((r) => r.repositoryId === repo.repository.id)
?.downloadStatus ===
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
);
async function* getAnalysesResults(): AsyncGenerator<
[
VariantAnalysisScannedRepository,
VariantAnalysisScannedRepositoryResult,
]
> {
if (!variantAnalysis) {
return;
}
if (!repositories) {
return;
}
for (const repo of repositories) {
const result = await variantAnalysisManager.loadResults(
variantAnalysis.id,
repo.repository.fullName,
{
skipCacheStore: true,
},
);
yield [repo, result];
}
}
const exportDirectory =
variantAnalysisManager.getVariantAnalysisStorageLocation(
variantAnalysis.id,
);
// The date will be formatted like the following: 20221115T123456Z. The time is in UTC.
const formattedDate = new Date()
.toISOString()
.replace(/[-:]/g, "")
.replace(/\.\d+Z$/, "Z");
const exportedResultsDirectory = join(
exportDirectory,
"exported-results",
`results_${formattedDate}`,
);
await exportVariantAnalysisAnalysisResults(
exportedResultsDirectory,
variantAnalysis,
getAnalysesResults(),
repositories?.length ?? 0,
exportFormat,
credentials,
progress,
token,
);
},
{
title: "Exporting variant analysis results",
cancellable: true,
},
);
}

View File

@@ -1,4 +1,4 @@
import { UserCancellationException } from "../commandRunner";
import { UserCancellationException } from "../progress";
import { DbManager } from "../databases/db-manager";
import { DbItemKind } from "../databases/db-item";
@@ -13,9 +13,9 @@ export interface RepositorySelection {
* @returns The user selection.
*/
export async function getRepositorySelection(
dbManager?: DbManager,
dbManager: DbManager,
): Promise<RepositorySelection> {
const selectedDbItem = dbManager?.getSelectedDbItem();
const selectedDbItem = dbManager.getSelectedDbItem();
if (selectedDbItem) {
switch (selectedDbItem.kind) {
case DbItemKind.LocalDatabase || DbItemKind.LocalList:

View File

@@ -18,7 +18,7 @@ import {
getRemoteControllerRepo,
setRemoteControllerRepo,
} from "../config";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { RequestError } from "@octokit/types/dist-types";
import { QueryMetadata } from "../pure/interface-types";
import { getErrorMessage, REPO_REGEX } from "../pure/helpers-pure";
@@ -34,6 +34,7 @@ import {
getQlPackPath,
FALLBACK_QLPACK_FILENAME,
QLPACK_FILENAMES,
QLPACK_LOCK_FILENAMES,
} from "../pure/ql";
export interface QlPack {
@@ -70,42 +71,23 @@ async function generateQueryPack(
const originalPackRoot = await findPackRoot(queryFile);
const packRelativePath = relative(originalPackRoot, queryFile);
const targetQueryFileName = join(queryPackDir, packRelativePath);
const workspaceFolders = getOnDiskWorkspaceFolders();
let language: string | undefined;
// Check if the query is already in a query pack.
// If so, copy the entire query pack to the temporary directory.
// Otherwise, copy only the query file to the temporary directory
// and generate a synthetic query pack.
if (await getQlPackPath(originalPackRoot)) {
// don't include ql files. We only want the queryFile to be copied.
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
[
join(originalPackRoot, "qlpack.lock.yml"),
join(originalPackRoot, "codeql-pack.lock.yml"),
await copyExistingQueryPack(
cliServer,
originalPackRoot,
queryFile,
].forEach((absolutePath) => {
if (absolutePath) {
toCopy.push(absolutePath);
}
});
let copiedCount = 0;
await copy(originalPackRoot, queryPackDir, {
filter: (file: string) =>
// copy file if it is in the packlist, or it is a parent directory of a file in the packlist
!!toCopy.find((f) => {
// Normalized paths ensure that Windows drive letters are capitalized consistently.
const normalizedPath = Uri.file(f).fsPath;
const matches =
normalizedPath === file || normalizedPath.startsWith(file + sep);
if (matches) {
copiedCount++;
}
return matches;
}),
});
void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`);
await fixPackFile(queryPackDir, packRelativePath);
queryPackDir,
packRelativePath,
);
language = await findLanguage(cliServer, Uri.file(targetQueryFileName));
} else {
@@ -114,20 +96,12 @@ async function generateQueryPack(
// copy only the query file to the query pack directory
// and generate a synthetic query pack
void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`);
await copy(queryFile, targetQueryFileName);
void extLogger.log("Generating synthetic query pack");
const syntheticQueryPack = {
name: QUERY_PACK_NAME,
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
defaultSuite: generateDefaultSuite(packRelativePath),
};
await writeFile(
join(queryPackDir, FALLBACK_QLPACK_FILENAME),
dump(syntheticQueryPack),
await createNewQueryPack(
queryFile,
queryPackDir,
targetQueryFileName,
language,
packRelativePath,
);
}
if (!language) {
@@ -149,12 +123,21 @@ async function generateQueryPack(
precompilationOpts = ["--no-precompile"];
}
if (await cliServer.useExtensionPacks()) {
await injectExtensionPacks(cliServer, queryPackDir, workspaceFolders);
}
await cliServer.packInstall(queryPackDir, {
workspaceFolders,
});
// Clear the CLI cache so that the most recent qlpack lock file is used.
await cliServer.clearCache();
const bundlePath = await getPackedBundlePath(queryPackDir);
void extLogger.log(
`Compiling and bundling query pack from ${queryPackDir} to ${bundlePath}. (This may take a while.)`,
);
await cliServer.packInstall(queryPackDir);
const workspaceFolders = getOnDiskWorkspaceFolders();
await cliServer.packBundle(
queryPackDir,
workspaceFolders,
@@ -168,6 +151,70 @@ async function generateQueryPack(
};
}
async function createNewQueryPack(
queryFile: string,
queryPackDir: string,
targetQueryFileName: string,
language: string | undefined,
packRelativePath: string,
) {
void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`);
await copy(queryFile, targetQueryFileName);
void extLogger.log("Generating synthetic query pack");
const syntheticQueryPack = {
name: QUERY_PACK_NAME,
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
defaultSuite: generateDefaultSuite(packRelativePath),
};
await writeFile(
join(queryPackDir, FALLBACK_QLPACK_FILENAME),
dump(syntheticQueryPack),
);
}
async function copyExistingQueryPack(
cliServer: cli.CodeQLCliServer,
originalPackRoot: string,
queryFile: string,
queryPackDir: string,
packRelativePath: string,
) {
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
[
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
...QLPACK_LOCK_FILENAMES.map((f) => join(originalPackRoot, f)),
queryFile,
].forEach((absolutePath) => {
if (absolutePath) {
toCopy.push(absolutePath);
}
});
let copiedCount = 0;
await copy(originalPackRoot, queryPackDir, {
filter: (file: string) =>
// copy file if it is in the packlist, or it is a parent directory of a file in the packlist
!!toCopy.find((f) => {
// Normalized paths ensure that Windows drive letters are capitalized consistently.
const normalizedPath = Uri.file(f).fsPath;
const matches =
normalizedPath === file || normalizedPath.startsWith(file + sep);
if (matches) {
copiedCount++;
}
return matches;
}),
});
void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`);
await fixPackFile(queryPackDir, packRelativePath);
}
async function findPackRoot(queryFile: string): Promise<string> {
// recursively find the directory containing qlpack.yml or codeql-pack.yml
let dir = dirname(queryFile);
@@ -223,7 +270,7 @@ export async function prepareRemoteQueryRun(
uri: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
dbManager?: DbManager,
dbManager: DbManager,
): Promise<PreparedRemoteQuery> {
if (!uri?.fsPath.endsWith(".ql")) {
throw new UserCancellationException("Not a CodeQL query file.");
@@ -329,19 +376,54 @@ async function fixPackFile(
}
const qlpack = load(await readFile(packPath, "utf8")) as QlPack;
// update pack name
qlpack.name = QUERY_PACK_NAME;
// update default suite
delete qlpack.defaultSuiteFile;
qlpack.defaultSuite = generateDefaultSuite(packRelativePath);
// remove any ${workspace} version references
updateDefaultSuite(qlpack, packRelativePath);
removeWorkspaceRefs(qlpack);
await writeFile(packPath, dump(qlpack));
}
async function injectExtensionPacks(
cliServer: cli.CodeQLCliServer,
queryPackDir: string,
workspaceFolders: string[],
) {
const qlpackFile = await getQlPackPath(queryPackDir);
if (!qlpackFile) {
throw new Error(
`Could not find ${QLPACK_FILENAMES.join(
" or ",
)} file in '${queryPackDir}'`,
);
}
const syntheticQueryPack = load(await readFile(qlpackFile, "utf8")) as QlPack;
const extensionPacks = await cliServer.resolveQlpacks(workspaceFolders, true);
Object.entries(extensionPacks).forEach(([name, paths]) => {
// We are guaranteed that there is at least one path found for each extension pack.
// If there are multiple paths, then we have a problem. This means that there is
// ambiguity in which path to use. This is an error.
if (paths.length > 1) {
throw new Error(
`Multiple versions of extension pack '${name}' found: ${paths.join(
", ",
)}`,
);
}
// Add this extension pack as a dependency. It doesn't matter which
// version we specify, since we are guaranteed that the extension pack
// is resolved from source at the given path.
syntheticQueryPack.dependencies[name] = "*";
});
await writeFile(qlpackFile, dump(syntheticQueryPack));
await cliServer.clearCache();
}
function updateDefaultSuite(qlpack: QlPack, packRelativePath: string) {
delete qlpack.defaultSuiteFile;
qlpack.defaultSuite = generateDefaultSuite(packRelativePath);
}
function generateDefaultSuite(packRelativePath: string) {
return [
{

View File

@@ -0,0 +1,8 @@
import { AnalysisMessage, CodeFlow, ResultSeverity } from "./analysis-result";
export interface DataFlowPaths {
codeFlows: CodeFlow[];
ruleDescription: string;
message: AnalysisMessage;
severity: ResultSeverity;
}

View File

@@ -51,7 +51,11 @@ import {
import { readFile, readJson, remove, pathExists, outputJson } from "fs-extra";
import { EOL } from "os";
import { cancelVariantAnalysis } from "./gh-api/gh-actions-api-client";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "../progress";
import { CodeQLCliServer } from "../cli";
import {
defaultFilterSortState,
@@ -62,6 +66,8 @@ import { URLSearchParams } from "url";
import { DbManager } from "../databases/db-manager";
import { App } from "../common/app";
import { redactableError } from "../pure/errors";
import { AppCommandManager, VariantAnalysisCommands } from "../common/commands";
import { exportVariantAnalysisResults } from "./export-results";
export class VariantAnalysisManager
extends DisposableObject
@@ -105,7 +111,7 @@ export class VariantAnalysisManager
private readonly cliServer: CodeQLCliServer,
private readonly storagePath: string,
private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager,
private readonly dbManager?: DbManager,
private readonly dbManager: DbManager,
) {
super();
this.variantAnalysisMonitor = this.push(
@@ -123,11 +129,54 @@ export class VariantAnalysisManager
);
}
getCommands(): VariantAnalysisCommands {
return {
"codeQL.autoDownloadVariantAnalysisResult":
this.enqueueDownload.bind(this),
"codeQL.copyVariantAnalysisRepoList":
this.copyRepoListToClipboard.bind(this),
"codeQL.loadVariantAnalysisRepoResults": this.loadResults.bind(this),
"codeQL.monitorVariantAnalysis": this.monitorVariantAnalysis.bind(this),
"codeQL.openVariantAnalysisLogs": this.openVariantAnalysisLogs.bind(this),
"codeQL.openVariantAnalysisView": this.showView.bind(this),
"codeQL.runVariantAnalysis":
this.runVariantAnalysisFromCommand.bind(this),
// Since we are tracking extension usage through commands, this command mirrors the "codeQL.runVariantAnalysis" command
"codeQL.runVariantAnalysisContextEditor":
this.runVariantAnalysisFromCommand.bind(this),
};
}
get commandManager(): AppCommandManager {
return this.app.commands;
}
private async runVariantAnalysisFromCommand(uri?: Uri) {
return withProgress(
async (progress, token) =>
this.runVariantAnalysis(
uri || Window.activeTextEditor?.document.uri,
progress,
token,
),
{
title: "Run Variant Analysis",
cancellable: true,
},
);
}
public async runVariantAnalysis(
uri: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
progress({
maxStep: 5,
step: 0,
message: "Getting credentials",
});
const {
actionBranch,
base64Pack,
@@ -452,19 +501,16 @@ export class VariantAnalysisManager
public async monitorVariantAnalysis(
variantAnalysis: VariantAnalysis,
cancellationToken: CancellationToken,
): Promise<void> {
await this.variantAnalysisMonitor.monitorVariantAnalysis(
variantAnalysis,
this.app.credentials,
cancellationToken,
);
}
public async autoDownloadVariantAnalysisResult(
scannedRepo: VariantAnalysisScannedRepository,
variantAnalysis: VariantAnalysis,
cancellationToken: CancellationToken,
): Promise<void> {
if (
this.repoStates.get(variantAnalysis.id)?.[scannedRepo.repository.id]
@@ -481,13 +527,6 @@ export class VariantAnalysisManager
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
if (cancellationToken && cancellationToken.isCancellationRequested) {
repoState.downloadStatus =
VariantAnalysisScannedRepositoryDownloadStatus.Failed;
await this.onRepoStateUpdated(variantAnalysis.id, repoState);
return;
}
let repoTask: VariantAnalysisRepositoryTask;
try {
const repoTaskResponse = await getVariantAnalysisRepo(
@@ -562,14 +601,9 @@ export class VariantAnalysisManager
public async enqueueDownload(
scannedRepo: VariantAnalysisScannedRepository,
variantAnalysis: VariantAnalysis,
token: CancellationToken,
): Promise<void> {
await this.queue.add(() =>
this.autoDownloadVariantAnalysisResult(
scannedRepo,
variantAnalysis,
token,
),
this.autoDownloadVariantAnalysisResult(scannedRepo, variantAnalysis),
);
}
@@ -647,6 +681,18 @@ export class VariantAnalysisManager
await env.clipboard.writeText(text.join(EOL));
}
public async exportResults(
variantAnalysisId: number,
filterSort?: RepositoriesFilterSortStateWithIds,
) {
await exportVariantAnalysisResults(
this,
variantAnalysisId,
filterSort,
this.app.credentials,
);
}
private getRepoStatesStoragePath(variantAnalysisId: number): string {
return join(
this.getVariantAnalysisStorageLocation(variantAnalysisId),

View File

@@ -1,4 +1,4 @@
import { CancellationToken, commands, EventEmitter } from "vscode";
import { commands, EventEmitter } from "vscode";
import { getVariantAnalysis } from "./gh-api/gh-api-client";
import {
@@ -37,7 +37,6 @@ export class VariantAnalysisMonitor extends DisposableObject {
public async monitorVariantAnalysis(
variantAnalysis: VariantAnalysis,
credentials: Credentials,
cancellationToken: CancellationToken,
): Promise<void> {
let attemptCount = 0;
const scannedReposDownloaded: number[] = [];
@@ -45,10 +44,6 @@ export class VariantAnalysisMonitor extends DisposableObject {
while (attemptCount <= VariantAnalysisMonitor.maxAttemptCount) {
await sleep(VariantAnalysisMonitor.sleepTime);
if (cancellationToken && cancellationToken.isCancellationRequested) {
return;
}
if (await this.shouldCancelMonitor(variantAnalysis.id)) {
return;
}

View File

@@ -2,6 +2,8 @@ import {
VariantAnalysis,
VariantAnalysisScannedRepositoryState,
} from "./shared/variant-analysis";
import { AppCommandManager } from "../common/commands";
import { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
export interface VariantAnalysisViewInterface {
variantAnalysisId: number;
@@ -11,6 +13,8 @@ export interface VariantAnalysisViewInterface {
export interface VariantAnalysisViewManager<
T extends VariantAnalysisViewInterface,
> {
commandManager: AppCommandManager;
registerView(view: T): void;
unregisterView(view: T): void;
getView(variantAnalysisId: number): T | undefined;
@@ -21,4 +25,11 @@ export interface VariantAnalysisViewManager<
getRepoStates(
variantAnalysisId: number,
): Promise<VariantAnalysisScannedRepositoryState[]>;
openQueryFile(variantAnalysisId: number): Promise<void>;
openQueryText(variantAnalysisId: number): Promise<void>;
cancelVariantAnalysis(variantAnalysisId: number): Promise<void>;
exportResults(
variantAnalysisId: number,
filterSort?: RepositoriesFilterSortStateWithIds,
): Promise<void>;
}

View File

@@ -15,14 +15,21 @@ import {
VariantAnalysisViewInterface,
VariantAnalysisViewManager,
} from "./variant-analysis-view-manager";
import { showAndLogWarningMessage } from "../helpers";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
} from "../helpers";
import { telemetryListener } from "../telemetry";
import { redactableError } from "../pure/errors";
import { DataFlowPathsView } from "./data-flow-paths-view";
import { DataFlowPaths } from "./shared/data-flow-paths";
export class VariantAnalysisView
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
implements VariantAnalysisViewInterface
{
public static readonly viewType = "codeQL.variantAnalysis";
private readonly dataFlowPathsView: DataFlowPathsView;
public constructor(
ctx: ExtensionContext,
@@ -32,6 +39,8 @@ export class VariantAnalysisView
super(ctx);
manager.registerView(this);
this.dataFlowPathsView = new DataFlowPathsView(ctx);
}
public async openView() {
@@ -106,10 +115,7 @@ export class VariantAnalysisView
break;
case "cancelVariantAnalysis":
void commands.executeCommand(
"codeQL.cancelVariantAnalysis",
this.variantAnalysisId,
);
await this.manager.cancelVariantAnalysis(this.variantAnalysisId);
break;
case "requestRepositoryResults":
void commands.executeCommand(
@@ -119,16 +125,10 @@ export class VariantAnalysisView
);
break;
case "openQueryFile":
void commands.executeCommand(
"codeQL.openVariantAnalysisQueryFile",
this.variantAnalysisId,
);
await this.manager.openQueryFile(this.variantAnalysisId);
break;
case "openQueryText":
void commands.executeCommand(
"codeQL.openVariantAnalysisQueryText",
this.variantAnalysisId,
);
await this.manager.openQueryText(this.variantAnalysisId);
break;
case "copyRepositoryList":
void commands.executeCommand(
@@ -138,21 +138,30 @@ export class VariantAnalysisView
);
break;
case "exportResults":
void commands.executeCommand(
"codeQL.exportVariantAnalysisResults",
await this.manager.exportResults(
this.variantAnalysisId,
msg.filterSort,
);
break;
case "openLogs":
await commands.executeCommand(
await this.manager.commandManager.execute(
"codeQL.openVariantAnalysisLogs",
this.variantAnalysisId,
);
break;
case "showDataFlowPaths":
await this.showDataFlows(msg.dataFlowPaths);
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
redactableError(
msg.error,
)`Unhandled error in variant analysis results view: ${msg.error.message}`,
);
break;
default:
assertNever(msg);
}
@@ -193,4 +202,8 @@ export class VariantAnalysisView
? `${variantAnalysis.query.name} - Variant Analysis Results`
: `Variant Analysis ${this.variantAnalysisId} - Results`;
}
private async showDataFlows(dataFlows: DataFlowPaths): Promise<void> {
await this.dataFlowPathsView.showDataFlows(dataFlows);
}
}

View File

@@ -1,17 +1,13 @@
import * as React from "react";
import { useRef, useState } from "react";
import styled from "styled-components";
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
import { Overlay, ThemeProvider } from "@primer/react";
import {
AnalysisMessage,
CodeFlow,
ResultSeverity,
} from "../../../variant-analysis/shared/analysis-result";
import { CodePathsOverlay } from "./CodePathsOverlay";
import { useTelemetryOnChange } from "../telemetry";
import { vscode } from "../../vscode-api";
const ShowPathsLink = styled(VSCodeLink)`
cursor: pointer;
@@ -24,46 +20,27 @@ export type CodePathsProps = {
severity: ResultSeverity;
};
const filterIsOpenTelemetry = (v: boolean) => v;
export const CodePaths = ({
codeFlows,
ruleDescription,
message,
severity,
}: CodePathsProps) => {
const [isOpen, setIsOpen] = useState(false);
useTelemetryOnChange(isOpen, "code-path-is-open", {
filterTelemetryOnValue: filterIsOpenTelemetry,
});
const linkRef = useRef<HTMLAnchorElement>(null);
const closeOverlay = () => setIsOpen(false);
const onShowPathsClick = () => {
vscode.postMessage({
t: "showDataFlowPaths",
dataFlowPaths: {
codeFlows,
ruleDescription,
message,
severity,
},
});
};
return (
<>
<ShowPathsLink onClick={() => setIsOpen(true)} ref={linkRef}>
Show paths
</ShowPathsLink>
{isOpen && (
<ThemeProvider colorMode="auto">
<Overlay
returnFocusRef={linkRef}
onEscape={closeOverlay}
onClickOutside={closeOverlay}
anchorSide="outside-top"
>
<CodePathsOverlay
codeFlows={codeFlows}
ruleDescription={ruleDescription}
message={message}
severity={severity}
onClose={closeOverlay}
/>
</Overlay>
</ThemeProvider>
)}
<ShowPathsLink onClick={onShowPathsClick}>Show paths</ShowPathsLink>
</>
);
};

View File

@@ -1,112 +0,0 @@
import * as React from "react";
import { useState } from "react";
import styled from "styled-components";
import {
AnalysisMessage,
CodeFlow,
ResultSeverity,
} from "../../../variant-analysis/shared/analysis-result";
import { useTelemetryOnChange } from "../telemetry";
import { SectionTitle } from "../SectionTitle";
import { VerticalSpace } from "../VerticalSpace";
import { CodeFlowsDropdown } from "./CodeFlowsDropdown";
import { CodePath } from "./CodePath";
const StyledCloseButton = styled.button`
position: absolute;
top: 1em;
right: 4em;
background-color: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
border: none;
cursor: pointer;
&:focus-visible {
outline: none;
}
`;
const OverlayContainer = styled.div`
height: 100%;
width: 100%;
padding: 2em;
position: fixed;
top: 0;
left: 0;
background-color: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
overflow-y: scroll;
`;
const CloseButton = ({ onClick }: { onClick: () => void }) => (
<StyledCloseButton onClick={onClick} tabIndex={-1}>
<span className="codicon codicon-chrome-close" />
</StyledCloseButton>
);
const PathsContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const PathDetailsContainer = styled.div`
padding: 0;
border: 0;
`;
const PathDropdownContainer = styled.div`
flex-grow: 1;
padding: 0 0 0 0.2em;
border: none;
`;
type CodePathsOverlayProps = {
codeFlows: CodeFlow[];
ruleDescription: string;
message: AnalysisMessage;
severity: ResultSeverity;
onClose: () => void;
};
export const CodePathsOverlay = ({
codeFlows,
ruleDescription,
message,
severity,
onClose,
}: CodePathsOverlayProps) => {
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
useTelemetryOnChange(selectedCodeFlow, "code-flow-selected");
return (
<OverlayContainer>
<CloseButton onClick={onClose} />
<SectionTitle>{ruleDescription}</SectionTitle>
<VerticalSpace size={2} />
<PathsContainer>
<PathDetailsContainer>
{codeFlows.length} paths available:{" "}
{selectedCodeFlow.threadFlows.length} steps in
</PathDetailsContainer>
<PathDropdownContainer>
<CodeFlowsDropdown
codeFlows={codeFlows}
setSelectedCodeFlow={setSelectedCodeFlow}
/>
</PathDropdownContainer>
</PathsContainer>
<VerticalSpace size={2} />
<CodePath
codeFlow={selectedCodeFlow}
severity={severity}
message={message}
/>
<VerticalSpace size={3} />
</OverlayContainer>
);
};

View File

@@ -18,20 +18,25 @@ describe(CodePaths.name, () => {
/>,
);
it("renders correctly when unexpanded", () => {
it("renders 'show paths' link", () => {
render();
expect(screen.getByText("Show paths")).toBeInTheDocument();
expect(screen.queryByText("Code snippet text")).not.toBeInTheDocument();
expect(screen.queryByText("Rule description")).not.toBeInTheDocument();
});
it("renders correctly when expanded", async () => {
it("posts extension message when 'show paths' link clicked", async () => {
render();
await userEvent.click(screen.getByText("Show paths"));
expect(screen.getByText("Code snippet text")).toBeInTheDocument();
expect(screen.getByText("Rule description")).toBeInTheDocument();
expect((window as any).vsCodeApi.postMessage).toHaveBeenCalledWith({
t: "showDataFlowPaths",
dataFlowPaths: {
codeFlows: createMockCodeFlows(),
ruleDescription: "Rule description",
message: createMockAnalysisMessage(),
severity: "Recommendation",
},
});
});
});

View File

@@ -0,0 +1,54 @@
import { getErrorMessage, getErrorStack } from "../../pure/helpers-pure";
import { vscode } from "../vscode-api";
// Keep track of previous errors that have happened.
// The listeners for uncaught errors and rejections can get triggered
// twice for each error. This is believed to be an effect caused
// by React's error boundaries. Adding an error boundary stops
// this duplicate reporting for errors that happen during component
// rendering, but unfortunately errors from event handlers and
// timeouts are still duplicated and there does not appear to be
// a way around this.
const previousErrors: Set<Error> = new Set();
function shouldReportError(error: Error): boolean {
const seenBefore = previousErrors.has(error);
previousErrors.add(error);
setTimeout(() => {
previousErrors.delete(error);
}, 1000);
return !seenBefore;
}
const unhandledErrorListener = (event: ErrorEvent) => {
if (shouldReportError(event.error)) {
vscode.postMessage({
t: "unhandledError",
error: {
message: getErrorMessage(event.error),
stack: getErrorStack(event.error),
},
});
}
};
const unhandledRejectionListener = (event: PromiseRejectionEvent) => {
if (shouldReportError(event.reason)) {
vscode.postMessage({
t: "unhandledError",
error: {
message: getErrorMessage(event.reason),
stack: getErrorStack(event.reason),
},
});
}
};
/**
* Adds listeners for unhandled errors / rejected promises.
* When an error is detected a "unhandledError" message is posted to the view.
*/
export function registerUnhandledErrorListener() {
window.addEventListener("error", unhandledErrorListener);
window.addEventListener("unhandledrejection", unhandledRejectionListener);
}

View File

@@ -0,0 +1,71 @@
import * as React from "react";
import styled from "styled-components";
import { useState } from "react";
import { useTelemetryOnChange } from "../common/telemetry";
import { CodeFlowsDropdown } from "../common/CodePaths/CodeFlowsDropdown";
import { SectionTitle, VerticalSpace } from "../common";
import { CodePath } from "../common/CodePaths/CodePath";
import { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths";
const PathsContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const PathDetailsContainer = styled.div`
padding: 0;
border: 0;
`;
const PathDropdownContainer = styled.div`
flex-grow: 1;
padding: 0 0 0 0.2em;
border: none;
`;
export type DataFlowPathsProps = {
dataFlowPaths: DataFlowPathsDomainModel;
};
export const DataFlowPaths = ({
dataFlowPaths,
}: {
dataFlowPaths: DataFlowPathsDomainModel;
}): JSX.Element => {
const [selectedCodeFlow, setSelectedCodeFlow] = useState(
dataFlowPaths.codeFlows[0],
);
useTelemetryOnChange(selectedCodeFlow, "code-flow-selected");
const { codeFlows, ruleDescription, message, severity } = dataFlowPaths;
return (
<>
<VerticalSpace size={2} />
<SectionTitle>{ruleDescription}</SectionTitle>
<VerticalSpace size={2} />
<PathsContainer>
<PathDetailsContainer>
{codeFlows.length} paths available:{" "}
{selectedCodeFlow?.threadFlows.length} steps in
</PathDetailsContainer>
<PathDropdownContainer>
<CodeFlowsDropdown
codeFlows={codeFlows}
setSelectedCodeFlow={setSelectedCodeFlow}
/>
</PathDropdownContainer>
</PathsContainer>
<VerticalSpace size={2} />
<CodePath
codeFlow={selectedCodeFlow}
severity={severity}
message={message}
/>
</>
);
};

View File

@@ -0,0 +1,47 @@
import * as React from "react";
import { useEffect, useState } from "react";
import { ToDataFlowPathsMessage } from "../../pure/interface-types";
import { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths";
import { DataFlowPaths } from "./DataFlowPaths";
export type DataFlowPathsViewProps = {
dataFlowPaths?: DataFlowPathsDomainModel;
};
export function DataFlowPathsView({
dataFlowPaths: initialDataFlowPaths,
}: DataFlowPathsViewProps): JSX.Element {
const [dataFlowPaths, setDataFlowPaths] = useState<
DataFlowPathsDomainModel | undefined
>(initialDataFlowPaths);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToDataFlowPathsMessage = evt.data;
if (msg.t === "setDataFlowPaths") {
setDataFlowPaths(msg.dataFlowPaths);
// Scroll to the top of the page when we're rendering
// new data flow paths.
window.scrollTo(0, 0);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
}, []);
if (!dataFlowPaths) {
return <>Loading data flow paths</>;
}
return <DataFlowPaths dataFlowPaths={dataFlowPaths} />;
}

View File

@@ -0,0 +1,31 @@
import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import { DataFlowPaths, DataFlowPathsProps } from "../DataFlowPaths";
import { createMockDataFlowPaths } from "../../../../test/factories/variant-analysis/shared/data-flow-paths";
describe(DataFlowPaths.name, () => {
const render = (props: DataFlowPathsProps) =>
reactRender(<DataFlowPaths {...props} />);
it("renders data flow paths", () => {
const dataFlowPaths = createMockDataFlowPaths();
render({ dataFlowPaths });
expect(screen.getByText(dataFlowPaths.ruleDescription)).toBeInTheDocument();
expect(
screen.getByText("1 paths available", { exact: false }),
).toBeInTheDocument();
expect(
screen.getByText("3 steps in", {
exact: false,
}),
).toBeInTheDocument();
expect(
screen.getByText("This zip file may have a dangerous path", {
exact: false,
}),
).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,31 @@
import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import {
DataFlowPathsView,
DataFlowPathsViewProps,
} from "../DataFlowPathsView";
import { createMockCodeFlows } from "../../../../test/factories/variant-analysis/shared/CodeFlow";
import { createMockDataFlowPaths } from "../../../../test/factories/variant-analysis/shared/data-flow-paths";
describe(DataFlowPathsView.name, () => {
const render = (props: Partial<DataFlowPathsViewProps>) =>
reactRender(<DataFlowPathsView {...props} />);
it("renders a loading data flow paths view", () => {
render({});
expect(screen.getByText("Loading data flow paths")).toBeInTheDocument();
});
it("renders a data flow paths view", () => {
const dataFlowPaths = createMockDataFlowPaths({
ruleDescription: "Rule description",
codeFlows: createMockCodeFlows(),
});
render({ dataFlowPaths });
expect(screen.queryByText("Code snippet text")).toBeInTheDocument();
expect(screen.getByText("Rule description")).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,9 @@
import * as React from "react";
import { WebviewDefinition } from "../webview-definition";
import { DataFlowPathsView } from "./DataFlowPathsView";
const definition: WebviewDefinition = {
component: <DataFlowPathsView />,
};
export default definition;

View File

@@ -5,7 +5,7 @@ import {
InterpretedResultSet,
GraphInterpretationData,
} from "../../pure/interface-types";
import { graphviz } from "d3-graphviz";
import { graphviz, GraphvizOptions } from "d3-graphviz";
import { tryGetLocationFromString } from "../../pure/bqrs-utils";
export type GraphProps = ResultTableProps & {
resultSet: InterpretedResultSet<GraphInterpretationData>;
@@ -59,11 +59,12 @@ export class Graph extends React.Component<GraphProps> {
return;
}
const options = {
const options: GraphvizOptions = {
fit: true,
fade: false,
growEnteringEdges: false,
zoom: true,
useWorker: false,
};
const element = document.querySelector(`#${graphId}`);
@@ -77,8 +78,7 @@ export class Graph extends React.Component<GraphProps> {
const borderColor = getComputedStyle(element).borderColor;
let firstPolygon = true;
graphviz(`#${graphId}`)
.options(options)
graphviz(`#${graphId}`, options)
.attributer(function (d) {
if (d.tag === "a") {
const url = d.attributes["xlink:href"] || d.attributes["href"];

Some files were not shown because too many files have changed in this diff Show More