Compare commits

...

1392 Commits

Author SHA1 Message Date
Andrew Eisenberg
bf662354fe Merge pull request #1609 from github/v1.7.2
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
v1.7.2
2022-10-14 12:55:51 -07:00
Andrew Eisenberg
6ea36867a2 v1.7.2 2022-10-14 10:44:07 -07:00
Andrew Eisenberg
8df061f443 Merge pull request #1607 from github/aeisenberg/fix-failing-tests
`loadPersistedState` should happen outside of dbm constructor
2022-10-14 10:13:10 -07:00
Andrew Eisenberg
0885a22984 loadPersistedState should happen outside of dbm constructor
Also, add stub to logger in tests.

This fixes some occasionally failing tests on main.
2022-10-14 09:29:47 -07:00
Andrew Eisenberg
0354b1caac Merge pull request #1605 from github/aeisenberg/fix-missing-success
Fix deserialization error
2022-10-14 08:29:37 -07:00
Koen Vlaswinkel
d32a3a0deb Merge pull request #1601 from github/koesie10/completed-status
Add display of duration and completion status
2022-10-14 17:25:10 +02:00
shati-patel
e41dba7627 Move createMockRemoteQueryHistoryItem into shared location 2022-10-14 16:14:22 +01:00
shati-patel
b2f4fecfb4 Move createMockLocalQueryInfo into shared location 2022-10-14 16:14:22 +01:00
Koen Vlaswinkel
21b6adb92c Merge pull request #1575 from github/koesie10/reset-config
Reset VSCode configuration between tests
2022-10-14 16:52:46 +02:00
Andrew Eisenberg
90577f516f Update changelog 2022-10-14 07:42:11 -07:00
Andrew Eisenberg
a2825162ac Fix deserialization error
We added a `successful` property to serialized local queries. But, this
property does not exist on older serialized queries. This change ensures
older queries get a `successful` property when deserialized.
2022-10-14 07:39:02 -07:00
Koen Vlaswinkel
2e26b857f4 Add tests for missing duration 2022-10-14 16:18:07 +02:00
Shati Patel
7bdd452d63 Merge pull request #1600 from github/shati-patel/query-history-info
Move `QueryHistoryInfo` type to separate file
2022-10-14 13:12:11 +01:00
shati-patel
be9a7a35bc Address review comments
- Change function name
- Fix import
2022-10-14 12:36:16 +01:00
Koen Vlaswinkel
dd8600fcc6 Fix processor test 2022-10-14 13:16:59 +02:00
Koen Vlaswinkel
cc8f304f96 Add tests for duration calculation
This adds tests for the duration calculation and moves it down a
component to make this easier. Adding tests for the
`VariantAnalysisHeader` would require constructing a complete variant
analysis object, while this is now just a simple unit test.
2022-10-14 13:04:51 +02:00
Koen Vlaswinkel
1ca623f68a Add display of duration and completion status
This will use the new fields on the API to display the duration and
completion status of a variant analysis.
2022-10-14 12:11:54 +02:00
Koen Vlaswinkel
53c404b131 Add new date fields from API to variant analysis types
This will add some new date fields that have been added in the API to
the variant analysis types and factories. They are stored as strings
since storing them as `Date` would make the types inconsistent if they
are serialized to JSON (`JSON.stringify` -> `JSON.parse` would result
in strings rather than dates).
2022-10-14 12:11:54 +02:00
Koen Vlaswinkel
f8d5fd8f6e Merge pull request #1586 from github/koesie10/upgrade-vscode-test
Upgrade from vscode-test to @vscode/test-electron
2022-10-14 11:54:22 +02:00
shati-patel
d49c2d7958 Add getRawName helper function to query-history-info 2022-10-14 10:43:57 +01:00
shati-patel
01d7329bc3 Move QueryHistoryInfo to separate file 2022-10-14 10:43:56 +01:00
github-actions[bot]
e8d230c8f5 Bump version to v1.7.2 (#1592)
Co-authored-by: aeisenberg <aeisenberg@users.noreply.github.com>
2022-10-14 09:36:00 +00:00
Charis Kyriakou
44a3e6b557 Subscribe to new variant analyses events in query history (#1598) 2022-10-14 09:25:03 +00:00
Shati Patel
8b2a3b18ce Query history: Add new VariantAnalysisHistoryItem type (#1590) 2022-10-14 10:04:56 +01:00
Charis Kyriakou
fad5bb31a0 Only show file name (not full path) in query details (#1595) 2022-10-13 13:21:31 +00:00
Shati Patel
484b7668cb Typo fixes in query history methods/properties (#1594) 2022-10-13 12:10:57 +01:00
Koen Vlaswinkel
33dd50ca4d Merge pull request #1588 from github/koesie10/open-query-text
Add ability to open the query text
2022-10-13 12:01:54 +02:00
Koen Vlaswinkel
195cd69567 Improve variant analysis query text error messages
Co-authored-by: Robert Brignull <robertbrignull@github.com>
2022-10-13 11:10:19 +02:00
Koen Vlaswinkel
268199e9e2 Rename queryText to text
The `text` property is already nested under `query`, so it's redundant
to prefix it with `query`. This also makes it consistent with the other
properties.
2022-10-13 10:24:27 +02:00
Dave Bartolomeo
6cef629507 Merge pull request #1591 from github/v1.7.1
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
Release prep v1.7.1
2022-10-12 14:29:02 -04:00
Andrew Eisenberg
563489d1e0 Release prep v1.7.1
- Add missing changelog entry
- Update .nvmrc and workflow node versions to align with vscode's
  node version.
2022-10-12 10:56:30 -07:00
Andrew Eisenberg
ebfcce30ba Merge pull request #1589 from github/aeisenberg/load-database-logging
Add better logging while loading databases
2022-10-12 10:41:21 -07:00
Shati Patel
159d900edb Add start time to VariantAnalysis type (#1587) 2022-10-12 16:56:09 +01:00
Andrew Eisenberg
46233b9355 Add better logging while loading databases 2022-10-12 08:15:22 -07:00
Koen Vlaswinkel
1d6a7f8df1 Open query text when clicking on view query
This will implement the final step of opening the query text. Inside
the webview, this will send the message to the extension host to open
the query text.
2022-10-12 17:05:23 +02:00
Koen Vlaswinkel
e380c78876 Add openQueryText message
This will allow the webview to send a `openQueryText` message, which
will open a virtual file to show the query text.
2022-10-12 17:05:23 +02:00
Koen Vlaswinkel
cd67ce9242 Add text document content provider for variant analysis
This will add a new text document content provider for showing variant
analyses. This is separate from the remote queries content provider
to allow this to evolve separately. It also retrieves the query text
from the manager rather than passing the text directly to prevent the
webview from opening a tab with arbitrary content.

See: 4c527a3573/extensions/ql-vscode/src/extension.ts (L1242-L1257)
2022-10-12 17:05:23 +02:00
Koen Vlaswinkel
cd8d82daac Add query text to variant analysis submission
This will add a new query text field to the variant analysis submission,
which will also propagate to the variant analysis itself. This will
allow us to show the query text on the variant analysis page.
2022-10-12 17:05:23 +02:00
Koen Vlaswinkel
726feb19e1 Register all settings as test setting automatically
This will register all settings for which a `Setting` instance is
created as settings which will be reset. This should make it less
error-prone to change settings in tests.
2022-10-12 16:30:38 +02:00
Koen Vlaswinkel
8c324a3263 Merge pull request #1577 from github/koesie10/open-existing-variant-analysis
Add command for opening existing variant analysis
2022-10-12 16:03:13 +02:00
Shati Patel
119649144e Emit "update" events while variant analysis is being monitored (#1579) 2022-10-12 15:00:18 +01:00
Koen Vlaswinkel
4c527a3573 Merge pull request #1585 from github/koesie10/open-query-file
Add ability to open the query file
2022-10-12 15:41:43 +02:00
Koen Vlaswinkel
525f5234b6 Upgrade from vscode-test to @vscode/test-electron
The `vscode-test` package was renamed to `@vscode/test-electron` in
December of last year. This commit updates the extension to use the new
package name.

The reason for this change is that the `vscode-test` package was
somewhat flaky in actually starting VSCode to run the tests from the
command line. The new package also has some bugfixes and other
improvements which would normally have been part of a new version of the
`vscode-test` package.
2022-10-12 15:12:17 +02:00
Koen Vlaswinkel
908abb4413 Merge pull request #1574 from github/koesie10/view-loaded-message
Handle `viewLoaded` message
2022-10-12 13:49:48 +02:00
Koen Vlaswinkel
a69ec03c6e Add ability to open the query file
This makes it possible to open the query file in the editor when
clicking on the query filename.

This is a slightly different implementation from the remote queries
implementation. The remote queries implementation will send the file
path to open to the extension host, and the extension host will simply
open the given file path. If someone is able to inject JavaScript into
the webview, this would allow them to open an arbitrary file in VSCode.

By moving the file path logic to the extension host, we can ensure that
we only allow opening the actual query file.
2022-10-12 13:46:37 +02:00
Andrew Eisenberg
a071470c5a QueryServer: Add support for new query-server (#1508)
* QueryServer: Add support for new query-server

* Add a new canary flag to enable new query server support

* Add evaluation results to query object

Ensures better backwards compatibility with legacy query objects.

* Fix query server command name

* Add log message for new query server

* Use only legacy results

Co-authored-by: alexet <alexet@semmle.com>
2022-10-12 12:19:19 +01:00
Koen Vlaswinkel
2ae95144a5 Show notification to user when loading varaint analysis fails 2022-10-12 12:47:32 +02:00
Koen Vlaswinkel
60faed1ccc Merge remote-tracking branch 'origin/main' into koesie10/view-loaded-message 2022-10-12 10:41:18 +02:00
Koen Vlaswinkel
6e6ea76c97 Rename settingState to initialSettingState 2022-10-12 10:40:08 +02:00
Robert
d30eb27320 Move inspect to config.ts to reduce duplication of knowledge 2022-10-12 10:39:23 +02:00
Angela P Wen
2104cb3d09 Bump CLI version to 2.11.1 for integration tests (#1583) 2022-10-11 14:01:06 -07:00
Henry Mercer
5644206777 Merge pull request #1581 from github/henrymercer/update-codeowners
Update CODEOWNERS
2022-10-11 16:48:39 +01:00
Koen Vlaswinkel
a6a0ee5f50 Merge pull request #1580 from github/koesie10/views-deps
Fix missing dependencies and cleanup of addEventListener
2022-10-11 17:14:15 +02:00
Henry Mercer
74c1e583b4 Update CODEOWNERS
With this change, secexp will own all folders named `remote-queries` or `variant-analysis`.
2022-10-11 16:04:27 +01:00
Koen Vlaswinkel
326653e25a Fix missing dependencies and cleanup of addEventListener
This will implement ebba9949a8
and d18e3dd40e
for the `Compare` and `RemoteQueries` views. These should not be
impacted in the same way as the `VariantAnalysis` view, but this will
make them consistent and more resilient to future changes.
2022-10-11 17:01:02 +02:00
Koen Vlaswinkel
0d057aed3f Merge pull request #1578 from github/koesie10/mrva-performance
Fix freezing of live results view
2022-10-11 16:48:49 +02:00
Koen Vlaswinkel
c90eede573 Merge pull request #1572 from github/koesie10/request-repo-results-message
Implement `requestRepoResults` message
2022-10-11 16:48:34 +02:00
Koen Vlaswinkel
ebba9949a8 Add cleanup function to registering the message listener
This cleanup function would never be called in normal operation, but if
we do decide to add a dependency to this `useEffect`, this will ensure
that only one listener is registered at a time.
2022-10-11 16:26:42 +02:00
Koen Vlaswinkel
d18e3dd40e Fix performance of variant analysis view
When the variant analysis view was being rerendered, we were also
reregistering the message listeners, while not deregistering the old
ones. This resulted in a loop of message listeners being registered,
and the variant analysis being rerendered every time a message was
received by one of the listeners. This will ensure that the listener
is only registered once to prevent this from happening.
2022-10-11 16:24:20 +02:00
Koen Vlaswinkel
9355f0633a Run monitorVariantAnalysis on a completed variant analysis
The `monitorVariantAnalysis` command will send the repository states and
finish off any downloads. Without this, the view not will not load
properly.
2022-10-11 16:22:20 +02:00
Koen Vlaswinkel
f553523f73 Add command for opening existing variant analysis
To make debugging the view easier and prevent needing to run a variant
analysis for each change, this will add a simple command which opens a
variant analysis by its ID. This it not intended to be visible to users
at any point.
2022-10-11 12:02:46 +02:00
Koen Vlaswinkel
627bb59bd5 Merge remote-tracking branch 'origin/koesie10/request-repo-results-message' into koesie10/view-loaded-message 2022-10-11 11:11:39 +02:00
Koen Vlaswinkel
95cbe02768 Use unzipped file path for loading results 2022-10-11 11:10:45 +02:00
Koen Vlaswinkel
e73a6874b2 Merge remote-tracking branch 'origin/main' into koesie10/request-repo-results-message 2022-10-11 11:08:49 +02:00
Elena Tanasoiu
dc6ae6cc39 Merge pull request #1576 from github/elenatanasoiu/unzip
Make download method handle zip files
2022-10-11 09:13:18 +01:00
Elena Tanasoiu
3902596823 Use real zip file for VariantAnalysisManager download tests
Now that we're unzipping results, we also have to use something closer
to a zip file when testing download functionality for the
`variantAnalysisManager`.

The `variantAnalysisManager` has access to the
`variantAnalysisResultsManager` so we could've stubbed the result
manager's `download` method instead of going as far as using a zip
fixture.

However, since the results manager is private it seems bad to make it
public in order to stub one of its methods.

So using realistic data in the setup seems like a good compromise.
2022-10-10 23:40:28 +01:00
Elena Tanasoiu
c400485a4e Delete duplicate test
This checks the same thing as the test before it.
2022-10-10 23:40:28 +01:00
Elena Tanasoiu
1a7ddcf843 Make download method handle zip files
This will:
- download a zip file as an ArrayBuffer
- save the file as `results.zip`
- unzip the contents into a `results/` folder

For the tests:

- In order to check whether we're saving the correct files in the tests,
we've had to make the `getRepoStorageDirectory` method public.
Unfortunately the temporary file path generated for tests is random so
we're not able to hardcode it.

- Now that we have a real zip file to use in our tests, we're first
converting this file into an ArrayBuffer, then stubbing the API to
return it. We then check that it's saved and unzipped correctly.
2022-10-10 23:38:48 +01:00
Elena Tanasoiu
7cef45c434 Use real zip file in our download tests
This matches what type of file we'd expect in real life: a zip file
containing a sarif file.

We've copied an example `results.sarif` file from other tests in the
`no-workspace` folder.
2022-10-10 23:38:21 +01:00
Elena Tanasoiu
69b06ae95c Make getVariantAnalysisRepoResult return the correct type
We expect this method to return a zip file which can be typed to an
`ArrayBuffer`. In the following commits we'll read this buffer and save it
as a zip file.
2022-10-10 23:07:04 +01:00
Koen Vlaswinkel
ae2bd81215 Use new test config for testings currently writing to the config 2022-10-10 15:41:52 +02:00
Koen Vlaswinkel
b9be9cff9f Add new class and helper for setting test config values
This class will be used to set test config values for the tests. It is
able to set the config value to a specified value for every test and
restore the value to the original value after the test.
2022-10-10 15:40:42 +02:00
Koen Vlaswinkel
8c5d73bd76 Simplify tests index-template
Instead of using the `glob` library and a custom promise, this will use
`glob-promise` which is used by other parts of the codebase already.
This reduces the amount of code which manually needs to call `reject`
and makes it easier to read.
2022-10-10 15:30:31 +02:00
Koen Vlaswinkel
81b53c9c19 Merge pull request #1573 from github/koesie10/real-cancellation-token
Use real `CancellationTokenSource` in tests
2022-10-10 09:15:20 +02:00
Elena Tanasoiu
a232b56bcd Merge pull request #1566 from github/shati-elena/query-history-analysis-added
Emit `variantAnalysisAdded` event
2022-10-07 17:32:35 +01:00
Elena Tanasoiu
c26d786a1c Emit variantAnalysisAdded event
When we first submit the variant analysis for processing, we'd like to update
the query history panel.

At the moment we're just adding the setup for triggering the event. In a future
PR we'll consume this event and change the query history panel accordingly.

In order for this to happen we will need to introduce a new `VariantAnalysisHistoryItem`
type which will massage the data we get from the API into a type which the Query
History panel can consume.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-10-07 17:08:39 +01:00
Koen Vlaswinkel
99d2df2067 Remove mock variant analysis view
The mock variant analysis view would only show the loading message. This
completely removes it since it does not provide value anymore.
2022-10-07 15:07:06 +02:00
Koen Vlaswinkel
866b137fd4 Remove default values from VariantAnalysis component
The `VariantAnalysis` component will now only receive values from the
`VariantAnalysisView`. We still allow setting defaults to support
Storybook.
2022-10-07 15:02:34 +02:00
Koen Vlaswinkel
76a00e5fa5 Send variant analysis on viewLoaded message
When the `viewLoaded` message is received by the view, it will now
retrieve the variant analysis from the manager and send it to the
view. This will allow the view to display the variant analysis.
2022-10-07 14:57:38 +02:00
Koen Vlaswinkel
f0d71ba356 Use real CancellationTokenSource in tests
This will change tests that are using a mocked `CancellationTokenSource`
to use a real `CancellationTokenSource` instead. Tests are run inside
VSCode, so we can use these without mocking.
2022-10-07 14:30:48 +02:00
Koen Vlaswinkel
b4fbfb6d2b Merge pull request #1570 from github/koesie10/variant-analysis-results-manager
Add variant analysis results manager
2022-10-07 14:08:03 +02:00
Koen Vlaswinkel
1d02c19854 Add tests for RepoRow expansion 2022-10-07 14:00:46 +02:00
Koen Vlaswinkel
3167ceec91 Send message to webview when results are loaded 2022-10-07 12:32:15 +02:00
Koen Vlaswinkel
fba49020e3 Send message to load results when expanding a repo item 2022-10-07 12:31:41 +02:00
Koen Vlaswinkel
dea36820e4 Add command to load results
This adds a new VSCode command which can be used to load results.
2022-10-07 12:30:55 +02:00
Koen Vlaswinkel
60df319754 Save repo task object to storage
To create the interpreted and raw results from the SARIF/BQRS files, we
need some information from the repo task object. This will store the
repo task object to the filesystem as JSON so we can read them when
loading results.
2022-10-07 12:28:19 +02:00
Koen Vlaswinkel
0bdee6e77e Switch caching/loading of results to use the full name
In most cases, we will not have access to the full repo task object
since this needs to be retrieved from the API. Since we are only using
the full name from the repo task object, we can just use the full name
instead.
2022-10-07 12:24:51 +02:00
Koen Vlaswinkel
88440ba148 Store variant analyses in manager
This will store all variant analysis that are run in the manager. Right
now, it only stores the variant analyses in memory. In the future, these
will be loaded from the query history and can be restored after a
restart.
2022-10-07 12:21:57 +02:00
Koen Vlaswinkel
a0fb3b47c8 Merge remote-tracking branch 'origin/main' into koesie10/variant-analysis-results-manager 2022-10-07 11:36:17 +02:00
Koen Vlaswinkel
86d10b439b Merge pull request #1550 from github/koesie10/restore-mrva-on-restart
Restore variant analysis view on restart of VSCode
2022-10-07 11:29:18 +02:00
Koen Vlaswinkel
902c489979 Use nullish coalescing operator for loading results 2022-10-07 10:28:13 +02:00
Koen Vlaswinkel
7fed5baebc Merge remote-tracking branch 'origin/main' into koesie10/restore-mrva-on-restart 2022-10-06 18:30:24 +02:00
dependabot[bot]
d3e961ffb3 Bump @primer/octicons-react in /extensions/ql-vscode (#1569)
Bumps [@primer/octicons-react](https://github.com/primer/octicons) from 16.3.0 to 17.6.0.
- [Release notes](https://github.com/primer/octicons/releases)
- [Changelog](https://github.com/primer/octicons/blob/main/CHANGELOG.md)
- [Commits](https://github.com/primer/octicons/compare/v16.3.0...v17.6.0)

---
updated-dependencies:
- dependency-name: "@primer/octicons-react"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-06 08:29:04 -07:00
Koen Vlaswinkel
a20d9102e6 Add variant analysis results manager
This adds a new variant analysis results manager which is responsible
for downloading and loading variant analysis results to/from the
filesystem. It is essentially the `AnalysesResultsManager` modified to
suit the variant analysis results.
2022-10-06 15:13:52 +02:00
Koen Vlaswinkel
131d252a8b Merge pull request #1567 from github/koesie10/set-repo-results-message
Implement message for setting download status
2022-10-06 10:43:46 +02:00
Koen Vlaswinkel
90023137ca Merge pull request #1562 from github/koesie10/set-variant-analysis-message
Implement `setVariantAnalysis` message
2022-10-05 16:20:32 +02:00
Koen Vlaswinkel
fcecfa112e Merge remote-tracking branch 'origin/main' into koesie10/restore-mrva-on-restart 2022-10-05 13:01:24 +02:00
Koen Vlaswinkel
303a7d1662 Show loading icon when downloading repo results 2022-10-05 12:58:28 +02:00
Koen Vlaswinkel
7c935b37b0 Receive setRepoStates message in webview 2022-10-05 12:58:28 +02:00
Koen Vlaswinkel
339fc9a755 Post setRepoStates message when downloading 2022-10-05 12:58:28 +02:00
Koen Vlaswinkel
4138ca1085 Receive setRepoResults message in React component 2022-10-05 12:58:28 +02:00
Koen Vlaswinkel
6941584214 Add variant analysis view to disposables of manager 2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
525d7f5f3d Receive setVariantAnalysis message in React component
This will store the variant analysis in the React state and replace it
when the `setVariantAnalysis` message is received.
2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
84621b7ecc Fix variant analysis monitor failing
All fields in the variant analysis skipped repositories are optional,
but this was not properly defined in the API types. This will correct
the types and the functions processing the data such that they handle
non-existing fields.
2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
2baa53a149 Post message to view when variant analysis is updated 2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
15579012f1 Emit event when variant analysis is updated 2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
28b00b249b Update variant analysis from API response
This will ensure that when we return a new variant analysis summary from
the API, the variant analysis object will be updated.
2022-10-05 12:53:05 +02:00
Koen Vlaswinkel
401da636a0 Keep track of opened variant analysis views
To be able to send messages to the open view for a variant analysis, we
need to have a reference to the view. This is done by keeping track of
all open views in a dictionary indexed by their variant analysis ID.

We currently only allow one view per variant analysis, but do allow
multiple variant analysis views to be open at a time. In the future, we
may want to allow multiple views per variant analysis (such that e.g.
"Split right" works), but this is not supported yet.

The reason for the indirection through the interfaces is to prevent
circular dependencies between the variant analysis view and the manager.
2022-10-05 12:53:02 +02:00
Elena Tanasoiu
ab9cf465cc Merge pull request #1559 from github/elenatanasoiu/download-variant-analysis-results
Download variant analysis results
2022-10-05 11:43:05 +01:00
Elena Tanasoiu
bb7246b612 Pass in variant analysis summary to autodownload command
This was missed in 4f84376faa
2022-10-04 18:09:02 +01:00
Elena Tanasoiu
b82cd8b6f4 Use real cancellation token match VSCode behaviour 2022-10-04 18:08:50 +01:00
Elena Tanasoiu
f56f017a84 Generate ids using faker for test factories 2022-10-04 17:07:15 +01:00
Elena Tanasoiu
7dc5eebcc1 Generate a real NotFoundRepoGroup
This won't have an `id` field. We initially generated this the same
way we did for all other skipped repos, but this one is special because
it's only providing the fullName field, while the others also provide
`id` and `private`.
2022-10-04 16:36:00 +01:00
Elena Tanasoiu
644a83d6d8 Only use safe navigator for optional fields 2022-10-04 16:35:59 +01:00
Elena Tanasoiu
4f84376faa Make monitor trigger autodownload of result when a new repo is scanned
This introduces a new `autoDownloadVariantAnalysisResult` command which
will be called by the VariantAnalysisMonitor every time it detects a new
repo has been scanned.

In turn, this will use the `autoDownloadVariantAnalysisResult` method
which we defined in an earlier commit on the VariantAnalysisManager.
2022-10-04 16:35:59 +01:00
Elena Tanasoiu
5e76c08f84 Only import what we need from vscode in VariantAnalysisMonitor
In the next commit, we'll also import `commands`.
2022-10-04 16:35:59 +01:00
Elena Tanasoiu
765c956481 Introduce download method on VariantAnalysisManager
This method will be called from the VariantAnalysisMonitor once
a new repo has been scanned.

It will then perform an API request to get the repo task for it,
which will contain an `artifact_url`.

Finally it will use the API method we introduced in the previous commit
to download the result for the repo and then save it on disk.
2022-10-04 16:35:59 +01:00
Elena Tanasoiu
deac8c8c02 Add API method for download scanned repo result
This will download the result for a particular repo by making a call
to the download URL via octokit.
2022-10-04 16:35:59 +01:00
Elena Tanasoiu
a47031b0d5 Call monitor from new VariantAnalysisManager class 2022-10-04 16:35:59 +01:00
Elena Tanasoiu
3bf27b3472 Set up factories for VSCode VariantAnalysis
In a previous PR [1] we introduced factories for generating variant analyses
(and their associated objects) that were returned from the API.

Let's also introduce factories for generating their VSCode equivalent.

We can immediately use them for generating a VariantAnalysis object for the
monitor tests.

[1]: https://github.com/github/vscode-codeql/pull/1545
2022-10-04 16:35:58 +01:00
Shati Patel
9422c6d65c Fix running Jest unit tests from the debugger on Windows (#1563) 2022-10-04 13:28:49 +01:00
Koen Vlaswinkel
b81e3c7b94 Merge pull request #1560 from github/koesie10/reduce-duplication-repo-rows
Combine repository row components into a single component
2022-10-04 12:19:50 +02:00
Andrew Eisenberg
011eee1d16 Merge pull request #1565 from github/aeisenberg/db-folder-check
Use better heuristics when checking db folders
2022-10-03 14:50:57 -07:00
Andrew Eisenberg
924d24b106 Update extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts
Co-authored-by: Angela P Wen <angelapwen@github.com>
2022-10-03 14:36:34 -07:00
Andrew Eisenberg
54ba5ced09 Use better heuristics when chekcking db folders 2022-10-03 14:07:55 -07:00
Koen Vlaswinkel
78a90ffa92 Merge pull request #1553 from github/koesie10/show-variant-analysis-view
Open variant analysis view after submission
2022-10-03 11:21:26 +02:00
Koen Vlaswinkel
b95ee896df Combine repository row components into a single component
This is a follow-up to clean up the skipped and analyzed repository
component duplication. The rows in both tabs are very similar, so this
will combine them to use a single component.
2022-10-03 11:07:14 +02:00
Koen Vlaswinkel
d33b07b2d1 Merge branch 'main' into koesie10/show-variant-analysis-view 2022-10-03 10:06:58 +02:00
James Fletcher
3d7f303c65 Merge pull request #1558 from github/lgtm-update
Remove mentions of lgtm.com from README
2022-09-30 20:23:21 +01:00
Andrew Eisenberg
540d6758d1 Merge pull request #1557 from github/aeisenberg/view-column
Ensure results view is opened in column beside
2022-09-30 09:22:21 -07:00
Koen Vlaswinkel
b5b34743f1 Open variant analysis view after submission
This will open the variant analysis view after the variant analysis has
been submitted. It will also show a notification that the analysis has
been submitted, which includes the query name.
2022-09-30 17:01:12 +02:00
Robert
0a6db47b5f Merge pull request #1549 from github/robertbrignull/skipped-repos
Implement skipped repositories tabs
2022-09-30 13:10:12 +01:00
James Fletcher
f679a2efec remove lgtm.com from reaadme 2022-09-30 11:47:43 +00:00
Robert
72253a1bb8 Merge branch 'main' into robertbrignull/skipped-repos 2022-09-30 11:22:38 +01:00
Elena Tanasoiu
2065c7d75c Merge pull request #1545 from github/elenatanasoiu/monitor-variant-analysis
Implement monitoring for variant analysis live results
2022-09-30 10:00:03 +01:00
Elena Tanasoiu
ff4ea3e4c8 Exit early if variant analysis completes 2022-09-30 09:41:23 +01:00
Andrew Eisenberg
9bd932294a Ensure results view is opened in column beside
The results view will always open next to the current editor.
2022-09-29 13:04:28 -07:00
Andrew Eisenberg
afdc8164c8 Merge pull request #1554 from github/aeisenberg/bump-to-2.11.0-cli
Update tests to v2.11.0
2022-09-29 09:18:40 -07:00
dependabot[bot]
ea022f4cde Bump @octokit/rest from 18.6.0 to 19.0.4 in /extensions/ql-vscode (#1551)
Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 18.6.0 to 19.0.4.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v18.6.0...v19.0.4)

---
updated-dependencies:
- dependency-name: "@octokit/rest"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-29 09:12:38 -07:00
Andrew Eisenberg
48ced51035 Update tests to v2.11.0 2022-09-29 07:50:43 -07:00
Koen Vlaswinkel
177688dc56 Merge pull request #1544 from github/koesie10/scanned-repos-tab
Add analyzed repositories component
2022-09-29 16:05:20 +02:00
Koen Vlaswinkel
c5cbf92b3a Merge pull request #1546 from github/koesie10/refactor-raw-results-table
Refactor `RawResultsTable` component
2022-09-29 16:03:29 +02:00
Robert
529ceb133e Fix text when 1 repo is shown 2022-09-29 13:02:22 +01:00
Robert
baaa3d31c0 Update codicon styling 2022-09-29 13:00:34 +01:00
Robert
9629c99ccb Move alertTitle and alertMessage to props 2022-09-29 13:00:18 +01:00
Robert
7ade7be0c4 Use simple array type 2022-09-29 12:40:40 +01:00
Robert
4272cee01b Rename files to end .stories.tsx 2022-09-29 12:31:53 +01:00
Koen Vlaswinkel
d8fbc56ec2 Restore variant analysis view on restart of VSCode
This implements persistence for the variant analysis webview, allowing
the webview panel to be restored when VSCode is restarted. It's probably
easier to add this now than to try to add it later.

The basic idea is that there are no real differences when opening the
webview for the first time. However, when VSCode is restarted it will
use the `VariantAnalysisViewSerializer` to restore the webview panel.
In our case this means recreating the `VariantAnalysisView`.

To fully test this, I've also added a mock variant analysis ID as the
state of the webview. This value is now randomly generated when calling
the `codeQL.mockVariantAnalysisView` command. This allows us to test
opening multiple webviews and that the webviews are restored with the
correct state.

See: https://code.visualstudio.com/api/extension-guides/webview#persistence
2022-09-29 13:26:56 +02:00
Elena Tanasoiu
e41b0ff779 Only check for failure reason during monitoring
There's no need to check for the status of variant analysis in order to
mark it as failed. The presence of a failure reason should be enough.
2022-09-29 10:29:05 +01:00
Koen Vlaswinkel
cf3ba32906 Merge pull request #1548 from github/koesie10/filter-raw-results
Hide repositories with raw results without rows
2022-09-29 11:08:19 +02:00
Koen Vlaswinkel
741d364a52 Simplify check for raw results
Co-authored-by: Robert <robertbrignull@github.com>
2022-09-29 10:18:33 +02:00
Elena Tanasoiu
49a2555dab Only send repository ids in databases param
At the moment we're only able to send one of:
- repositories
- repositoryLists
- repositoryOwners

In the future, we intend to be able to send a combination of these
but at the moment the API will only ever allow you to send one.

So let's be consistent and just send `repositories` here.
2022-09-28 16:20:33 +01:00
Elena Tanasoiu
f4e6a0db9b Remove failure_message
Since the API would either return a real value or skip this field.
2022-09-28 16:07:09 +01:00
Elena Tanasoiu
4e7b89864c Declare type of status in function signature
Rather than casting it later in the function body.
2022-09-28 15:54:38 +01:00
Elena Tanasoiu
02443b5ddd Add missing / to nwo names 2022-09-28 15:54:38 +01:00
Elena Tanasoiu
50b507dba5 Parse failure reason correctly 2022-09-28 15:54:38 +01:00
Elena Tanasoiu
aea5d33c42 Be verbose about expected output 2022-09-28 15:54:38 +01:00
Elena Tanasoiu
b2427a6534 Rename nwos to full_names
To match what we now return in the API.
2022-09-28 15:54:37 +01:00
Elena Tanasoiu
b95f6a5afb Replace forEach & push with map 2022-09-28 15:54:27 +01:00
Koen Vlaswinkel
7b7413ba26 Merge pull request #1547 from github/koesie10/fix-remote-query-run
Fix not being able to run variant analyses
2022-09-28 13:35:53 +02:00
Koen Vlaswinkel
d33fa5df8a Hide repositories with raw results without rows
Currently, when running a query which produces raw results, we will show
all repositories, even if they do not have any results. This change will
ensure that we are only showing repositories which have results. This
matches the behavior for queries which produce interpreted results.
2022-09-28 13:34:31 +02:00
Koen Vlaswinkel
2efff809eb Fix not being able to run variant analyses
The `controllerRepo` parameter was being encoded/escaped by Octokit,
resulting in a URL like
`repos/dsp-testing%2Fqc-controller/code-scanning/codeql/queries` rather
than `repos/dsp-testing/qc-controller/code-scanning/codeql/queries`.

This switches it to use the ID instead, since we already have the ID
and do not have access to the owner and repo separately anymore.
2022-09-28 13:18:47 +02:00
Robert
c442ff5599 Implement skipped repositories tabs 2022-09-28 12:09:32 +01:00
Elena Tanasoiu
e4de8c6b9b Define mock variant analysis with scanned repos
Now that we have a monitor, we expect the variant analysis to return
a list of scanned repos.

Let's re-use our previous factory for creating mocked responses to
get a dummy variant analysis with scanned repos.
2022-09-28 11:33:47 +01:00
Elena Tanasoiu
c032e4f9a7 Hook up new monitor class to VScode command introduced earlier
In a previous commit we were submitting a variant analysis to the API
and then triggering a `monitorVariantAnalysis` command.

Here we're hooking up the command to the VariantAnalysisMonitor class.
2022-09-28 11:33:47 +01:00
Elena Tanasoiu
487cc7b088 Introduce a VariantAnalysisMonitor class
This will poll the API every 5 seconds for changes to the variant
analysis. By default it will continue to run for a maximum of 2 days,
or when the user closes VSCode.

The monitor will receive a variantAnalysis summary from the API that
will contain an up-to-date list of scanned repos.

The monitor will then return a list of scanned repo ids.

In a future PR we'll add the functionality to:
- update the UI for in progress/completed states
- raise error on timeout
- download the results
2022-09-28 11:33:47 +01:00
Elena Tanasoiu
d9e9c1b885 Make createMockApiResponse more convenient
So that we're able to:
- set the status value
- build scanned and skipped repos by default

For previous tests, we needed to perform checks on scanned & skipped
repos so we needed to build them outside of this method. When we re-use
this method for the VariantAnalysisMonitor, we will just need a generic
ApiResponse so we can create these repos inside the method.
2022-09-28 11:33:47 +01:00
Elena Tanasoiu
e19637b59c Extract methods for generating VariantAnalysis objects
We're going to need some of these methods to generate a valid VariantAnalysis.

We might as well extract them from the tests for the VariantAnalysisProcessor.
2022-09-28 11:33:47 +01:00
Elena Tanasoiu
066bf3fd26 Trigger monitoring processs
Once we submit a variant analysis and get our response from the API,
we'd like to set up a way to monitor the variant analysis as it starts
producing live results.

Here we're using a VSCode command to trigger a monitoring process which
will poll the API for changes.
2022-09-28 11:33:46 +01:00
Elena Tanasoiu
7ab1f3a83d Introduce a way to process API responses into variant analyses
This receives an API response and builds a VariantAnalysis from the fields.
2022-09-28 11:33:45 +01:00
Koen Vlaswinkel
e3e2fcc349 Refactor RawResultsTable component
The `RawResultsTable` was using inline styles, while we should prefer
to use styled components. This refactors it to use styled components and
also improves some other miscelleanous things (extracting the props to
a separate type and moving the `Cell` above the `Row` since the latter
uses the former).
2022-09-28 11:32:11 +02:00
Elena Tanasoiu
17ed18a29d Install faker 2022-09-27 18:28:41 +01:00
Koen Vlaswinkel
110d930b68 Merge pull request #1543 from github/koesie10/outcome-panel
Add outcome panels
2022-09-27 15:20:24 +02:00
Koen Vlaswinkel
f8cc3aec32 Add analyzed repositories component
This adds the analyzed repositories component for showing within the
"Analyzed" tab. I wasn't completely sure whether there should be a
difference between "Pending" and "In progress", but pending will now not
show an icon, while in progress will show a spinner.

For the collapsible items, it does not reuse the `CollapsibleItem`
component because that component is tightly coupled with the styles
of the remote queries component.
2022-09-27 14:59:18 +02:00
Koen Vlaswinkel
f408418f23 Merge remote-tracking branch 'origin/main' into koesie10/outcome-panel 2022-09-27 14:03:13 +02:00
Robert
0b638b6ae1 Merge pull request #1538 from github/robertbrignull/submit-variant-analysis
Implement submitting a live-results variant analysis
2022-09-27 10:36:24 +01:00
Robert
ce7c7119c7 Return a VariantAnalysis from runRemoteQuery 2022-09-27 10:11:49 +01:00
Robert
5dce5e83b0 Promise resolves successfully but result is undefined 2022-09-26 15:08:37 +01:00
Robert
ac3b94dac8 Merge pull request #1541 from github/robertbrignull/loading-component
Add loading component
2022-09-26 14:55:41 +01:00
Robert
519c3039b8 Don't wait for dialog box 2022-09-26 14:55:00 +01:00
Koen Vlaswinkel
0a5c272b17 Merge pull request #1539 from github/koesie10/alert-components
Add Alert component for showing warnings and errors
2022-09-26 15:00:38 +02:00
Robert
32ec043cbe Hook into main variant analysis component 2022-09-26 13:52:37 +01:00
Robert
454a1eab39 Adjust styling 2022-09-26 13:48:32 +01:00
Koen Vlaswinkel
d3701944bf Add outcome panels
This creates the component for showing the outcome panels. It does not
implement the content of each individual panel; it only implements the
tabs, panel views, and the general warnings.
2022-09-26 14:03:03 +02:00
Koen Vlaswinkel
43bcd69e39 Use proper casing for alert type text 2022-09-26 14:01:22 +02:00
Koen Vlaswinkel
53a17d5728 Use better color variables for alerts 2022-09-26 13:59:55 +02:00
Andrew Eisenberg
b0dab966f3 Merge pull request #1498 from alexet/alexet/prepare-new-qs
QueryServer: Abstract over the query running parts.
2022-09-23 11:10:39 -07:00
Andrew Eisenberg
e4a3161283 Merge pull request #1540 from github/aeisenberg/safe-max-fix
Ensure `safeMax` is safe for undefined values
2022-09-23 10:49:02 -07:00
Robert
47e53da89c Fix typo 2022-09-23 17:25:49 +01:00
Robert
f8f81cfb40 Add loading component 2022-09-23 17:20:13 +01:00
Andrew Eisenberg
fd43bed99d Merge remote-tracking branch 'alexet/alexet/prepare-new-qs' into alexet/prepare-new-qs 2022-09-23 08:56:38 -07:00
Andrew Eisenberg
ffc3d406c2 Merge branch 'main' into alexet/prepare-new-qs 2022-09-23 08:56:10 -07:00
Andrew Eisenberg
11bf3c9462 Ensure safeMax is safe for undefined values
I came across this when I had a query that threw an error while running
for unrelated reasons. At this point, the query results were in a bad
state, but this caused `safeMax` to be called with `undefined` and
it prevented the extension from starting. This changed fixed the error.
2022-09-23 08:21:44 -07:00
Robert
9b2c40b298 Move duplicate definition to constant 2022-09-23 15:41:28 +01:00
Robert
abf6c6f108 Remove unnecessary async 2022-09-23 15:41:11 +01:00
Robert
910c1b7352 Stub isVariantAnalysisLiveResultsEnabled instead of updating setting 2022-09-23 14:43:48 +01:00
Robert
f47d6ec21c Stub getRepositoryFromNwoStub 2022-09-23 14:43:16 +01:00
Robert
0e23dd59db Remove settings duplicated in beforeEach 2022-09-23 14:11:00 +01:00
Koen Vlaswinkel
160a0aebfe Add Alert component for showing warnings and errors 2022-09-23 15:10:15 +02:00
Robert
4d3385825b Move test to pure-tests directory 2022-09-23 13:05:07 +01:00
Robert
80862944d8 Add tests of parseVariantAnalysisQueryLanguage 2022-09-23 12:31:11 +01:00
Robert
91344a74f6 Fix tests that use parseResponse 2022-09-23 12:31:01 +01:00
Robert
7538ad1ba4 Add parseVariantAnalysisQueryLanguage method 2022-09-23 12:21:14 +01:00
Robert
24c2663fe7 Use separate describe blocks for live results enabled vs disabled 2022-09-23 12:03:27 +01:00
Robert
50aaf3b537 Move more implementation of fetching the controller repo into getControllerRepo
This involved changing a few different methods to take a Repository object
instead of taking owner and repo separately. Overall I think this is a good change.
2022-09-23 11:01:25 +01:00
Robert
847082cd30 Set live results mode to off both before and after tests 2022-09-23 10:41:14 +01:00
Koen Vlaswinkel
8c7c197b22 Merge pull request #1537 from github/koesie10/icons-storybook
Add Storybook stories for icons
2022-09-23 09:28:14 +02:00
dependabot[bot]
1f95eb2f49 Bump sinon from 13.0.1 to 14.0.0 in /extensions/ql-vscode (#1535)
Bumps [sinon](https://github.com/sinonjs/sinon) from 13.0.1 to 14.0.0.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v13.0.1...v14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-22 10:38:00 -07:00
Robert
7874a34947 Add tests for runRemoteQuery when in live results mode 2022-09-22 17:33:35 +01:00
Koen Vlaswinkel
a74c8a7cee Merge pull request #1536 from github/koesie10/eslint-plugin-react-hooks
Add ESLint plugin for React hooks
2022-09-22 17:39:12 +02:00
Koen Vlaswinkel
3aced3c4d3 Add Storybook stories for icons
This will add Storybook stories for the error, success, and warning
icons, as well as for the generic `Codicon` component.

To show the available icons for the `Codicon` component, a static JSON
list is generated from the contents of a CSV file included as part of
the `@vscode/codicons` npm package. The command to regenerate the file
is included in the story.
2022-09-22 17:37:33 +02:00
Koen Vlaswinkel
bec23f36d2 Add ESLint plugin for React hooks
This will add the ESLint plugin for React hooks which will automatically
check that all dependencies are listed in `useMemo`, `useEffect`, etc.

See: https://www.npmjs.com/package/eslint-plugin-react-hooks
2022-09-22 17:10:45 +02:00
Koen Vlaswinkel
92bbf3a2e8 Merge pull request #1534 from github/koesie10/variant-analysis-header-domain-model
Use domain model for VariantAnalysisHeader
2022-09-22 15:27:32 +02:00
Koen Vlaswinkel
5c478e98d9 Merge pull request #1532 from github/koesie10/split-common-components
Split CodePaths and FileCodeSnippet into multiple files
2022-09-22 15:09:42 +02:00
Koen Vlaswinkel
f26988731e Add missed dependency in useMemo 2022-09-22 15:03:35 +02:00
Koen Vlaswinkel
e6f9ce050b Extract potentially reusable functions to shared file 2022-09-22 15:00:09 +02:00
Robert
52f993f748 Implement submitting a variant analysis 2022-09-22 12:20:24 +01:00
Charis Kyriakou
99fe65f6f7 Update skipped repo groups structure for variant analysis (#1533) 2022-09-22 12:02:30 +01:00
Robert
7d721d9544 Add getControllerRepoId method 2022-09-22 11:35:16 +01:00
Koen Vlaswinkel
1005ecdc6a Fix mock view 2022-09-22 12:06:04 +02:00
Koen Vlaswinkel
c9f65be721 Use domain model for VariantAnalysisHeader
This will change the VariantAnalysisHeader to take the VariantAnalysis
domain model instead of a large amount of props.

It also adds the `canceled` status to the `VariantAnalysisStatus` to
represent a stopped variant analysis.
2022-09-22 11:58:26 +02:00
Koen Vlaswinkel
9ad28f36b4 Split CodePaths and FileCodeSnippet into multiple files 2022-09-22 11:12:03 +02:00
Koen Vlaswinkel
9c076152cb Merge pull request #1523 from github/koesie10/refactor-common-components
Refactor CodePaths and FileCodeSnippet components
2022-09-22 11:03:56 +02:00
Charis Kyriakou
bbb6f10f17 Add new GitHub API client with functions for new MRVA flow (#1527) 2022-09-22 09:02:44 +00:00
Koen Vlaswinkel
8a671be85c Merge remote-tracking branch 'origin/main' into koesie10/refactor-common-components 2022-09-22 10:51:30 +02:00
Koen Vlaswinkel
0476815f8a Merge pull request #1531 from github/koesie10/move-components-to-common
Move CodePaths and FileCodeSnippet to common directory
2022-09-22 10:43:56 +02:00
Koen Vlaswinkel
53dfd1243f Move CodePaths and FileCodeSnippet to common directory 2022-09-22 10:26:55 +02:00
Koen Vlaswinkel
d69772d1f8 Merge pull request #1517 from github/koesie10/variant-analysis-stats
Add variant analysis stats component
2022-09-22 10:06:22 +02:00
Koen Vlaswinkel
2fd5f38574 Merge pull request #1528 from github/koesie10/fix-export-all-selection
Fix "Export All" not always exporting the correct query
2022-09-22 10:05:44 +02:00
Koen Vlaswinkel
06d22841cf Rename getQueryById to getRemoteQueryById 2022-09-22 09:30:07 +02:00
Koen Vlaswinkel
0133cd7734 Improve error message for not found queries
Co-authored-by: Charis Kyriakou <charisk@users.noreply.github.com>
2022-09-22 09:28:57 +02:00
Robert
a53c04e2c1 Merge pull request #1530 from github/robertbrignull/release-instructions
Clarify step of the release process
2022-09-21 17:16:09 +01:00
Robert
eba6c190e8 Merge pull request #1529 from github/version/bump-to-v1.7.1
Bump version to v1.7.1
2022-09-21 17:09:41 +01:00
Charis Kyriakou
d0e6e3ca89 Define variant analysis domain models (#1521) 2022-09-21 16:59:44 +01:00
Robert
cc00456cbc Clarify step of the release process 2022-09-21 16:56:28 +01:00
robertbrignull
434567aa34 Bump version to v1.7.1 2022-09-21 15:22:10 +00:00
Koen Vlaswinkel
7b1a93d7c6 Use HorizontalSpace instead of margin-left 2022-09-21 15:38:28 +02:00
Koen Vlaswinkel
d3ea84e863 Rename VariantAnalysisCompletionStats to VariantAnalysisStatusStats 2022-09-21 15:22:00 +02:00
Koen Vlaswinkel
1b6685ef6f Fix "Export All" not always exporting the correct query
The "Export All" button was always exporting the selected query, while a
different query could be open in a VSCode panel. This will ensure that
the query ID is passed to the export function, so that the correct query
is exported.
2022-09-21 14:24:54 +02:00
Koen Vlaswinkel
f26795ca17 Merge remote-tracking branch 'origin/main' into koesie10/variant-analysis-stats 2022-09-21 12:28:47 +02:00
Koen Vlaswinkel
617f7bab0a Extract icons to reusable components 2022-09-21 12:25:36 +02:00
Koen Vlaswinkel
8da1a28478 Add documentation to formatDecimal function 2022-09-21 12:16:37 +02:00
Koen Vlaswinkel
4518d9a81d Use div instead of empty styled component 2022-09-21 12:16:20 +02:00
Koen Vlaswinkel
3817133b5b Refactor CodePaths and FileCodeSnippet components
This refactors the CodePaths and FileCodeSnippet components to be more
readable and in style with the rest of the "new" components. It does the
following:

- Remove uses of the `style` and `sx` props; replace it by using
  `styled-components` instead
- Remove uses of Primer icons
- Split out the components into multiple files
- Change the colors of the severity to match VSCode colors (and make
  them themable)

I haven't removed the use of the Primer `Overlay` component yet, since
this component seems to do quite a lot and the VSCode WebView UI Toolkit
doesn't have a replacement for it.
2022-09-21 11:29:39 +02:00
Robert
c9b68caee4 Merge pull request #1526 from github/v1.7.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
Release PR for v1.7.0
2022-09-21 09:51:37 +01:00
Andrew Eisenberg
60c4d8d40a Merge pull request #1519 from github/aeisenberg/upgrade-db-fix
Avoid error after upgrading a database
2022-09-20 09:38:26 -07:00
Robert
1a9d63315f v1.7.0 2022-09-20 16:23:47 +01:00
Charis Kyriakou
5c8098f28d Move GitHub Actions API client to gh-api directory (#1524) 2022-09-20 15:52:55 +01:00
Charis Kyriakou
bcf70c6962 Move zipFile util to its own module for easy re-use (#1522) 2022-09-20 09:57:33 +01:00
Koen Vlaswinkel
64f33a5f44 Merge pull request #1520 from github/koesie10/react-components-common
Move common React components to separate directory
2022-09-20 10:51:37 +02:00
dependabot[bot]
48a527ad52 Bump applicationinsights from 1.8.7 to 2.3.5 in /extensions/ql-vscode (#1515)
Bumps [applicationinsights](https://github.com/microsoft/ApplicationInsights-node.js) from 1.8.7 to 2.3.5.
- [Release notes](https://github.com/microsoft/ApplicationInsights-node.js/releases)
- [Commits](https://github.com/microsoft/ApplicationInsights-node.js/compare/1.8.7...2.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-19 10:02:35 -07:00
Koen Vlaswinkel
faabe6d887 Move common React components to separate directory 2022-09-19 10:03:30 +02:00
Koen Vlaswinkel
4b8d611d86 Merge pull request #1512 from github/koesie10/variant-analysis-header
Add variant analysis top header
2022-09-19 09:21:21 +02:00
Andrew Eisenberg
bfc9a17ffb Update CHANGELOG 2022-09-16 16:19:58 -07:00
Andrew Eisenberg
a4a3f70984 Avoid error after upgrading a database
The `runUpgrade` query server command is mistakenly caching the old
dbscheme in memory after running the upgrade. The problem is in the
CLI. The workaround is to restart the query server after running an
upgrade. This is not a great solution, but considering that explicit
upgrades are now very rare. I do not think it is worth putting in
too much effort for a proper fix.
2022-09-16 15:58:39 -07:00
Koen Vlaswinkel
98bae3253d Merge branch 'koesie10/variant-analysis-header' into koesie10/variant-analysis-stats 2022-09-16 16:34:43 +02:00
Koen Vlaswinkel
70098aa19c Fix running unit tests on Windows 2022-09-16 14:58:26 +02:00
Koen Vlaswinkel
1261fdd41e Add stories for individual variant analysis header components 2022-09-16 14:43:02 +02:00
Koen Vlaswinkel
c914312e85 Move variant analysis header tests to individual components 2022-09-16 14:40:41 +02:00
Koen Vlaswinkel
cd2b5a8c59 Set timezone and locale for tests 2022-09-16 14:36:12 +02:00
Koen Vlaswinkel
29a43c7dc1 Merge pull request #1518 from github/koesie10/codeowners-mrva
Add correct CODEOWNERS for MRVA webviews
2022-09-16 13:45:23 +02:00
Koen Vlaswinkel
8ef3c3713b Add correct properties to variant analysis mock view 2022-09-16 13:33:34 +02:00
Koen Vlaswinkel
54f83d11d6 Add correct CODEOWNERS for MRVA webviews 2022-09-16 13:30:26 +02:00
Koen Vlaswinkel
22cfad6711 Add variant analysis stats to header component 2022-09-16 13:25:23 +02:00
Koen Vlaswinkel
cbc2650f30 Add tests for date and number formatting functions 2022-09-16 13:18:21 +02:00
Koen Vlaswinkel
55b060af97 Use browser date format 2022-09-16 13:18:21 +02:00
Koen Vlaswinkel
9f347d136b Format date according to designs 2022-09-16 13:18:21 +02:00
Koen Vlaswinkel
0d0367c39d Add tests for variant analysis stats component 2022-09-16 13:18:21 +02:00
Koen Vlaswinkel
ba0a30dcfe Add variant analysis stats component 2022-09-16 13:18:21 +02:00
Koen Vlaswinkel
3079d7f285 Split variant analysis header component 2022-09-16 12:10:13 +02:00
Koen Vlaswinkel
10eb355900 Merge pull request #1467 from github/koesie10/deprecate-lgtm-download
Deprecate download from LGTM by hiding it behind canary flag
2022-09-16 09:39:37 +02:00
Andrew Eisenberg
0daea7399a Merge branch 'main' into alexet/prepare-new-qs 2022-09-15 14:36:00 -07:00
Koen Vlaswinkel
1b0077a115 Implement PR feedback 2022-09-15 10:06:13 +02:00
Charis Kyriakou
db5e743055 Some more renames around webviews (#1513) 2022-09-14 16:01:49 +01:00
Charis Kyriakou
a6d63222f5 Consolidate view loading messages into one (#1511) 2022-09-14 15:19:21 +01:00
Koen Vlaswinkel
58e80ecce3 Rename styled components to be more descriptive 2022-09-14 16:14:30 +02:00
Koen Vlaswinkel
0ad44a3fe2 Use VSCodeLink for links in header 2022-09-14 16:11:20 +02:00
Charis Kyriakou
09dccc13a2 Renamed 'interface managers' to something more specific (#1510) 2022-09-14 13:09:43 +00:00
Koen Vlaswinkel
2cdded9cca Add story for variant analysis view 2022-09-14 14:27:37 +02:00
Koen Vlaswinkel
e8a0b24f57 Add Jest tests to VSCode config 2022-09-14 14:20:54 +02:00
Koen Vlaswinkel
182c2f3b8e Add variant analysis header to view 2022-09-14 14:14:19 +02:00
Koen Vlaswinkel
e5376b3469 Add DOM tests for variant analysis header 2022-09-14 13:56:38 +02:00
Koen Vlaswinkel
ef22cf174e Add React testing library 2022-09-14 13:56:38 +02:00
Koen Vlaswinkel
d158487081 Add variant analysis header 2022-09-14 13:56:38 +02:00
Charis Kyriakou
2e9c0c301c Add new variant analysis view (#1506) 2022-09-14 11:52:58 +00:00
Koen Vlaswinkel
f256e18041 Merge pull request #1509 from github/koesie10/fix-eslint-vscode-setting
Fix ESLint VSCode configuration
2022-09-14 10:46:46 +02:00
Koen Vlaswinkel
aa23680603 Fix ESLint VSCode configuration
The working directory of ESLint was not set directly, so ESLint warnings
did not show up in VSCode. This sets the working directory properly such
that ESLint warnings are shown in VSCode.

See: https://github.com/Microsoft/vscode-eslint#settings-options
2022-09-14 10:21:55 +02:00
Andrew Eisenberg
e5fe2148ab Add back the evaluation results to CompletedQuery
This ensures that queries created by new versions of the extension
can still be read by older versions of the extension.
2022-09-13 14:51:44 -07:00
Andrew Eisenberg
c44b7b1d78 Apply suggestions from code review 2022-09-13 14:51:44 -07:00
alexet
24ede1b66f QueryServer: Abstract over the query running parts of the query server in preperation for the new query server. 2022-09-13 14:51:44 -07:00
Andrew Eisenberg
6335b9881b Add back the evaluation results to CompletedQuery
This ensures that queries created by new versions of the extension
can still be read by older versions of the extension.
2022-09-13 14:08:35 -07:00
shati-patel
8c0fee5a2e Bump CLI version to 2.10.5 for integration tests 2022-09-13 16:58:21 +01:00
Koen Vlaswinkel
e95f8e85a8 Merge remote-tracking branch 'origin/main' into koesie10/deprecate-lgtm-download 2022-09-13 10:38:53 +02:00
Andrew Eisenberg
c6531a293e Apply suggestions from code review 2022-09-12 16:10:58 -07:00
alexet
e648d9c67c QueryServer: Abstract over the query running parts of the query server in preperation for the new query server. 2022-09-12 16:10:58 -07:00
Koen Vlaswinkel
45efca9425 Merge pull request #1503 from github/koesie10/remove-open-on-github-for-mrva
Remove open on GitHub item from cancelled local results
2022-09-09 09:25:05 +02:00
Alexander Eyers-Taylor
9071f54863 Don't display destructive upgrades any more. (#1501)
* Don't display destructive upgrades any more.

* Add change note

* Update extensions/ql-vscode/CHANGELOG.md

Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2022-09-08 18:57:12 +00:00
Koen Vlaswinkel
0aa34a51ff Merge pull request #1504 from github/koesie10/remove-query-name-from-query-id
Remove query name from query ID for MRVA queries
2022-09-08 17:04:09 +02:00
Koen Vlaswinkel
181b5d6f7b Remove query name from query ID for MRVA queries 2022-09-08 13:59:26 +02:00
Koen Vlaswinkel
7502fdee67 Remove open on GitHub item from cancelled local results
This will make a distinction between cancelled local and remote results,
allowing us to hide the *Open Variant Analysis on GitHub* item from
local failed/cancelled items. It also hides the *Show Evaluator Log*
items for cancelled/failed remote queries.
2022-09-08 13:36:50 +02:00
Koen Vlaswinkel
24652a84e4 Merge pull request #1497 from github/koesie10/storybook
Setup Storybook for testing UI components
2022-09-08 10:38:22 +02:00
Koen Vlaswinkel
2ee46cfd81 Add comment to .npmrc file for Storybook option 2022-09-08 09:22:08 +02:00
Alexander Eyers-Taylor
7c4eac8520 Avoid prompting for upgrades when they wouldn't be prompted. (#1500) 2022-09-07 17:53:37 +01:00
Koen Vlaswinkel
6fdc632743 Add overview page for Storybook 2022-09-07 17:06:23 +02:00
Koen Vlaswinkel
a38a0356a0 Remove MRVA prefix from component stories 2022-09-07 16:43:19 +02:00
Koen Vlaswinkel
9383b03971 Move Storybook config to TypeScript 2022-09-07 16:41:42 +02:00
Koen Vlaswinkel
baf130d60e Remove WebView UI Toolkit stories 2022-09-07 16:37:37 +02:00
Koen Vlaswinkel
d15e3885d7 Add documentation about parameters in preview.js 2022-09-07 16:36:24 +02:00
Koen Vlaswinkel
2211e2317d Add mock VSCode API in Storybook
This allows us to add a story for the "main" remote queries view.
2022-09-07 14:35:04 +02:00
Koen Vlaswinkel
6018ebaca9 Setup Storybook for testing UI components
This sets up Storybook for testing of React components. It adds stories
for some of the MRVA components. It does not add stories for the main
MRVA views since those are not independent of VSCode and need to be run
from within VSCode.
2022-09-06 10:55:55 +02:00
Koen Vlaswinkel
da9065101f Merge pull request #1492 from github/koesie10/retry-artifacts
Add retry for finding result-index artifact
2022-09-05 10:08:11 +02:00
Koen Vlaswinkel
80867e6f58 Move result-index availability check to monitorQuery 2022-09-02 15:47:17 +02:00
Dave Bartolomeo
5067fbc452 Merge pull request #1494 from github/version/bump-to-v1.6.13
Bump version to v1.6.13
2022-09-02 05:17:43 -04:00
dbartol
d88b5170ac Bump version to v1.6.13 2022-09-01 21:06:50 +00:00
Dave Bartolomeo
d4673d9ca0 Merge pull request #1493 from dbartol/v1.16.12
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.16.12
2022-09-01 16:38:14 -04:00
Dave Bartolomeo
87f45a7739 v1.16.12 2022-09-01 16:25:04 -04:00
Koen Vlaswinkel
0c89df9a80 Merge pull request #1482 from github/koesie10/bundle-codicons
Bundle Codicons using Webpack
2022-09-01 16:12:55 +02:00
Koen Vlaswinkel
57666bbbe3 Add retry for finding result-index artifact
It seems like the result-index artifact may not be available immediately
after the workflow run has finished. This adds a retry mechanism to wait
for the result-index to be available. It will retry at most 10 times
with a wait of 1 second between each retry.
2022-09-01 13:01:56 +02:00
Koen Vlaswinkel
ba8b32078d Simplify and clarify Webpack font config 2022-09-01 11:21:06 +02:00
Koen Vlaswinkel
fa4dd087e5 Remove Codicons references from webview 2022-09-01 09:39:19 +02:00
Dave Bartolomeo
ac74b967b3 Merge pull request #1490 from dbartol/dbartol/log-version/work 2022-08-31 21:01:36 -04:00
Dave Bartolomeo
c349c6a048 Fix race condition when generating evaluator log summaries
The original code that logged the human-readable log summary generated the log asynchronously, which was a reasonable choice. When I added support for viewing and scanning logs, I didn't notice that the summary was being generated asynchronously, and wrote my code assuming that the summary was already on disk when I opened it to find where each relation's log started. The effect was that, depending on timing, the evaluation sometimes failed with an error popup complaining about not being able to open the log summary file.

The fix is to _generate_ the log summary synchronously, but continue to _log_ it asynchronously.
2022-08-31 18:17:45 -04:00
Dave Bartolomeo
234b05994c Guard --sourcemap option based on CLI version 2022-08-31 18:08:21 -04:00
Koen Vlaswinkel
af8f0231c0 Merge pull request #1485 from github/koesie10/add-github-download-button
Remove canary requirement for GitHub database download
2022-08-31 16:57:12 +02:00
Edoardo Pirovano
84bd029749 Restart CLI server too when restarting query server 2022-08-31 14:39:44 +01:00
shati-patel
7d2e4b6de4 Bump CLI version to 2.10.4 for integration tests 2022-08-31 13:52:40 +01:00
Koen Vlaswinkel
23a0e03cef Completely remove using credentials in non-canary mode
This does not remove the previously added mechanism of not requesting
credentials, but using them when they are available. I expect this to be
used in the future.
2022-08-31 14:22:17 +02:00
Koen Vlaswinkel
21c5ed01ad Fix typo in getOctokit JSDoc
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-31 11:48:27 +02:00
Koen Vlaswinkel
d2af550bcc Merge remote-tracking branch 'origin/main' into koesie10/bundle-codicons 2022-08-31 09:51:46 +02:00
Koen Vlaswinkel
cf36a52762 Merge pull request #1478 from github/koesie10/abstract-interface-manager
Add abstract interface manager
2022-08-31 09:48:05 +02:00
Koen Vlaswinkel
ac1a97efa0 Refactor databaseFetcher tests to not use proxyquire 2022-08-30 15:32:08 +02:00
Koen Vlaswinkel
8d5067f622 Update CHANGELOG 2022-08-30 15:09:16 +02:00
Koen Vlaswinkel
fe5f1c417d Remove authentication requirement for download GitHub databases
This makes authentication for download GitHub CodeQL databases optional.
If you are already authenticated, your token will be used. If you are
not authenticated, an anonymous request will be made.

If the canary flag is enabled, you will be prompted for credentials when
downloading a database and you are not yet logged in.
2022-08-30 15:05:15 +02:00
Koen Vlaswinkel
95438bb7e3 Remove canary requirement for GitHub database download 2022-08-30 14:33:48 +02:00
Koen Vlaswinkel
6d7d0ca41a Merge pull request #1477 from github/koesie10/unified-webpack-bundle
Unify the Webpack bundle
2022-08-30 11:29:45 +02:00
Koen Vlaswinkel
3749e17769 Bundle Codicons using Webpack
This will include the Codicons inside the webview bundle, reducing the
number of files that need to be loaded and the resource roots that need
to be included.
2022-08-29 14:31:29 +02:00
Koen Vlaswinkel
ee49fb5070 Merge branch 'koesie10/unified-webpack-bundle' into koesie10/abstract-interface-manager 2022-08-29 14:12:20 +02:00
Koen Vlaswinkel
de6c523bad Merge remote-tracking branch 'origin/main' into koesie10/unified-webpack-bundle 2022-08-29 13:57:23 +02:00
Koen Vlaswinkel
6612c279ae Merge pull request #1479 from github/koesie10/improve-controller-repo-prompt
Improve prompot for controller repo
2022-08-29 09:53:26 +02:00
Koen Vlaswinkel
2dfa0e8b52 Simplify interface manager and types 2022-08-29 09:51:49 +02:00
Koen Vlaswinkel
0197306713 Remove unnecessary top-level package-lock.json 2022-08-29 09:47:24 +02:00
Dave Bartolomeo
269165eaa3 Merge pull request #1476 from github/version/bump-to-v1.6.12
Bump version to v1.6.12
2022-08-26 10:38:05 -04:00
Koen Vlaswinkel
14c736d72e Improve prompot for controller repo
This will improve the prompt for the controller repo by making clear
that the GitHub Actions workflow will be run in the specified repo.
2022-08-26 13:58:11 +02:00
Koen Vlaswinkel
b8898b939c Add abstract interface manager
This will add a new abstract class that implements the creation of the
panel and webview to reduce duplication across the different interface
managers.
2022-08-26 12:34:28 +02:00
Koen Vlaswinkel
45da1e0f1f Unify the Webpack bundle
This will move all webviews into a single Webpack bundle. This will make
it easier to add new webviews since we don't need to add a new bundle,
but just need to add a new directory with an `index.tsx` file.

It also moves the CSS processing to Webpack so that we don't need to
specify the CSS files to use separately, but can simply do so in the
TypeScript files.
2022-08-26 11:15:24 +02:00
dbartol
88c990c6ae Bump version to v1.6.12 2022-08-25 20:46:21 +00:00
Dave Bartolomeo
ac7211c117 Merge pull request #1475 from dbartol/dbartol/extension-release/work
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
Prepare for release 1.6.11
2022-08-25 16:36:44 -04:00
Dave Bartolomeo
d1d13fbd2e Update changelog for release 2022-08-25 13:11:50 -04:00
Dave Bartolomeo
f99166d26c Update Node version to match vscode 2022-08-25 13:01:35 -04:00
dependabot[bot]
9cd6f9a768 Bump d3 and @types/d3 in /extensions/ql-vscode (#1461)
Bumps [d3](https://github.com/d3/d3) and [@types/d3](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/d3). These dependencies needed to be updated together.

Updates `d3` from 6.7.0 to 7.6.1
- [Release notes](https://github.com/d3/d3/releases)
- [Changelog](https://github.com/d3/d3/blob/main/CHANGES.md)
- [Commits](https://github.com/d3/d3/compare/v6.7.0...v7.6.1)

Updates `@types/d3` from 6.7.5 to 7.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/d3)

---
updated-dependencies:
- dependency-name: d3
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/d3"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-24 08:13:04 -07:00
Koen Vlaswinkel
4dd16f4611 Merge pull request #1472 from github/koesie10/fix-data-not-loaded-in-mrva-results
Fix data not being loaded in MRVA results panel
2022-08-24 15:14:26 +02:00
Koen Vlaswinkel
2113d08545 Fix data not being loaded in MRVA results panel
When the MRVA results panel is closed (so the panel gets disposed) and
opened again, it would not load the MRVA data (such as whether a query
has already been downloaded). This fixes it by also resetting the
internal state of whether the panel is loaded when the panel is
disposed.
2022-08-24 13:20:01 +02:00
Koen Vlaswinkel
5b5ef26864 Merge pull request #1471 from github/revert-1466-koesie10/add-github-download-button
Revert "Remove canary requirement for GitHub database download"
2022-08-24 12:12:44 +02:00
Koen Vlaswinkel
c5a6e64df8 Revert "Remove canary requirement for GitHub database download" 2022-08-24 11:51:44 +02:00
Charis Kyriakou
178d626062 Upgrade webview-ui-toolkit version with long link fix (#1469) 2022-08-24 10:17:41 +01:00
Dave Bartolomeo
d1d48b3506 Merge pull request #1468 from dbartol/dbartol/pmod-highlight/work
Add `implements` and `signature` to syntax highlighting
2022-08-23 17:02:03 -04:00
Dave Bartolomeo
9180d1d9fc Fix comment 2022-08-23 16:42:57 -04:00
Dave Bartolomeo
674c5ecbff Add implements and signature to syntax highlighting 2022-08-23 14:56:47 -04:00
Koen Vlaswinkel
951d0b1004 Merge remote-tracking branch 'origin/main' into koesie10/deprecate-lgtm-download 2022-08-23 16:27:23 +02:00
Koen Vlaswinkel
edcac6925c Merge pull request #1466 from github/koesie10/add-github-download-button
Remove canary requirement for GitHub database download
2022-08-23 16:24:12 +02:00
Koen Vlaswinkel
2989e4cfb9 Update CHANGELOG 2022-08-23 16:08:23 +02:00
Koen Vlaswinkel
8f869813a9 Deprecate download from LGTM by hiding it behind canary flag 2022-08-23 16:05:11 +02:00
Koen Vlaswinkel
c10500c5ea Update CHANGELOG 2022-08-23 14:58:36 +02:00
Koen Vlaswinkel
0832850009 Remove canary requirement for GitHub database download 2022-08-23 14:45:48 +02:00
Alexander Eyers-Taylor
b352830674 Improve startup time (#1465)
* ArchiveFileSystem: Only parse zips once

* CLIServer: Only get version once
2022-08-23 11:05:10 +01:00
Andrew Eisenberg
e913165249 Merge pull request #1463 from github/aeisenberg/bump-timeout 2022-08-17 17:21:31 -07:00
Andrew Eisenberg
ef94bb3d38 Bump telemetry test timeout
This test is failing occasionally on our CI system. Let's see if this
change prevents the failures.
2022-08-17 15:47:30 -07:00
Shati Patel
4d6076c4ea Escape HTML characters when rendering MRVA results as markdown (#1462) 2022-08-17 10:52:36 +01:00
Dave Bartolomeo
43650fde00 Merge pull request #1454 from github/dbartol/join-order
Report suspicious join orders
2022-08-15 14:13:35 -04:00
Angela P Wen
f2c72a67f6 Bump CLI version to 2.10.3 for integration tests (#1460) 2022-08-15 16:41:26 +00:00
Dave Bartolomeo
2b1f3227ce Fix computation of result sizes in IN_LAYER events 2022-08-12 17:00:26 -04:00
Dave Bartolomeo
841f1d3310 Replace console logging to route through problem reporter 2022-08-12 16:43:21 -04:00
Dave Bartolomeo
99756ae63b Fix PR feedback 2022-08-12 16:25:52 -04:00
Dave Bartolomeo
9a2bea39e6 Better handling of missing log data 2022-08-12 16:14:24 -04:00
Dave Bartolomeo
1aab49c719 Specify return type 2022-08-12 16:01:58 -04:00
Dave Bartolomeo
cf925c256f Update extensions/ql-vscode/src/log-insights/log-scanner-service.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-12 15:50:28 -04:00
Dave Bartolomeo
8383a76e43 Merge branch 'dbartol/join-order' of https://github.com/github/vscode-codeql into dbartol/join-order 2022-08-12 15:41:52 -04:00
Dave Bartolomeo
c6d792f41e Fix PR feedback
Better handling of malformed RA
2022-08-12 15:39:32 -04:00
Dave Bartolomeo
277192e7d3 Update extensions/ql-vscode/src/log-insights/join-order.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-12 14:59:20 -04:00
Dave Bartolomeo
85988ecf34 Update extensions/ql-vscode/src/log-insights/join-order.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-12 14:50:10 -04:00
Dave Bartolomeo
49d12674b7 Cache regexprs 2022-08-12 14:47:50 -04:00
Dave Bartolomeo
beeb19dc05 Fix typo 2022-08-12 12:58:46 -04:00
Dave Bartolomeo
de88d27057 Update extensions/ql-vscode/src/log-insights/join-order.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-12 12:49:29 -04:00
Dave Bartolomeo
eb2d00e999 Update extensions/ql-vscode/src/log-insights/join-order.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-08-12 12:48:28 -04:00
Dave Bartolomeo
d58fb54928 Better formatting of metrics 2022-08-11 13:51:11 -04:00
Dave Bartolomeo
fdc209ca08 Test for log scanning 2022-08-10 18:07:59 -04:00
Dave Bartolomeo
28092f2b86 Move more of log scanning into pure code 2022-08-10 17:33:55 -04:00
Dave Bartolomeo
8970ad78ae Remove code added via bad merge 2022-08-10 13:51:08 -04:00
Dave Bartolomeo
e7a0c58940 Fix CodeQL alert 2022-08-10 13:18:00 -04:00
Dave Bartolomeo
02270aaeee Fix lint 2022-08-10 13:13:59 -04:00
Dave Bartolomeo
51fb03b4b1 Fix tests to match code changes 2022-08-10 13:11:34 -04:00
Dave Bartolomeo
838a2b71ac Scan logs on change in current query 2022-08-09 18:02:27 -04:00
Charis Kyriakou
f01c421d42 Merge pull request #1458 from github/version/bump-to-v1.6.11
Bump version to v1.6.11
2022-08-09 16:59:14 +01:00
charisk
561bc6f53c Bump version to v1.6.11 2022-08-09 15:21:26 +00:00
Charis Kyriakou
24b421e82d v1.6.10 (#1456)
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
2022-08-09 16:17:57 +01:00
Dave Bartolomeo
3c57597a19 Share code for splitting records from pseudo-JSONL 2022-08-05 17:36:45 -04:00
Dave Bartolomeo
e8d5029912 Merge remote-tracking branch 'origin/main' into dbartol/join-order-temp 2022-08-05 17:34:52 -04:00
Dave Bartolomeo
cb514f5c78 Pre-cleanup to avoid merge conflicts 2022-08-05 14:59:40 -04:00
Dave Bartolomeo
57bb8cee41 Update regexes to match new summary text 2022-08-04 16:17:27 -04:00
Dave Bartolomeo
1219ef4a8c Remove unnecessary command 2022-08-04 16:17:09 -04:00
Dave Bartolomeo
677a0f7940 Fix lint 2022-08-04 14:42:47 -04:00
Charis Kyriakou
b8cca29eb3 Ensure query history state is persisted after new query is added (#1451) 2022-08-04 15:06:47 +01:00
Shati Patel
4cbf104bdf (Minor) Remove outdated comment (#1453) 2022-08-04 13:24:48 +00:00
Angela P Wen
26ccde9e7d Bump CLI version to 2.10.2 for integration tests 2022-08-03 10:30:55 +01:00
Angela P Wen
beb5b78b89 Add 50ms wait for flaky telemetry popup test (#1449) 2022-08-02 08:24:07 -07:00
Dave Bartolomeo
c3a21b93c0 Merge pull request #1430 from github/dbartol/goto-ql
Initial implementation of sourcemap-based jump-to-QL command
2022-08-01 13:52:06 -04:00
Dave Bartolomeo
6b9f73e156 Add comment to test data file 2022-08-01 13:19:15 -04:00
Dave Bartolomeo
6409e09063 Code cleanup 2022-08-01 12:28:35 -04:00
Dave Bartolomeo
8f5611b074 Move sourcemap tests to cli-integration 2022-08-01 12:14:00 -04:00
Dave Bartolomeo
7f3fcce1ac Temporarily increase delay for extension activation in test 2022-07-29 13:11:46 -04:00
Dave Bartolomeo
4bc1d1ed8a Force activation of extension 2022-07-29 12:44:06 -04:00
Dave Bartolomeo
02e5b4e830 Fix installation of dependent extensions 2022-07-29 12:03:43 -04:00
Dave Bartolomeo
538792e8bb Try installing extension dependencies for minimal-workspace tests 2022-07-29 11:35:52 -04:00
Dave Bartolomeo
56ec970121 Merge branch 'dbartol/goto-ql' of https://github.com/github/vscode-codeql into dbartol/goto-ql 2022-07-29 11:01:07 -04:00
Dave Bartolomeo
57a04297bd Only disable specific extensions for minimal-workspace tests 2022-07-29 11:01:02 -04:00
Dave Bartolomeo
59f1e4e90a Update extensions/ql-vscode/src/pure/log-summary-parser.ts 2022-07-28 22:31:18 -04:00
Dave Bartolomeo
7c1fce3319 Merge remote-tracking branch 'origin/main' into dbartol/goto-ql 2022-07-28 22:29:45 -04:00
Dave Bartolomeo
476ea7aef0 Integration test 2022-07-28 22:20:22 -04:00
Elena Tanasoiu
0c654c4320 Merge pull request #1444 from github/elenatanasoiu/fix-bugs
Don't show parentheses when results are not yet fetched in Query History
2022-07-26 10:33:32 +01:00
Elena Tanasoiu
895ac6ae26 Squash extra whitespace for Query History labels
We'd like to remove duplicate whitespace in these labels in order
to make it less likely that we introduce extra space.

We initially also tried trimming whitespaces at the start and end
of these labels but that had no effect.
2022-07-26 09:49:27 +01:00
Elena Tanasoiu
52484f1211 Don't show parentheses when results are not yet fetched
We missed a place where we needed to check if results are present
before attempting to show them.

Let's also add tests for this.
2022-07-26 09:47:55 +01:00
Elena Tanasoiu
cba188b4db Use named arguments for mock function
We'd like to be able to add tests for when the result count exists and
when it's missing.

Let's change the createMockRemoteQueryInfo method so that we can pass
in parameters by name, e.g.

```
createMockRemoteQueryInfo(undefined, 2)
```

becomes

```
createMockRemoteQueryInfo({ repositoryCount: 2 }
```
2022-07-26 09:46:10 +01:00
Elena Tanasoiu
123b1fc085 Clarify title description
To make it clear it's referring to a `user-specified` label, not that the user is not specified.
2022-07-25 17:16:36 +01:00
Angela P Wen
833f8e06ca Add a tree viewer UI for the evaluator logs (#1433)
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2022-07-22 12:01:39 +02:00
Andrew Eisenberg
747049ed1b Merge pull request #1435 from github/dependabot/npm_and_yarn/extensions/ql-vscode/yargs-parser-20.2.4
Bump yargs-parser from 5.0.0-security.0 to 20.2.4 in /extensions/ql-vscode
2022-07-20 08:47:15 -07:00
Andrew Eisenberg
d62e9181f2 Merge pull request #1436 from github/dependabot/npm_and_yarn/extensions/ql-vscode/semver-regex-and-husky-3.1.4
Bump semver-regex and husky in /extensions/ql-vscode
2022-07-20 08:45:04 -07:00
Dave Bartolomeo
e4d1f4e73e Fix newline handling for cross-platform logs
We were splitting JSONL records based on the current OS newline sequence. In order to handle reading of logs from the opposite OS, I've switched our split to handle both flavors of line ending. This originally showed up as log parser unit tests failing on Windows (the checked-in log used Unix line endings), but could affect real world usage as well.
2022-07-20 11:21:53 -04:00
dependabot[bot]
c1922126d3 Bump terser from 5.14.1 to 5.14.2 in /extensions/ql-vscode
Bumps [terser](https://github.com/terser/terser) from 5.14.1 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-20 12:03:15 +01:00
elenatanasoiu
d2ebb3d20a Bump version to v1.6.10 2022-07-20 11:57:41 +01:00
Angela P Wen
72858e341a Bump CLI version to 2.10.1 for integration tests (#1442) 2022-07-20 11:55:43 +02:00
Elena Tanasoiu
4499773f6f Merge pull request #1440 from github/v1.6.9
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.6.9
2022-07-20 10:16:21 +01:00
Elena Tanasoiu
1d3b0e0ca9 v1.6.9 2022-07-20 10:01:12 +01:00
Elena Tanasoiu
98e503c768 Merge pull request #1438 from github/shati-patel/gist-description
MRVA: Fix Gist description when repository count is undefined
2022-07-20 09:46:22 +01:00
Elena Tanasoiu
62c3974d35 Check for undefined, null or zero repositories
`undefined`, `null` and 0 will evaluate to `false` so if we only want to
display the repository count when these values are not present we can
check for a truthy value:

```
query.repositoryCount ? `(${pluralize(...)})` : '';
```

instead of checking explicitly:

```
query.repositoryCount !== undefined && query.repositoryCount !== null && query.repositoryCount != 0 ? `(${pluralize(...)})` : '';
```
2022-07-20 09:30:54 +01:00
Dave Bartolomeo
40e0027074 Fix newline handling for cross-platform logs
We were splitting JSONL records based on the current OS newline sequence. In order to handle reading of logs from the opposite OS, I've switched our split to handle both flavors of line ending. This originally showed up as log parser unit tests failing on Windows (the checked-in log used Unix line endings), but could affect real world usage as well.
2022-07-19 17:29:33 -04:00
shati-patel
ab1c2e0a0d Explicitly check for undefined 2022-07-19 20:00:10 +01:00
shati-patel
d918c41197 Fix Gist description when repository count is undefined 2022-07-19 18:25:25 +01:00
Dave Bartolomeo
84048ccac1 Merge remote-tracking branch 'origin/main' into dbartol/goto-ql 2022-07-19 09:39:51 -04:00
dependabot[bot]
cbb09da0d0 Bump semver-regex and husky in /extensions/ql-vscode
Bumps [semver-regex](https://github.com/sindresorhus/semver-regex) and [husky](https://github.com/typicode/husky). These dependencies needed to be updated together.

Updates `semver-regex` from 2.0.0 to 3.1.4
- [Release notes](https://github.com/sindresorhus/semver-regex/releases)
- [Commits](https://github.com/sindresorhus/semver-regex/compare/v2.0.0...v3.1.4)

Updates `husky` from 4.2.5 to 4.3.8
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v4.2.5...v4.3.8)

---
updated-dependencies:
- dependency-name: semver-regex
  dependency-type: indirect
- dependency-name: husky
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-19 12:51:24 +00:00
dependabot[bot]
c8d3428f21 Bump yargs-parser in /extensions/ql-vscode
Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 5.0.0-security.0 to 20.2.4.
- [Release notes](https://github.com/yargs/yargs-parser/releases)
- [Changelog](https://github.com/yargs/yargs-parser/blob/main/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs-parser/commits/v20.2.4)

---
updated-dependencies:
- dependency-name: yargs-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-19 12:51:22 +00:00
Elena Tanasoiu
2cf5b39cfe Merge pull request #1432 from github/charisk-elena/result-count-on-history-labels
Add result count to remote queries in Query History
2022-07-19 13:50:22 +01:00
Elena Tanasoiu
13921bf8a2 Extract sum method for adding up repo results
When a queryResult is created, it comes with an array for AnalysisSummaries.
There is one summary per repository.

We've had to calculate the total number of results for all summaries in multiple
places, so let's extract a method for this as well.
2022-07-19 13:26:56 +01:00
Elena Tanasoiu
12a97ecba2 Shorten param forwarding for repositoryCount 2022-07-19 13:26:54 +01:00
Elena Tanasoiu
26529232f4 Rename numRepositoriesQueries to repositoryCount
To make it consistent with `resultCount`.
2022-07-19 13:25:48 +01:00
Elena Tanasoiu
1b425fc261 DRY up labels using the new pluralize method 2022-07-19 13:25:40 +01:00
Elena Tanasoiu
9c598c2f06 Extract pluralize method
There are at least 4 different files where this method could DRY things up,
so let's extract it.

I've chosen to move it to src/helpers.ts but happy to be told there's a better
place for shared utility methods like this one.
2022-07-19 12:32:24 +01:00
Elena Tanasoiu
99a784f072 Be able to sort remote queries by number of results
Previously we would set all remote query results to -1 when someone
attempted to sort queries.

We would then only sort local queries as those had access to the number
of results.

Let's include number of results for remote queries in the sorting.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-19 12:32:24 +01:00
Elena Tanasoiu
030488a459 Make local and remote query results match
In the previous commit we're now displaying number of results for remote
queries.

Previously we could only do this for local queries.

Let's make the format match for both types of queries by displaying
number of results in parentheses: `(x results)`.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-19 12:32:24 +01:00
Elena Tanasoiu
377f7965b1 Add result count to remote queries in Query History
When you run a remote query, we'd like to display more information about
it in the Query History panel.

At the moment we've improved this [1] by adding the language and number of repositories.

In this commit we're also adding the number of results for a remote query.

So the final format of the query history item will change from:

`<query_name> - <query_status>`

to

`<query_name> (<language>) on x repositories (y results) - <query_status>`

[1]: https://github.com/github/vscode-codeql/pull/1427

Co-authored-by: Charis Kyriakou <charisk@github.com>
Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-19 12:32:22 +01:00
Charis Kyriakou
651a6fbda8 Ensure completed flag is set on remote query history items (#1434) 2022-07-19 10:40:02 +01:00
Elena Tanasoiu
55ffdf7963 Merge pull request #1431 from github/shati-elena/rename-gist
Add useful information to MRVA gist titles
2022-07-19 09:11:47 +01:00
Elena Tanasoiu
cc907d2f31 Add test for exportResultsToGist method
While we're here we're also adding a test for the `exportResultsToGist`
method, as there were no tests for the `export-results.ts` file.

We initially attempted to add the test to the pure-tests folder, but the
`export-results.ts` file imports some components from `vscode`, which
meant we needed to set up the test in an environment where VSCode
dependencies are available.

We chose to add the test to `vscode-tests/no-workspace` for convenience,
as there are already other unit tests there.

We've also had to import our own query and analysis result to be able
to work with data closer to reality for exported results.

Since we've introduced functionality to build a gist title, let's check
that the `exportResultsToGist` method will forward the correct title to
the GitHub Actions API.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-18 19:52:51 +01:00
Dave Bartolomeo
49a1576d14 Merge branch 'dbartol/goto-ql' of https://github.com/github/vscode-codeql into dbartol/goto-ql 2022-07-18 14:33:37 -04:00
Dave Bartolomeo
0cc4561ee9 Discard cached sourcemap when summary document is closed
Also some minor lint feedback
2022-07-18 14:33:33 -04:00
Elena Tanasoiu
c4df9dbec8 Extract method for creating Extension context
We'd like to re-use this to test the `exportResultsToGist` method in
`export-results.ts`.

So let's move it to a shared folder in the `vscode-tests/no-workspace` folder.

Since there's no `helper.ts` file in this folder and to avoid any confusion with
the `helpers.test.ts` file, I've opted to put this shared method into `index.ts`.

Happy to be told there's a better pattern for this as it doesn't feel very nice!
2022-07-18 19:22:44 +01:00
Elena Tanasoiu
c384a631dc Handle missing repo count gracefully
Let's handle this case gracefully and skip displaying the number of repositories
when they're not available.

Similarly let's add a check to see if we should pluralize the `repository` noun
or not.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-18 19:22:44 +01:00
Elena Tanasoiu
b079690f0e Add useful information to MRVA gist titles
All exported MRVA gists are given the name `CodeQL variant analysis
results', which makes it hard to work out what it contains at a glance.

We're adding more information in the gist title to make it more useful.

Example of new title:

`Empty Block (Go) x results (y repositories)`

This translates to:

`<query name> (<query language>) <number of results> results (<number of repositories> repositories)`

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-18 19:22:41 +01:00
Elena Tanasoiu
4e863e995b Introduce method to add analysis results
We'd like to improve MRVA query gists by giving them more descriptive
titles that contain useful information about the query.

Let's add the number of query results to the title of the gist.

To do this we'll first need to count all the results provided to us in
the `analysisResults` array. There is an item in this array for each of
the repositories we've queried, so we're introducing a method to sum up
results for all the items in the array.

Co-authored-by: Shati Patel <shati-patel@github.com>
2022-07-18 19:20:58 +01:00
Dave Bartolomeo
576737cac8 Update extensions/ql-vscode/src/log-insights/summary-language-support.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-07-15 14:50:48 -04:00
Dave Bartolomeo
742aa4ca19 Use error message helper 2022-07-15 11:43:05 -04:00
Shati Patel
f992679e94 MRVA: Include more info in query history label (#1427)
Co-authored-by: Elena Tanasoiu <elenatanasoiu@github.com>
2022-07-15 13:58:45 +01:00
Shati Patel
ffe1704ac0 Replace code paths dropdown with VS Code UI Toolkit (#1429) 2022-07-15 13:04:36 +01:00
Dave Bartolomeo
b5e6700cba Log message on failure to open sourcemap 2022-07-14 18:10:58 -04:00
Dave Bartolomeo
7f5302dc37 fs-extra 2022-07-14 17:39:16 -04:00
Dave Bartolomeo
3ea5524048 Hide "Go to QL Code" behind canary flag 2022-07-14 17:21:52 -04:00
Dave Bartolomeo
1823ae8397 Fix test expectation 2022-07-14 17:03:39 -04:00
Dave Bartolomeo
6dca9ccbeb Fix linter issues 2022-07-14 14:12:10 -04:00
Dave Bartolomeo
f3c2862937 Fix lint error 2022-07-14 14:06:19 -04:00
Dave Bartolomeo
855cb485d5 Initial implementation of sourcemap-based jump-to-QL command 2022-07-14 13:55:46 -04:00
Edoardo Pirovano
bd2dd04ac6 Regularly scrub query history view 2022-07-14 16:59:08 +01:00
Edoardo Pirovano
bbf4a03b03 Fix typo in config parameter name 2022-07-13 16:34:18 +01:00
Shati Patel
f38eb4895d Replace "repository search" filter box with VS Code UI Toolkit (#1424) 2022-07-13 15:13:31 +01:00
Andrew Eisenberg
f559b59ee5 Merge pull request #1420 from github/robertbrignull/api-retry
Add API retries for octokit requests
2022-07-12 08:12:21 -07:00
Angela P Wen
c9d895ea42 Parse summary of evaluator logs into data model (#1405)
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-07-12 14:04:55 +02:00
Shati Patel
e57bbcb711 Use VSCodeTags instead of Primer Labels in webview (#1421) 2022-07-01 16:21:44 +01:00
Shati Patel
b311991644 MRVA: Fix grammar in pop-up message (#1416) 2022-07-01 12:43:46 +01:00
Robert
825054a271 Use octokit retry module 2022-07-01 11:19:49 +00:00
Robert
f7aa0a5ae5 Install @octokot/plugin-retry 2022-07-01 11:06:22 +00:00
Andrew Eisenberg
f486ccfac6 Merge pull request #1418 from github/aeisenberg/resolve-ml-libs
Resolve ml-queries from directory
2022-06-30 08:56:15 -07:00
Andrew Eisenberg
70f74d3baf Resolve ml-queries from directory
Previously, there was a bug where quick eval queries would crash when
the eval snippet is in a library file.

The problem was that the `codeql resolve queries` command fails when
passed a library file. The fix is to avoid passing the library file at
all. Instead, pass the directory. This is safe because the resolve
queries command only needs to know which query pack the file is
contained in. Passing in the parent directory is the same as passing in
a file in this particular case.
2022-06-30 08:36:55 -07:00
Charis Kyriakou
ebad1844df MRVA: Don't show notification if user aborts firing off a query (#1417) 2022-06-30 14:35:33 +01:00
Charis Kyriakou
a40a2edaf2 Merge pull request #1414 from github/version/bump-to-v1.6.9
Bump version to v1.6.9
2022-06-29 13:17:30 +01:00
charisk
5f3d525ff8 Bump version to v1.6.9 2022-06-29 11:56:36 +00:00
Charis Kyriakou
8f5d88156f Merge pull request #1413 from github/v1.6.8
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.6.8
2022-06-29 12:53:51 +01:00
Charis Kyriakou
7c941fe8a8 v1.6.8 2022-06-29 12:42:18 +01:00
Henry Mercer
e9835cb376 Improve changelog note
Co-authored-by: Edoardo Pirovano <6748066+edoardopirovano@users.noreply.github.com>
2022-06-29 12:01:10 +01:00
Henry Mercer
7651a960b1 Add changelog note 2022-06-29 12:01:10 +01:00
Henry Mercer
5b17a84733 Avoid resolve ml-models errors being logged during quick eval
Currently `resolve ml-models` only supports queryspecs, i.e. .ql, .qls,
directory, and query pack specifications. Therefore quick evaluation within
a library isn't
supported.
2022-06-29 12:01:10 +01:00
Andrew Eisenberg
22873a2f3c Invoke codeql pack install after adding a quick query
This ensures the pack lock file is in place after the quick query is
generated.
2022-06-29 10:25:00 +01:00
Andrew Eisenberg
2debadd3bf Update changelog 2022-06-29 10:25:00 +01:00
Charis Kyriakou
6808d7dcaf MRVA: Display alert text even if location is undefined (#1407) 2022-06-29 08:35:56 +01:00
Shati Patel
3480aa5495 Remove older CLI versions from testing matrix (#1410) 2022-06-28 14:07:11 +00:00
Shati Patel
a4d1ad57c7 Bump CLI version for integration tests (#1409) 2022-06-28 13:49:37 +00:00
Robert
628e0e924d Merge pull request #1408 from github/robertbrignull/cutoff_repos
Add cutoff repos and counts to error message
2022-06-28 06:16:18 -07:00
Robert
16077f4124 Add cutoff repos to error message 2022-06-28 12:21:11 +01:00
Charis Kyriakou
e6a68b3223 Add ability to define repo lists in a file outside of settings (#1402) 2022-06-24 16:48:10 +01:00
Charis Kyriakou
539a494914 Only copy repos that have results when copying repo list (#1406) 2022-06-24 14:13:33 +01:00
Charis Kyriakou
9c29c5c9c6 Add ability to create repo list from MRVA results (#1403) 2022-06-24 09:26:12 +01:00
Charis Kyriakou
fd4b6022a9 Refactor: Invert dependency between query history and remote quries managers (#1396) 2022-06-23 13:28:57 +01:00
dependabot[bot]
58bbb59e39 Bump shell-quote from 1.7.2 to 1.7.3 in /extensions/ql-vscode
Bumps [shell-quote](https://github.com/substack/node-shell-quote) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/substack/node-shell-quote/releases)
- [Changelog](https://github.com/substack/node-shell-quote/blob/master/CHANGELOG.md)
- [Commits](https://github.com/substack/node-shell-quote/compare/v1.7.2...1.7.3)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-23 10:58:47 +01:00
Robert
5cc55530e1 Merge pull request #1399 from github/robertbrignull/skipped_private_repos
Show in log message when repos are filtered out for being private
2022-06-23 02:28:54 -07:00
Robert
3d74dbf48a Update extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/run-remote-query.test.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-06-23 10:16:37 +01:00
Elena Tanasoiu
b7489d8f66 Merge pull request #1400 from github/elenatanasoiu/check-for-codeql-folder-in-workspace
Fail fast if codeql repo is missing from the workspace
2022-06-23 09:26:40 +01:00
Elena Tanasoiu
e0b2aa9b45 Update docs for running cli-integration tests 2022-06-23 09:13:20 +01:00
Elena Tanasoiu
10b4c15053 Fail fast if codeql CLI is missing from the workspace
In order to run our cli-integration tests, we're required to have a
local copy of the codeql CLI repo. We can then run the tests by running
the `Launch Integration Tests - With CLI` task from inside VS Code.

(See CONTRIBUTING.md for details.)

If we don't have the CLI repo cloned locally or we're not pointing to it
in `launch.json`, we don't get a clear indication of what the problem is.

The tests will still attempt to run.

Let's fail fast instead and add an actionable error message to the output.
2022-06-23 09:13:20 +01:00
Robert
8bc83a336a Show skipped private repos in log message 2022-06-22 17:18:29 +01:00
Elena Tanasoiu
c84b858205 Merge pull request #1397 from github/elenatanasoiu/improve-mrva-controller-feedback
MRVA: Improve experience when controller repo does not exist
2022-06-22 17:03:26 +01:00
Elena Tanasoiu
e5f3a973a0 Improve experience when controller repo does not exist
The controller repo is set via the `codeQL.variantAnalysis.controllerRepo`
setting in VSCode.

While we have validation to check that the repo is not null and the
format of the controller repo is correct: `<owner>/<repo>`, we still
allow you to provide a non-existent repo (e.g. a mispelled one).

When the MRVA request is sent over to the API, it will verify that the
repo exists and return a very generic "Not Found" response.

This will then be logged out in the "Output" tab for VSCode.

We'd like to give users a better indication of what has gone wrong in
this case so we're making the error message more verbose.

Co-authored-by: Charis Kyriakou <charisk@github.com>
Co-authored-by: Shati Patel <shati-patel@github.com>
2022-06-22 16:42:51 +01:00
Andrew Eisenberg
3682f05a42 Merge pull request #1398 from github/aeisenberg/integration-tests-fix
Fix failing integration test
2022-06-21 17:33:54 -07:00
Andrew Eisenberg
eb5ce029ba Fix failing integration test
How did this ever work? It was using an old variant of the
qlpack name.

Also, this commit makes the unhandledRejection handler less
verbose. This gets hit when the tests end and there is a cancellation.
this is not an error.
2022-06-21 17:22:43 -07:00
Charis Kyriakou
0ebff2d6e6 Add ability of running MRVA against a whole org (#1372) 2022-06-21 09:19:07 +01:00
Andrew Eisenberg
d061634fe3 Merge pull request #1379 from github/aeisenberg/fix-bqrs-decode
Fix quoting of string columns in csv
2022-06-20 08:38:36 -07:00
Andrew Eisenberg
6b9410c67e Merge pull request #1388 from github/aeisenberg/fix-flaky-test
Arcane workaround to fix a flaky test
2022-06-20 08:13:22 -07:00
Andrew Eisenberg
8245e54e9c Update extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts 2022-06-20 08:00:31 -07:00
Shati Patel
8ee744ef0c Bump CLI version used in integration tests (#1394) 2022-06-20 12:02:53 +00:00
Charis Kyriakou
da179b2580 Use VSCodeProgressRing instead of Primer's Spinner (#1392) 2022-06-20 08:39:28 +01:00
Shati Patel
0714f06adc MRVA: Include number of repositories queried in confirmation message (#1393) 2022-06-17 16:15:13 +01:00
Charis Kyriakou
b2906257a1 Remove use of Primer's box component (#1389) 2022-06-17 08:16:51 +01:00
Shati Patel
18097e4676 Allow repo names with periods (#1391) 2022-06-16 17:43:31 +01:00
Charis Kyriakou
efcade84c6 First pass at using VS Code UI toolkit (#1382) 2022-06-16 08:24:42 +00:00
Andrew Eisenberg
7f27375d17 Arcane workaround to fix a flaky test
For an inexplicable reason, the first time the selection
occurs, the value is incorrect. We often miss this error
in our tests if the expectation is reached before the
selection changed event fires.

It seems that the _second_ time the selection changed
event fires, the value is correct.

This change ensures we wait for the second selection change.
And we avoid running expectations until then.e
2022-06-15 15:16:51 -07:00
Andrew Eisenberg
01e1f134be Merge pull request #1361 from github/dependabot/npm_and_yarn/extensions/ql-vscode/glob-promise-4.2.2
Bump glob-promise from 3.4.0 to 4.2.2 in /extensions/ql-vscode
2022-06-15 21:22:15 +02:00
dependabot[bot]
0695b0557f Bump glob-promise from 3.4.0 to 4.2.2 in /extensions/ql-vscode
Bumps [glob-promise](https://github.com/ahmadnassri/node-glob-promise) from 3.4.0 to 4.2.2.
- [Release notes](https://github.com/ahmadnassri/node-glob-promise/releases)
- [Commits](https://github.com/ahmadnassri/node-glob-promise/compare/v3.4.0...v4.2.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>
2022-06-15 18:24:24 +00:00
Elena Tanasoiu
c63f0c0833 Merge pull request #1387 from github/elenatanasoiu/node-18
Prepare for Node 18 upgrade
2022-06-15 19:23:01 +01:00
Elena Tanasoiu
3264ffaaa4 Upgrade webpack
We're upgrading the minimum version of webpack from 5.28.0 to 5.62.2
since this version doesn't rely on OpenSSL for its hashing algorithm so
it wouldn't need legacy OpenSSL support when we decide to upgrade to
Node 18.

This allows us to build our extension on Node 18:
https://github.com/github/vscode-codeql/runs/6904100934?check_suite_focus=true

Happily, this also works fine with our current version of Node (16.13.0).
2022-06-15 18:00:46 +01:00
Elena Tanasoiu
40959c8876 Use source-map 0.7.4
A new release of source-map was pushed 10 days ago:
https://github.com/mozilla/source-map/releases/tag/v0.7.4

It contains a fix for building on Node 18 (which was added in Oct
2020): https://github.com/mozilla/source-map/issues/423.

Let's make use of it!
2022-06-15 18:00:38 +01:00
Elena Tanasoiu
ecea7f4638 Merge pull request #1386 from github/elenatanasoiu/update-release-docs
Follow guidance for git tagging in contribution docs
2022-06-15 17:27:27 +01:00
Elena Tanasoiu
0b15a166fa Follow guidance for git tagging in contribution docs
Adding two things:
- A bit more detail on how to add a tag and how to delete a badly named one
- Switch to the official way of sharing tags according to the git docs[^1]

[^1]: https://git-scm.com/book/en/v2/Git-Basics-Tagging
2022-06-15 16:57:38 +01:00
elenatanasoiu
c368424a15 Bump version to v1.6.8 2022-06-15 15:42:36 +01:00
Elena Tanasoiu
5df1f80307 Merge pull request #1384 from github/v1.6.7
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.6.7
2022-06-15 15:03:00 +01:00
Elena Tanasoiu
4b59045149 v1.6.7 2022-06-15 13:41:11 +00:00
Andrew Eisenberg
a3a05131c7 Handle quote escaping in csv export 2022-06-13 11:17:37 -07:00
Elena Tanasoiu
a9922b86fe Merge pull request #1374 from github/elenatanasoiu/set-node-version
Use the same Node version as VSCode
2022-06-13 16:54:10 +02:00
Elena Tanasoiu
431350ac0e Merge pull request #1375 from github/elenatanasoiu/adjust-font-on-results
Use base styling for MRVA results font
2022-06-13 16:53:32 +02:00
Elena Tanasoiu
5f8802fe7f Use base styling for MRVA results font
It's been pointed out that MRVA results are hard to read as the font is
small and narrowly spaced. It also doesn't match the font type normally
used in source files.

We can instead switch to using the font-family used by VS Code itself and
increase the font size from `x-small` to `small` for code snippets.
2022-06-13 14:28:15 +01:00
Elena Tanasoiu
5f21594d23 Provide a maximum node version in package.json
As recommended here https://github.com/github/vscode-codeql/pull/1369#issuecomment-1142418037, since the current build for this extension does not work with Node v18 https://github.com/github/vscode-codeql/issues/1373, it would be good to set a maximum node version until this gets addressed.

So we're updating `engines` here to allow for a maximum version, which in this case is v17.0.0.
2022-06-13 11:21:29 +01:00
Elena Tanasoiu
8964ec1a4d Use the same Node version as VSCode
As recommended here https://github.com/github/vscode-codeql/pull/1369#issuecomment-1142418037, we want to stay in sync with the current node version shipped with
VSCode (v16.13.0):

32d40cf44e/remote/.yarnrc (L2)

For this we can add a `.nvmrc` file to alert nvm to switch to the preferred version automatically.

It will also help prevent builds from failing when setting up the project for the first time, as building the extension currently fails in Node v18: https://github.com/github/vscode-codeql/issues/1373

We're also updating the docs to mention using `nvm` to manage node versions and point to the right place to check for current supported versions.
2022-06-13 11:21:25 +01:00
Andrew Eisenberg
aa270e57ec Refactor exportCsvResults and create test
1. `exportCsvResults` now no longer requires an `onFinish` callback.
2. The test adds a generic framework for creating a mock cli server.
   This should be used in future tests.
2022-06-06 10:21:12 +02:00
Andrew Eisenberg
fe7eb07f39 Don't choose a non-existent result set for csv viewing
If the `#select` resultset doesn't exist, arbitrarily choose the first
result set when viewing csv results. This will almost certainly be the
correct result set.

In the future, we could offer a popup if there are multiple result sets
available, but let's wait on that until someone actually asks for it.
2022-06-06 09:23:40 +02:00
Andrew Eisenberg
c10da7f960 Update Changelog 2022-06-03 16:26:02 -07:00
Andrew Eisenberg
0c8390c094 Fix quoting of string columns in csv 2022-06-03 16:24:10 -07:00
shati-patel
d41c63bf7d Change parameter type + extract local variable 2022-06-01 21:19:35 +01:00
shati-patel
a3bbdafabb Add tests for 'tryGetRemoteLocation' 2022-06-01 21:19:35 +01:00
shati-patel
a78eef464b Handle undefined URLs 2022-06-01 21:19:35 +01:00
shati-patel
e8348ac12a Check format of URI 2022-06-01 21:19:35 +01:00
shati-patel
5efc3835db Add sourceLocationPrefix to one of the test files 2022-06-01 21:19:35 +01:00
shati-patel
c4ed6e88de Pass sourceLocationPrefix down through all the functions 2022-06-01 21:19:35 +01:00
shati-patel
51e6559145 Update tryGetRemoteLocation to use sourceLocationPrefix (if available) 2022-06-01 21:19:35 +01:00
Charis Kyriakou
db8b419885 Combine time constants (#1371) 2022-06-01 16:52:18 +01:00
Elena Tanasoiu
475d7cc535 Merge pull request #1369 from github/elenatanasoiu/fix-casing-query-history
Be consistent about casing in Query History menu
2022-06-01 12:24:29 +01:00
Elena Tanasoiu
1858de5ed0 Update Changelog 2022-06-01 12:07:13 +01:00
Angela P Wen
642f4788fb Update tests to CLI v2.9.3 (#1370) 2022-05-31 17:44:43 +00:00
Elena Tanasoiu
7e70f8b758 Be consistent about casing in Query History
Reported here: https://github.com/github/code-scanning/issues/6008

We originally started out by capitalizing each word [1], but made some
small changes [2] which resulted in our Query History options
being inconsistent.

Let's fix that.

[1]: a5da556496/extensions/ql-vscode/package.json
[1]: b470e41431
2022-05-31 18:34:37 +01:00
Charis Kyriakou
e417bea948 Move time constants to time module (#1368) 2022-05-31 13:21:45 +01:00
Andrew Eisenberg
6b4be93169 Merge pull request #1363 from github/aeisenberg/resolve-ml-model
Add new support for resolve ml-models
2022-05-30 18:46:53 -07:00
Andrew Eisenberg
061eaad743 Update extensions/ql-vscode/src/cli.ts
Change version where precise ml-model resolution was introduced.
2022-05-30 18:32:11 -07:00
Andrew Eisenberg
8ff21d6c89 Merge pull request #1365 from github/aeisenberg/time
Extract time functions
2022-05-30 07:54:18 -07:00
Andrew Eisenberg
0d9f4e8c0f Merge pull request #1366 from github/aeisenberg/handle-missing-nwo
Handle missing nwos returned from graphql query
2022-05-30 07:53:55 -07:00
Andrew Eisenberg
02288718dc Handle missing nwos returned from graphql query 2022-05-27 13:12:49 -07:00
Andrew Eisenberg
615cf86fc0 Refactor time functions
Rename, add comments, and extract some local variables.
2022-05-27 08:51:14 -07:00
Andrew Eisenberg
d63a209674 Make conditional statement more explicit 2022-05-27 07:08:58 -07:00
Andrew Eisenberg
9d26304f7a Extract time functions
Create the `time.ts` module as a place to put fime functions.
Move two time functions there and create tests for them.

The `humanizeUnit` function now uses ECMAscript apis. This ensures
that pluralization happens appropriately.

Also, fix a small bug in the results view to enure `repository`
is correctly pluralized.
2022-05-26 15:47:03 -07:00
Andrew Eisenberg
f73bda438a Merge pull request #1362 from github/aeisenberg/last-update-sort
Add sort MRVA results by last updated
2022-05-26 09:15:37 -07:00
Andrew Eisenberg
19b65a654e Fix method name 2022-05-26 08:44:04 -07:00
Andrew Eisenberg
770127e67a Use the repo push icon 2022-05-26 06:55:12 -07:00
Andrew Eisenberg
f373e6467a Store LastUpdated as a duration, not a timestamp
The `lastUpdated` value is now the duration between timestamp of the
last time the repo was updated and time the file was downloaded.
This fixes the duration and it won't change over time.
2022-05-25 20:30:28 -07:00
Andrew Eisenberg
e43b4e66a1 Add sort MRVA results by last updated
1. Refactor references of `Stargazers` to `RepositoryMetadata` since
   the query is now more generic.
2. Update the graphql query to request last updated as well as stars
3. Update web view to display last updated
4. Update sort mechanism for last updated

A few notes:

1. I used `Intl.RelativeTimeFormat` to humanize the times. It wasn't as
   simple as I had hoped since I need to also make a guess as to which
   unit to use.
2. The icon used by last updated is not quite what is in the wireframes.
   But, I wanted to stick with primer icons and I used the closest I can
   get.
3. The last updated time is retrieved when the query is first loaded
   into vscode and then never changes. However, this time is always
   compared with `Date.now()`. So, opening the query up a week from now,
   all of the last updated times would be one week older (even if the
   repository has been updated since then).

   I don't want to re-retrieve the last updated time each time we open
   the query, so this timestamp will get out of date eventually.

   Is this confusing as it is?
2022-05-24 19:57:40 -07:00
Andrew Eisenberg
90ec003386 Add new support for resolve ml-models
The new support will be available in the next
release of the CLI, most likely 2.9.3,

This change requires the query to be run to be
passed in to the call to resolve ml-models.
2022-05-24 17:24:46 -07:00
Angela P Wen
2f9aca785e Log most expensive predicates and timings to query log (#1349) 2022-05-20 13:21:33 -07:00
Andrew Eisenberg
405a6c9901 Merge pull request #1353 from github/aeisenberg/sort-remote-results
Add sorting to variant analysis results
2022-05-20 09:23:10 -07:00
Andrew Eisenberg
3611b1fe61 Add comments and simplify some JSX
Use `ActionMenu.Anchor` instead of `ActionMenu.Button`.

The theming styles are not correct. Will work on that next.
2022-05-20 08:01:54 -07:00
Andrew Eisenberg
7b33441519 Merge pull request #1360 from github/dependabot/npm_and_yarn/extensions/ql-vscode/mocha-10.0.0
Bump mocha from 9.1.3 to 10.0.0 in /extensions/ql-vscode
2022-05-19 08:29:52 -07:00
dependabot[bot]
2a8f61dfbe Bump mocha from 9.1.3 to 10.0.0 in /extensions/ql-vscode
Bumps [mocha](https://github.com/mochajs/mocha) from 9.1.3 to 10.0.0.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v9.1.3...v10.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-19 13:05:48 +00:00
Andrew Eisenberg
dcfd6d43c0 Merge pull request #1307 from github/dependabot/npm_and_yarn/extensions/ql-vscode/js-yaml-4.1.0
Bump js-yaml from 3.14.0 to 4.1.0 in /extensions/ql-vscode
2022-05-18 15:03:36 -07:00
Andrew Eisenberg
4e4d8b2f04 Fix js-yaml issues
With js-yaml 4.0, safeLoad is no longer available. Use load instead.
2022-05-18 14:45:28 -07:00
Andrew Eisenberg
50197ba7b7 Merge pull request #1308 from github/dependabot/npm_and_yarn/extensions/ql-vscode/style-loader-3.3.1
Bump style-loader from 0.23.1 to 3.3.1 in /extensions/ql-vscode
2022-05-18 14:40:50 -07:00
Andrew Eisenberg
6c376d8721 Add integration test for ensuring the graphql query succeeds 2022-05-18 14:20:24 -07:00
Andrew Eisenberg
82ada54103 Add sorting to variant analysis results
Sort by stars, number of results, and name.

This also includes a graphql query that retrieves all the stars
for relevant repositories.
2022-05-18 13:56:17 -07:00
shati-patel
0fdfeb3cd3 Bump version to v1.6.7 2022-05-17 16:37:42 +01:00
shati-patel
096d7719c6 v1.6.6
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
2022-05-17 16:07:06 +01:00
Angela P Wen
619c485224 Show query results before structured evaluator log summary completes (#1350) 2022-05-17 10:45:52 -04:00
Shati Patel
9367d5fb45 MRVA: Export results to local markdown files (#1344) 2022-05-17 10:03:23 +01:00
Shati Patel
50ec97ad91 Update CLI version used in tests 2022-05-16 17:43:06 +01:00
shati-patel
fa5fcde987 Rename button component 2022-05-16 16:45:07 +01:00
shati-patel
5b33333404 Fix padding to match design doc 2022-05-16 16:45:07 +01:00
shati-patel
cf50624e4e Tidy up 2022-05-16 16:45:07 +01:00
shati-patel
ccc9ed8b49 MRVA: Add webview button to export results 2022-05-16 16:45:07 +01:00
shati-patel
141f5381e7 MRVA: Export results from query history 2022-05-16 10:08:46 +01:00
shati-patel
be054ca4f8 Move "exporting results" to a separate file 2022-05-12 13:26:16 +01:00
Shati Patel
0a06452450 Update extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts
Co-authored-by: Charis Kyriakou <charisk@users.noreply.github.com>
2022-05-11 17:27:45 +01:00
shati-patel
b840d3f9bf Tidy up Gist creation 2022-05-11 17:27:45 +01:00
shati-patel
c829c30688 MRVA: Add command to export markdown results to gist 2022-05-11 17:27:45 +01:00
Shati Patel
7947afb1b4 Remove unnecessary commas from alert shortDescription 2022-05-10 11:39:41 +01:00
dependabot[bot]
c32b53613d Bump js-yaml from 3.14.0 to 4.1.0 in /extensions/ql-vscode
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.14.0 to 4.1.0.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.14.0...4.1.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 21:51:58 +00:00
Andrew Eisenberg
c058e7a128 Merge pull request #1340 from github/aeisenberg/fix-integration-tests
Fix cli-integration tests
2022-05-09 14:48:17 -07:00
Andrew Eisenberg
1dc663339d Revert back to vscode-test 2022-05-09 14:02:22 -07:00
Andrew Eisenberg
351db4efc8 Fix cli-integration tests
The main problem this commit fixes is with vscode 1.67.0, an error is
thrown when inside of integration tests and a dialog box is opened. We
were opening the telemetry dialog box. Now, an env variable is set
during cli-integration tests that prevents the dialog from being
opened.

There are also other cleanups and improvements with cli-integration
tests that assist with running locally:

- `vscode-test` dependency has been renamed to `@vscode/test-electron`,
  so use that instead and make the small API changes to support it.
- Commit the codeql-pack.lock.yml file so it isn't recreated on each
  test run.
- Ensure all databases are removed before _and after_ each test run
  that manipulates the set of installed databases
- Similarly, for quick query files, delete them before and after each
  test.
- Change some async `forEach` blocks to for loops in order to support
  sequential operations more easily.
2022-05-09 13:50:28 -07:00
Shati Patel
12d6ea3966 Update CLI version used in tests 2022-05-05 18:17:18 +01:00
shati-patel
e1adc7b428 MRVA: Rename summary file to make it appear first alphabetically 2022-05-05 14:32:55 +01:00
shati-patel
dc34adadcd Tidy up tests to use expected directory 2022-05-05 14:01:39 +01:00
shati-patel
6e06381640 Move expected files into subdirectory 2022-05-05 14:01:39 +01:00
shati-patel
f55389cd26 MRVA: Also test names of generated markdown files 2022-05-05 14:01:39 +01:00
shati-patel
6d930f53ba Don't include file extension for summary file
(to be consistent with other markdown files)
2022-05-04 17:18:00 +01:00
shati-patel
f7616cf685 Refactor: Include filename when generating markdown 2022-05-04 17:18:00 +01:00
Andrew Eisenberg
f55d9820bd Merge pull request #1329 from github/aeisenberg/run-queries-message
Further massage the message after running remote queries
2022-05-04 08:03:04 -07:00
Andrew Eisenberg
befc2cddd2 Apply suggestions from code review
Co-authored-by: Robert <robertbrignull@github.com>
2022-05-04 07:46:53 -07:00
Andrew Eisenberg
ef268e043f Further massage the message after running remote queries 2022-05-03 16:38:44 -07:00
Dave Bartolomeo
cff235c420 Auto-format 2022-05-03 18:14:03 -04:00
Dave Bartolomeo
1089a052ec Initial implementation of join order metric scanning 2022-05-03 13:20:30 -04:00
Charis Kyriakou
e10d2aef8e Upgrade node version minimum requirement (#1326) 2022-05-03 16:10:45 +01:00
Charis Kyriakou
a97c5fe836 MRVA: Support both local and gist links when generating markdown 2022-05-03 13:27:53 +01:00
shati-patel
9b6eddddae MRVA: Expand path results individually 2022-04-29 18:50:30 +01:00
shati-patel
ed84825e65 See if replaceAll works on actions 2022-04-29 14:58:16 +01:00
shati-patel
cb84003c31 Actually fix the test + code scanning error 🤞🏽 2022-04-29 14:58:16 +01:00
shati-patel
a1cd87aa3a Update test data + try to fix code scanning error 2022-04-29 14:58:16 +01:00
shati-patel
7d3b015e20 Generate markdown for raw result tables 2022-04-29 14:58:16 +01:00
Charis Kyriakou
7d0d11f526 MRVA: Add view on GitHub action to cancelled/failed queries (#1325) 2022-04-29 11:11:33 +01:00
Shati Patel
eb2520e7ca Fix outdated description of "watch" command 2022-04-28 15:29:38 +01:00
shati-patel
2675bf464e Correctly indent code snippets that use tabs 2022-04-28 12:03:39 +01:00
shati-patel
b638449498 Link to specific highlighted line instead of whole code snippet 2022-04-28 12:03:39 +01:00
Dave Bartolomeo
1d195cb347 Merge remote-tracking branch 'origin/main' into dbartol/join-order 2022-04-27 17:50:50 -04:00
Dave Bartolomeo
8d8ed28aea Add necessary dependencies 2022-04-27 17:50:46 -04:00
shati-patel
e12bf63f9a Minor tidy-up 2022-04-27 16:45:01 +01:00
shati-patel
ffcc1f82f1 Generate markdown summary file for raw results 2022-04-27 16:45:01 +01:00
shati-patel
04d7b12dd8 Extract "getAnalysisResultCount" to helper file 2022-04-27 16:45:01 +01:00
shati-patel
3e33b00a75 Add test data for raw results 2022-04-27 16:45:01 +01:00
Andrew Eisenberg
12dc378fc1 Merge pull request #1305 from github/aeisenberg/mrva-result-message
Update the warning message after running variant analysis
2022-04-26 11:46:03 -07:00
Andrew Eisenberg
bbe99f4451 Fix newlines in tests 2022-04-26 11:34:00 -07:00
shati-patel
91b17f8fa6 Update CLI version used in tests 2022-04-26 14:36:48 +01:00
Shati Patel
69f1778309 Update extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts
Co-authored-by: Charis Kyriakou <charisk@users.noreply.github.com>
2022-04-26 10:15:45 +01:00
shati-patel
c55e801d00 Add example with multiple paths 2022-04-26 10:15:45 +01:00
shati-patel
b363f77a83 Tidy up how we display paths 2022-04-26 10:15:45 +01:00
shati-patel
f55f46f95b Markdown rendering: Display paths 2022-04-26 10:15:45 +01:00
github-actions[bot]
5ee2f0efe1 Bump version to v1.6.6 (#1315)
Co-authored-by: angelapwen <angelapwen@users.noreply.github.com>
2022-04-25 10:34:34 -07:00
Angela P Wen
1314a36ba4 v1.6.5 (#1314)
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
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2022-04-25 09:42:44 -07:00
shati-patel
2b8b621298 10% nicer way of wrapping code lines 😄
+ update test data to contain a single-line example
2022-04-25 12:42:10 +01:00
shati-patel
aed4c9fc58 MRVA: Make markdown code snippets look nicer
Remove some extraneous newlines
2022-04-25 12:42:10 +01:00
Andrew Eisenberg
604001dfb1 Update extensions/ql-vscode/src/remote-queries/run-remote-query.ts
Move comment
2022-04-22 13:42:10 -07:00
shati-patel
1a03c0e4ac Attempt to fix tests 2022-04-22 14:52:15 +01:00
shati-patel
a8c54b7640 MRVA: Don't display excessive error/warning pop-ups if user doesn't select a repo list 2022-04-22 14:52:15 +01:00
shati-patel
9bb60c9474 Link to workflow + fix incorrect comment 2022-04-22 13:01:54 +01:00
shati-patel
0b2ce7a071 MRVA: Display available results, even if some jobs are cancelled 2022-04-22 13:01:54 +01:00
Andrew Eisenberg
44145baca7 Use os.EOL instead of \n 2022-04-21 08:47:48 -07:00
Angela P Wen
dac7881ca3 Bug fix for show eval log and show eval log summary commands in query history view (#1304) 2022-04-21 08:11:58 -07:00
Charis Kyriakou
31bd927959 Fix max-width for code paths (#1309) 2022-04-21 13:12:40 +00:00
dependabot[bot]
46922de3c0 Bump style-loader from 0.23.1 to 3.3.1 in /extensions/ql-vscode
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 0.23.1 to 3.3.1.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v0.23.1...v3.3.1)

---
updated-dependencies:
- dependency-name: style-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-21 13:02:49 +00:00
shati-patel
908a862dd1 Tidy up test 2022-04-21 09:57:23 +01:00
shati-patel
6676ba99d0 Add initial test data for problem query 2022-04-21 09:57:23 +01:00
shati-patel
6d3c6e598f Change folder structure to have separate folders for path-problem and problem queries 2022-04-21 09:57:23 +01:00
shati-patel
e1a10fc827 Markdown results: Highlight snippets with "<strong>" 2022-04-21 09:17:31 +01:00
Andrew Eisenberg
2ebdbaafa3 Update the warning message after running variant analysis
Adds more information about onboarding new repos.
2022-04-20 14:46:23 -07:00
shati-patel
a74dfea08b Use HTML code blocks
This is so that we can highlight code snippets using `<strong>` tags
2022-04-20 10:32:24 +01:00
Andrew Eisenberg
44ff380c86 Merge pull request #1295 from github/aeisenberg/result-log
Add better error messages for partial failing variant analysis
2022-04-19 17:55:31 -07:00
Andrew Eisenberg
0a41713253 Add new test
And rename test file.
2022-04-19 17:45:17 -07:00
Andrew Eisenberg
f5a5675da4 Merge pull request #1298 from github/aeisenberg/no-results-mixing
Avoid loading wrong results into an open window
2022-04-19 16:02:14 -07:00
Andrew Eisenberg
7a8cf55090 Merge pull request #1294 from github/aeisenberg/db-name-github
Display nicer names for github-downloaded databases
2022-04-19 16:01:04 -07:00
Andrew Eisenberg
7932de3b7d Merge pull request #1299 from github/aeisenberg/remove-jsonc 2022-04-18 09:06:13 -07:00
Andrew Eisenberg
c8ba967a54 Remove jsonc dependency
This dependency was only used to parse package.json and
this can be just as easily parsed by regular JSON object.

jsonc can also parse JSON with comments, but there are no
comments in package.json.
2022-04-14 15:45:24 -07:00
Andrew Eisenberg
f5d2f0e0ca Merge pull request #1263 from github/dependabot/npm_and_yarn/extensions/ql-vscode/zip-a-folder-1.1.3
Bump zip-a-folder from 0.0.12 to 1.1.3 in /extensions/ql-vscode
2022-04-14 15:36:44 -07:00
Andrew Eisenberg
2c7e2f4b7f Avoid loading wrong results into an open window
This fixes a bug where an open results view will accumulate results from
other queries who have their results downloaded while this view is open.

The fix is to ensure that the results view for the query is open when
some results are downloaded.
2022-04-14 14:54:42 -07:00
dependabot[bot]
ee3ebe687b Bump zip-a-folder from 0.0.12 to 1.1.3 in /extensions/ql-vscode
Bumps [zip-a-folder](https://github.com/maugenst/zip-a-folder) from 0.0.12 to 1.1.3.
- [Release notes](https://github.com/maugenst/zip-a-folder/releases)
- [Commits](https://github.com/maugenst/zip-a-folder/commits)

---
updated-dependencies:
- dependency-name: zip-a-folder
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-14 21:09:55 +00:00
Andrew Eisenberg
77024f0757 Merge pull request #1297 from github/dependabot/npm_and_yarn/extensions/ql-vscode/async-2.6.4
Bump async from 2.6.3 to 2.6.4 in /extensions/ql-vscode
2022-04-14 14:08:46 -07:00
Andrew Eisenberg
c0e39886eb Add unit tests for remote queries in logs
Also, change text slightly.
2022-04-14 13:39:36 -07:00
dependabot[bot]
6339e7897d Bump async from 2.6.3 to 2.6.4 in /extensions/ql-vscode
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-14 19:41:37 +00:00
Andrew Eisenberg
783a8a8772 Merge pull request #1290 from github/aeisenberg/remote-history-label-editing
Allow remote query items to have their labels edited
2022-04-14 12:40:50 -07:00
Andrew Eisenberg
8f2d865999 Display nicer names for github-downloaded databases
This will now name databases downloaded from github based on their nwo.

Also, this adds a new button to suggest downloading from github in an
empty databases view.
2022-04-14 12:36:43 -07:00
Andrew Eisenberg
d6d0825926 Merge branch 'main' into aeisenberg/remote-history-label-editing 2022-04-14 12:30:08 -07:00
Andrew Eisenberg
37de2e7f52 Add better error messages for partial failing variant analysis
Two scenarios handled:

1. no database for existing repo
2. repo does not exits (or no access rights for current user)

In either case, an error message is sent to the logs, with a notificaiton
in a popup.
2022-04-13 16:32:13 -07:00
Andrew Eisenberg
800c9e0c93 Remove deprecated comments
Also, change interpolation of result count. For Remote queries, this
value will be empty. For local queries, use the label `X results`, where
`X` is the number of results for this query.
2022-04-13 14:08:44 -07:00
shati-patel
a1bc7eb4d5 Capitalize! 2022-04-13 17:00:17 +01:00
shati-patel
8ff45d2aee Split handling of highlighted code lines into helper function 2022-04-13 17:00:17 +01:00
Andrew Eisenberg
8ec19777b5 Merge pull request #1291 from github/aeisenberg/handle-remote-cancel
Handle cancelling of remote queries
2022-04-13 06:59:14 -07:00
Andrew Eisenberg
3e388fedeb Merge pull request #1292 from github/aeisenberg/rename-remote-queries
Rename remote queries -> variant analysis
2022-04-13 06:41:33 -07:00
Andrew Eisenberg
83ffba2f08 Rename remote queries -> variant analysis
In some user facing text.
2022-04-12 13:16:44 -07:00
Andrew Eisenberg
f1c4fef8ba Allow remote query items to have their labels edited
The labels for remote query items are interpolated using the same
strategy as local queries with two caveats:

1. There is no easy way to get the result count without reading files,
   so, this value is kept empty.
2. There is no database name for remote queries. Instead, use the
   nwo of the controller repo.

Also, adds tests for the history item label provider.
2022-04-12 12:37:31 -07:00
Andrew Eisenberg
eec506a209 Introduce history-item-label-provider
The label provider is the instance that performs the logic for
generating labels for history items, using string interpolation when
necessary.

This commit creates the label provider and uses it with local queries.
Remote queries will be changed in the next commit.
2022-04-12 12:35:01 -07:00
Andrew Eisenberg
2ca0060c6a Remove references to 'remote query' in user-facing text
(Only in recently introduced locations. More work still needs to be
done.)

Also:

- Change error to info
- Create credentials directly, don't use a callback.
2022-04-12 12:20:39 -07:00
shati-patel
8b2d79a7f7 Formatting fixes and code tidy-up 2022-04-12 12:32:45 +01:00
shati-patel
c4db8b6d4b Create markdown summary file for sharing MRVA results 2022-04-12 12:32:45 +01:00
Andrew Eisenberg
61d4305593 Handle cancelling of remote queries
This change issues a cancel request when the user clicks on "cancel" for
a remote query.

The cancel can take quite a while to complete, so a message is popped up
to let the user know.
2022-04-11 19:05:00 -07:00
Andrew Eisenberg
542e1d24aa Allow remote query items to have their labels edited
The labels for remote query items are interpolated using the same
strategy as local queries with two caveats:

1. There is no easy way to get the result count without reading files,
   so, this value is kept empty.
2. There is no database name for remote queries. Instead, use the 
   nwo of the controller repo.
2022-04-11 14:20:57 -07:00
shati-patel
47ec074cfb Tidy-up and address review comments 2022-04-11 15:24:08 +01:00
shati-patel
e44835e795 Make line endings consistent? 2022-04-11 15:24:08 +01:00
shati-patel
2e28146a58 Create markdown files for sharing results 2022-04-11 15:24:08 +01:00
Andrew Eisenberg
85e051a76d Merge pull request #1285 from github/aeisenberg/reenable-openvsx
Reenable publishing to open-vsx
2022-04-08 09:40:40 -07:00
Andrew Eisenberg
7027a61e63 Update changelog 2022-04-07 14:01:28 -07:00
Andrew Eisenberg
e8c5b27d92 Reenable publishing to open-vsx
The extension ms-vscode.test-adapter-converter is now available on
open-vsx, but under a different name.

Fixes https://github.com/github/vscode-codeql/issues/1085

I have verified that I can publish and install the extension by
manually publishing v1.6.4.
2022-04-07 13:58:16 -07:00
Andrew Eisenberg
a3deec7875 Merge pull request #1280 from febuiles/patch-2
Update dependency-review.yml
2022-04-07 08:39:47 -07:00
Andrew Eisenberg
6282a462c8 Merge pull request #1283 from github/bump-cli 2022-04-07 07:44:29 -07:00
Shati Patel
dac5952e96 Bump CLI version used in integration tests 2022-04-07 15:30:41 +01:00
Federico Builes
ada6fcb908 Try using workflow_dispatch. 2022-04-07 13:36:57 +02:00
Andrew Eisenberg
8d2f902420 Merge pull request #1282 from github/version/bump-to-v1.6.5
Bump version to v1.6.5
2022-04-07 02:11:28 -07:00
aeisenberg
fc3fe7a81e Bump version to v1.6.5 2022-04-06 22:39:04 +00:00
Andrew Eisenberg
426cc95e9f Merge pull request #1281 from github/v1.6.4
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.6.4
2022-04-06 15:36:05 -07:00
Andrew Eisenberg
9e40043fe0 v1.6.4 2022-04-06 14:54:56 -07:00
Federico Builes
14608fe5f7 Update dependency-review.yml 2022-04-06 15:17:40 +02:00
Charis Kyriakou
22ed090685 Add support for system defined repository lists (#1271) 2022-04-06 09:05:22 +01:00
Charis Kyriakou
2ca4097daf Move remote queries test files to be under remote-queries dir (#1270) 2022-04-05 08:40:10 +01:00
github-actions[bot]
f1d16015bf Bump version to v1.6.4 (#1278)
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-04-04 23:44:55 +00:00
Andrew Eisenberg
9a81ad05ed Merge pull request #1277 from github/v1.6.3
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.6.3
2022-04-04 12:39:27 -07:00
Andrew Eisenberg
76e983d19c v1.6.3
Also adds a step in our release process to manually test the new
extension build.
2022-04-04 12:30:41 -07:00
Andrew Eisenberg
a3015c0fa3 Merge pull request #1276 from github/aeisenberg/dev-dependencies
Move source-map-support to dependencies
2022-04-04 12:27:09 -07:00
Andrew Eisenberg
88d0bda049 Move source-map-support to dependencies 2022-04-04 12:15:57 -07:00
Andrew Eisenberg
d2ec54e89e Merge pull request #1273 from github/version/bump-to-v1.6.3
Bump version to v1.6.3
2022-04-04 09:10:52 -07:00
edoardopirovano
4559c5a38d Bump version to v1.6.3 2022-04-04 15:28:36 +00:00
Edoardo Pirovano
16bd106abc v1.6.2
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
2022-04-04 08:25:23 -07:00
Charis Kyriakou
e5dcec8d8e Move repository selection code to own module (#1269) 2022-04-04 11:03:53 +01:00
Charis Kyriakou
ad3565d3ad Use the repos defined in the query result instead of the query (#1268) 2022-04-04 11:03:05 +01:00
Andrew Eisenberg
5fe12ecd74 Merge pull request #1265 from github/aeisenberg/pat-instructions-update
Move vscode marketplace pat isntructions to internal docs
2022-03-31 12:24:51 -07:00
Andrew Eisenberg
318214642f Merge pull request #1249 from github/dependabot/npm_and_yarn/extensions/ql-vscode/ts-node-10.7.0
Bump ts-node from 8.10.2 to 10.7.0 in /extensions/ql-vscode
2022-03-31 12:15:43 -07:00
Andrew Eisenberg
227fe3ee6b Fix typo
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2022-03-31 12:12:57 -07:00
dependabot[bot]
978a82dd1a Bump ts-node from 8.10.2 to 10.7.0 in /extensions/ql-vscode
Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 8.10.2 to 10.7.0.
- [Release notes](https://github.com/TypeStrong/ts-node/releases)
- [Commits](https://github.com/TypeStrong/ts-node/compare/v8.10.2...v10.7.0)

---
updated-dependencies:
- dependency-name: ts-node
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-31 18:51:38 +00:00
Andrew Eisenberg
04f72a7da9 Merge pull request #1260 from github/aeisenberg/source-map-support
Add source map support and clean test dependencies
2022-03-31 11:42:22 -07:00
Andrew Eisenberg
a0954a1dc0 Move vscode marketplace pat isntructions to internal docs 2022-03-31 10:22:33 -07:00
Angela P Wen
cc1bf74370 Print end-of-query summary logs to Query Server Console (#1264)
* Log new end summary file to query server console

* Change supported CLI version to 2.9.0
2022-03-31 16:26:13 +00:00
Andrew Eisenberg
2f7908773a Merge pull request #1253 from github/aeisenberg/codeSnippet-handling 2022-03-31 07:19:44 -07:00
Andrew Eisenberg
0efd02979e Merge pull request #1242 from github/aeisenberg/analysis-results-on-restart 2022-03-31 07:19:02 -07:00
shati-patel
bd9776c4b7 Variant analysis: Remove handling of invalid repos
This is now done automatically on the API side
2022-03-31 15:15:16 +01:00
Andrew Eisenberg
35e9da83ec Add source map support and clean test dependencies
1. Source map support means that stack traces will point to the *.ts
   file instead of the generated *.js file
2. Cleaning test dependencies means moving all mocha and chai
   registration into the respective index files and removing unnecessary
   imports.
2022-03-30 12:30:18 -07:00
Andrew Eisenberg
4f5ca0bca9 Merge pull request #1261 from github/aeisenberg/dependabot-changes
Run dependabot updates weekly
2022-03-30 12:05:06 -07:00
Andrew Eisenberg
43f314b2b5 Change missing code snippet handling in UI
Also, simplify sarif tests.
2022-03-30 12:02:19 -07:00
Andrew Eisenberg
4bdf579ce2 Merge branch 'aeisenberg/analysis-results-on-restart' into aeisenberg/codeSnippet-handling 2022-03-30 11:57:24 -07:00
Andrew Eisenberg
aba3039eef Merge pull request #1257 from github/dependabot/npm_and_yarn/extensions/ql-vscode/sinon-13.0.1
Bump sinon from 9.0.2 to 13.0.1 in /extensions/ql-vscode
2022-03-30 11:48:11 -07:00
Andrew Eisenberg
bbff791c65 Merge pull request #1258 from github/dependabot/npm_and_yarn/extensions/ql-vscode/gulp-sourcemaps-3.0.0
Bump gulp-sourcemaps from 2.6.5 to 3.0.0 in /extensions/ql-vscode
2022-03-30 11:47:20 -07:00
Andrew Eisenberg
1ed50b3081 Run dependabot updates weekly
Daily is too noisy.
2022-03-30 11:45:39 -07:00
Andrew Eisenberg
67336a24e7 Simplify checking for downloaded analyses
And some renaming.
2022-03-30 11:30:10 -07:00
Andrew Eisenberg
48174c327d Merge pull request #1246 from github/aeisenberg/repo-filter
Add repositories search box
2022-03-30 11:14:27 -07:00
Andrew Eisenberg
43f2539b42 Remove unused css class 2022-03-30 10:54:14 -07:00
dependabot[bot]
462a7a722a Bump gulp-sourcemaps from 2.6.5 to 3.0.0 in /extensions/ql-vscode
Bumps [gulp-sourcemaps](https://github.com/gulp-sourcemaps/gulp-sourcemaps) from 2.6.5 to 3.0.0.
- [Release notes](https://github.com/gulp-sourcemaps/gulp-sourcemaps/releases)
- [Commits](https://github.com/gulp-sourcemaps/gulp-sourcemaps/compare/v2.6.5...v3.0.0)

---
updated-dependencies:
- dependency-name: gulp-sourcemaps
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-30 13:04:47 +00:00
dependabot[bot]
4101bb252e Bump sinon from 9.0.2 to 13.0.1 in /extensions/ql-vscode
Bumps [sinon](https://github.com/sinonjs/sinon) from 9.0.2 to 13.0.1.
- [Release notes](https://github.com/sinonjs/sinon/releases)
- [Changelog](https://github.com/sinonjs/sinon/blob/main/docs/changelog.md)
- [Commits](https://github.com/sinonjs/sinon/compare/v9.0.2...v13.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-30 13:04:31 +00:00
Shati Patel
4ff4e4827e Bump CLI version in integration tests 2022-03-30 12:03:16 +01:00
Andrew Eisenberg
8daa92ad49 Merge branch 'main' into aeisenberg/analysis-results-on-restart 2022-03-29 16:04:35 -07:00
Andrew Eisenberg
371e83bff9 Merge branch 'aeisenberg/analysis-results-on-restart' into aeisenberg/codeSnippet-handling 2022-03-29 15:30:08 -07:00
Andrew Eisenberg
6fa0227a1e Merge branch 'main' into aeisenberg/codeSnippet-handling 2022-03-29 15:08:17 -07:00
Andrew Eisenberg
c38e4ce265 Merge pull request #1252 from github/aeisenberg/settings
Prevent cli path from being synced across remote instances
2022-03-29 14:23:51 -07:00
Andrew Eisenberg
de06ed148d Merge branch 'main' into aeisenberg/analysis-results-on-restart 2022-03-29 14:21:15 -07:00
Andrew Eisenberg
21bcd62ba8 Merge pull request #1239 from github/dependabot/npm_and_yarn/extensions/ql-vscode/types/gulp-replace-1.1.0
Bump @types/gulp-replace from 0.0.31 to 1.1.0 in /extensions/ql-vscode
2022-03-29 14:21:06 -07:00
Andrew Eisenberg
76c034f79a Merge branch 'main' into aeisenberg/repo-filter 2022-03-29 14:15:31 -07:00
Andrew Eisenberg
d8d394ce40 Use new version of gulp-replace 2022-03-29 14:09:01 -07:00
Andrew Eisenberg
213f4ce92f Merge branch 'main' into aeisenberg/settings 2022-03-29 13:54:41 -07:00
Andrew Eisenberg
2d1726763f Merge pull request #1254 from github/aeisenberg/fix-main
Fix duplication import
2022-03-29 13:54:00 -07:00
Andrew Eisenberg
abfd9b3cbd Fix duplication import 2022-03-29 13:21:08 -07:00
Andrew Eisenberg
6114f6a7fd Merge branch 'main' into aeisenberg/analysis-results-on-restart 2022-03-29 13:18:13 -07:00
Andrew Eisenberg
61e674e9f6 Allow for undefined codeSnippets
This reverts commit 006cc8c52a.
2022-03-29 13:10:28 -07:00
Andrew Eisenberg
006cc8c52a Undo sarif-processing change
Will move to a different PR.
2022-03-29 13:07:56 -07:00
Andrew Eisenberg
ffe7fdcb46 Rename methods and address comments 2022-03-29 13:04:00 -07:00
Andrew Eisenberg
49cceffe1b Merge pull request #1235 from github/aeisenberg/history-sort
Add query history sorting for remote queries
2022-03-29 11:13:35 -07:00
Andrew Eisenberg
011782395a Merge pull request #1250 from github/dependabot/npm_and_yarn/extensions/ql-vscode/types/webpack-5.28.0
Bump @types/webpack from 4.41.21 to 5.28.0 in /extensions/ql-vscode
2022-03-29 11:13:00 -07:00
Andrew Eisenberg
558009543f Update changelog 2022-03-29 11:11:44 -07:00
Andrew Eisenberg
aaef5bde2c Prevent cli path from being synced across remote instances
This will fix a problem where settings sync will cause the cli not
to be found on codespaces.
2022-03-29 11:08:31 -07:00
Andrew Eisenberg
f52f595d56 Add max-width for remote queries results page 2022-03-29 11:05:22 -07:00
dependabot[bot]
50196d8430 Bump @types/webpack from 4.41.21 to 5.28.0 in /extensions/ql-vscode
Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.21 to 5.28.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/webpack)

---
updated-dependencies:
- dependency-name: "@types/webpack"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 17:17:18 +00:00
Andrew Eisenberg
2ecfbfbb42 Merge pull request #1244 from github/aeisenberg/webpack-watch
Add webpack watch gulp task
2022-03-29 10:16:18 -07:00
Andrew Eisenberg
9508dffe6d Merge pull request #1236 from github/dependabot/npm_and_yarn/extensions/ql-vscode/fs-extra-10.0.1
Bump fs-extra from 9.0.1 to 10.0.1 in /extensions/ql-vscode
2022-03-29 10:15:13 -07:00
Andrew Eisenberg
b4a72bbcab Merge pull request #1238 from github/dependabot/npm_and_yarn/extensions/ql-vscode/through2-4.0.2
Bump through2 from 3.0.2 to 4.0.2 in /extensions/ql-vscode
2022-03-29 10:08:32 -07:00
Andrew Eisenberg
4ceaaf92cc Merge pull request #1237 from github/dependabot/npm_and_yarn/extensions/ql-vscode/vsce-2.7.0
Bump vsce from 1.88.0 to 2.7.0 in /extensions/ql-vscode
2022-03-29 10:07:04 -07:00
Andrew Eisenberg
ef28c9531b Update extensions/ql-vscode/gulpfile.ts/webpack.ts 2022-03-29 08:50:42 -07:00
Shati Patel
c86c602e39 Allow GitHub URL as well as NWO (#1241) 2022-03-29 12:45:46 +01:00
Angela P Wen
3bee2905e5 Gate show eval log and summary commands behind CLI v2.8.4 (#1243) 2022-03-29 05:30:31 -04:00
Edoardo Pirovano
9ac8a15cd5 Address review comments from @aeisenberg 2022-03-29 05:30:31 -04:00
Edoardo Pirovano
81b8104064 Expose per-query structured evaluator logs 2022-03-29 05:30:31 -04:00
Andrew Eisenberg
65f58b1f98 Add repositories search box
A simple, webview-only search box for filtering repositories from
the remote queries results view.
2022-03-28 17:01:11 -07:00
Andrew Eisenberg
7e872aa6d6 Add webpack watch gulp task
Now, when running `npm run watch`, both the regular tsc command
and the webpack command will be run in watch mode.

The raw gulp tasks are now:

- `gulp watchView` to watch webpack compilation.
- `gulp watchCss` to watch for css changes.
- `gulp compileView` to compile the webpack once and exit.

However, stats are no longer being printed out. Not sure why.
2022-03-28 15:43:35 -07:00
Andrew Eisenberg
0383a91a68 Display proper download state in remote results view
Before displaying any results for a remote query, ensure that all
downloaded results are in memory. This ensures the proper download icon
is displayed alongside each NWO.
2022-03-28 12:38:13 -07:00
Andrew Eisenberg
bb6ebe5750 Handle query directory not existing
Also, fix some changelog notes.
2022-03-28 10:55:02 -07:00
Andrew Eisenberg
71aa3d145f Update changelog 2022-03-25 14:30:01 -07:00
dependabot[bot]
2f1f80029b Bump @types/gulp-replace from 0.0.31 to 1.1.0 in /extensions/ql-vscode
Bumps [@types/gulp-replace](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/gulp-replace) from 0.0.31 to 1.1.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/gulp-replace)

---
updated-dependencies:
- dependency-name: "@types/gulp-replace"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 21:28:00 +00:00
dependabot[bot]
ad18cfa284 Bump through2 from 3.0.2 to 4.0.2 in /extensions/ql-vscode
Bumps [through2](https://github.com/rvagg/through2) from 3.0.2 to 4.0.2.
- [Release notes](https://github.com/rvagg/through2/releases)
- [Commits](https://github.com/rvagg/through2/compare/v3.0.2...v4.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 21:27:45 +00:00
dependabot[bot]
92ed1c6ac9 Bump vsce from 1.88.0 to 2.7.0 in /extensions/ql-vscode
Bumps [vsce](https://github.com/Microsoft/vsce) from 1.88.0 to 2.7.0.
- [Release notes](https://github.com/Microsoft/vsce/releases)
- [Commits](https://github.com/Microsoft/vsce/compare/v1.88.0...v2.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 21:27:25 +00:00
dependabot[bot]
e71e04a8f1 Bump fs-extra from 9.0.1 to 10.0.1 in /extensions/ql-vscode
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 9.0.1 to 10.0.1.
- [Release notes](https://github.com/jprichardson/node-fs-extra/releases)
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jprichardson/node-fs-extra/compare/9.0.1...10.0.1)

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 21:27:03 +00:00
Andrew Eisenberg
ef127c279c Merge pull request #1233 from github/aeisenberg/dependabot
Add dependabot configuration
2022-03-25 14:26:02 -07:00
Andrew Eisenberg
4afac5fa4d Add query history sorting for remote queries
Also, fix two smaller issues:

- Ensure the `Open Query Directory` command opens inside the specified
  directory.
- Ensure label changes are saved across restarts.
2022-03-25 14:25:07 -07:00
Andrew Eisenberg
29ae97aa82 Add actions to dependabot config 2022-03-25 13:18:46 -07:00
Andrew Eisenberg
9319d7e8ef Add dependabot configuration 2022-03-25 12:21:10 -07:00
dependabot[bot]
689db3713b Bump minimist from 1.2.5 to 1.2.6 in /extensions/ql-vscode
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 18:48:40 +00:00
Andrew Eisenberg
0b9fcb884b Merge pull request #1202 from github/aeisenberg/update-tsc
Update tsc to 4.5.5
2022-03-25 11:33:37 -07:00
Andrew Eisenberg
23e29a1fdc Update tsc to 4.5.5
The default version of tsc in vscode is now 4.5.4. This version
has changed the type of the variable in the catch block.
Previously, it was `any`. Now it is `unknown`.

This change updates vscode so that it can build with 4.5.4.

Previously, this had been a bit of a pain since sometimes running
a compile task in vscode will use the global default version of
tsc.
2022-03-25 09:48:51 -07:00
Shati Patel
90d636a026 Download databases from GitHub (#1229) 2022-03-25 15:24:09 +00:00
Andrew Eisenberg
3e3e12afb9 Merge pull request #1230 from github/aeisenberg/astviewer-uri
Fix invalid file comparison for changing ast viewer location
2022-03-25 08:21:05 -07:00
Andrew Eisenberg
421f5d23ec Update changelog 2022-03-24 12:39:11 -07:00
Andrew Eisenberg
0fa91f32cb Fix invalid file comparison for changing ast viewer location
This fixes a bug where the ast viewer was not updating its source
location when a user clicks on different parts of a file.

The problem was that the file name of the AST viewer was being stored as
a base name, which was getting compared with the full URI string of the
current file.

This fixes the comparison to ensure that the full URI strings are always
being compared.
2022-03-24 12:36:17 -07:00
shati-patel
3d21b203be Make "promptForLanguage" more general
(so we can use it for downloading a GH database as well)
2022-03-21 16:37:51 +00:00
shati-patel
3972b8f4c1 Rename LGTM-specific function 2022-03-21 16:37:51 +00:00
Tobias Speicher
2d1707db00 refactor: replace deprecated String.prototype.substr()
.substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated
Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
2022-03-21 14:16:54 +00:00
Robert
72aa4f0561 Merge pull request #1226 from github/robertbrignull/allow-custom-action-branch
Allow a custom branch name in settings file
2022-03-21 10:52:21 +00:00
Robert
fd57cc95e9 Remove unnnecessary function 2022-03-21 10:38:00 +00:00
Robert
04c392be7e Allow a custom branch name in settings file 2022-03-18 16:26:06 +00:00
github-actions[bot]
38da598214 Bump version to v1.6.2 (#1221)
Co-authored-by: charisk <charisk@users.noreply.github.com>
2022-03-17 12:47:33 +00:00
Charis Kyriakou
3f2c9b647c v1.6.1 (#1220)
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
2022-03-17 12:04:37 +00:00
Shati Patel
7d5b4369c1 Fix highlighting issues (#1219) 2022-03-17 11:45:31 +00:00
Shati Patel
aade33fa88 Minor webview fixes (#1217) 2022-03-17 11:12:50 +00:00
Shati Patel
2a8a90bdfc Change public occurrences of "remote queries" (#1215) 2022-03-17 10:14:32 +00:00
Shati Patel
f36048cc95 Use variable for highlighting code (#1216) 2022-03-17 10:08:42 +00:00
Charis Kyriakou
517feeca21 Remove SARIF viewer support (#1213) 2022-03-16 14:39:52 +00:00
Charis Kyriakou
9436a49118 Remove helper command for working on the Remote Query results view (#1214) 2022-03-16 14:19:19 +00:00
Charis Kyriakou
0e02cb08fd Enable viewing of analyses results (#1212) 2022-03-16 14:15:43 +00:00
Shati Patel
26244efc50 Create remote file links to GitHub URL (#1209)
Co-authored-by: Charis Kyriakou <charisk@github.com>
2022-03-16 14:11:17 +00:00
Charis Kyriakou
6339eeffe5 Minor styling fix for raw results (#1211) 2022-03-16 11:44:51 +00:00
Charis Kyriakou
8cc2f598eb Fix highlight region end column calculation (#1210) 2022-03-16 09:47:09 +00:00
Charis Kyriakou
46a1dd57f4 Minor style fixes around result rendering (#1208) 2022-03-15 14:43:24 +00:00
shati-patel
9d99fc521e Get database sha from result index 2022-03-15 10:30:01 +00:00
Shati Patel
bcf79354ee Bump CLI version in integration tests 2022-03-15 10:22:18 +00:00
Charis Kyriakou
27a8636bac Deal with non-printable characters when rendering raw results (#1203) 2022-03-14 11:25:33 +00:00
Charis Kyriakou
92a99938c9 Add support for remote queries raw results (#1198) 2022-03-14 08:18:43 +00:00
Charis Kyriakou
ed61eb0a95 Deal with analysis messages that have links to locations (#1195) 2022-03-14 08:14:09 +00:00
Andrew Eisenberg
50d495b522 Merge pull request #1201 from mrysav/patch-1
Install Dependency Review Action
2022-03-11 10:40:06 -08:00
Andrew Eisenberg
526d5c2c44 Apply suggestions from code review 2022-03-11 10:29:02 -08:00
Charis Kyriakou
1720f9201e Update Primer React to v35 (#1199) 2022-03-10 20:24:12 +00:00
Mitchell Rysavy
e62de1ca22 Create dependency-review.yml 2022-03-10 14:48:06 -05:00
Charis Kyriakou
d052ddb742 Rename analysis alert results (#1197) 2022-03-10 07:56:05 +00:00
Andrew Eisenberg
af53a02ea5 Merge pull request #1192 from github/aeisenberg/disable-openvsx-deploy
Disable the open-vsx-publish job
2022-03-09 09:27:17 -08:00
Charis Kyriakou
8e2d18da8c Rename ColumnValue to CellValue (#1196) 2022-03-09 16:44:15 +00:00
Charis Kyriakou
2c5004387d Add support for showing code flows (#1187) 2022-03-09 09:15:45 +00:00
Charis Kyriakou
3fc3b259ba Add pre-push hook check to block leftover .only()s (#1189) 2022-03-08 09:32:18 +00:00
Andrew Eisenberg
cd95f68692 Merge pull request #1191 from github/version/bump-to-v1.6.1
Bump version to v1.6.1
2022-03-07 10:25:23 -08:00
Andrew Eisenberg
59c3b1ba2f Disable the open-vsx-publish job
It is failing, blocked on #1085
2022-03-07 10:19:42 -08:00
aeisenberg
fa85865fe5 Bump version to v1.6.1 2022-03-07 18:04:29 +00:00
Andrew Eisenberg
5575d4142c Merge pull request #1190 from github/v1.6.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.6.0
2022-03-07 10:00:52 -08:00
Andrew Eisenberg
ae6263a07f v1.6.0
Note that the change to `run-remote-query.ts` is being coordinated
with the rest of the remote-queries team.
2022-03-07 09:46:56 -08:00
Charis Kyriakou
9af75634fa Remove .only from mocha test (#1188) 2022-03-07 10:04:29 +00:00
Andrew Eisenberg
04b8681272 Merge pull request #1184 from github/aeisenberg/open-remote
Add command to open remote query on github
2022-03-04 10:46:55 -08:00
Andrew Eisenberg
d5549f2894 Add command to open remote query on github
Command is available for remote queries that are in progress or
completed.
2022-03-04 10:36:41 -08:00
Charis Kyriakou
b510b85ca0 Extract code snippet into stand alone component (#1181) 2022-03-04 08:06:19 +00:00
Andrew Eisenberg
5ad754a3a2 Merge pull request #1178 from github/aeisenberg/log-history
Save log files to the query history directory
2022-03-03 08:14:25 -08:00
Andrew Eisenberg
4f04f9db6e Merge pull request #1179 from github/aeisenberg/open-query-folder
Add new command to open the query history directory
2022-03-03 08:00:55 -08:00
Andrew Eisenberg
025a1a1383 Add new command to open the query history directory
Allows users to explore the contents and all artifacts of the query
that was just run.
2022-03-03 07:57:56 -08:00
Andrew Eisenberg
f28c1f91d9 Ensure structured logging file exists before running a query 2022-03-03 07:57:23 -08:00
Charis Kyriakou
c609377a9c Add SARIF processing and basic alert rendering (#1171) 2022-03-03 09:03:27 +00:00
Andrew Eisenberg
2579d12f24 Save log files to the query history directory
This commit deprecates the the cutsom log directory option by saving
all log files with query history.

There is a simplification of the `OutputChannelLogger` since it no
longer needs to manage deleting log files on exit.

Also, the `codeQL.runningQueries.customLogDirectory` is marked as
deprecated. If this value is being used, a warning message is popped
up after the query completes.
2022-03-02 12:21:15 -08:00
Andrew Eisenberg
c18f7953e7 Merge pull request #1111 from github/aeisenberg/graph-viewer
More work on the graph viewer
2022-03-02 07:52:49 -08:00
Andrew Eisenberg
3a292b02b6 Simplify walkDirectory
The check for `seenFiles` is not necessary since we do not need to
handle symbolic links.
2022-03-01 14:02:21 -08:00
Andrew Eisenberg
7baf2d0a2a Small formatting changes for graphes 2022-03-01 11:50:55 -08:00
Andrew Eisenberg
328289eb1c Ensure graph view loads when result is clicked
Without these changes, a race condition was sometimes hit when viewing
a graph. There are two, related issues that are fixed. These problems
did not appear in the past since rendering a normal results view is
much faster and the message handler is always already set up by the
time the interface first sends a message over to the web view.

1. `vscode.postMessage({ t: 'resultViewLoaded' });` was being called
   before the component is completely mounted. Ie- `componentDidMount`
   is not called. So, the interface is notified that the web view is
   ready to receive messages _before_ it is actually ready to receive
   messages.

   The change ensures the interface only sends messages when the web
   view is ready.

2. `this._panelLoaded` is never set to false if the panel is unloaded.
   This means that if a panel is re-opened, the interface assumes that
   the view is nearly _immediately_ ready to receive messages.

   The change ensures that the interface waits for the webview to really
   be loaded before sending messages.

In both of these cases, if the interface sends the `setState` message
too early, then the message is ignored since no handlers have been added
to the web view.
2022-03-01 11:46:26 -08:00
Andrew Eisenberg
95d93eeb61 Merge pull request #1176 from github/bump-cli
Bump CLI version in integration tests
2022-03-01 08:15:05 -08:00
Charis Kyriakou
b54cc27cab Update ECMAScript version to be the same across the codebase (#1177) 2022-03-01 16:01:29 +00:00
shati-patel
c9ca1ee7b3 Bump CLI version in integration tests 2022-03-01 12:14:30 +01:00
Charis Kyriakou
649d6d94a3 Expose Remote Query language (#1173) 2022-02-28 13:12:01 +00:00
Charis Kyriakou
bf68d21830 Some UI fixes for collapsible items (#1172) 2022-02-28 10:53:02 +00:00
Andrew Eisenberg
64b33b76cb Update node version in CI 2022-02-26 19:48:55 -08:00
Andrew Eisenberg
c189df3fd6 Ensure Graph results can only be seen if in canary mode 2022-02-25 18:04:21 -08:00
Andrew Eisenberg
277869ebca Ensure graph queries with ids that have slashes work
Do this by actually walking the interpretation directory.

Move the directory walker from tests to prod and make it async. Also
add tests for it.

And add a warning on graph views to let users know that it is not
production quality.

Finally, change the interpreted directory to be `graphResults` instead
of `interpretedResults.sarif`.
2022-02-25 18:03:14 -08:00
shati-patel
303513a566 Make icon part of clickable link
Plus fix the associated styling/CSS
2022-02-25 21:08:16 +00:00
shati-patel
8712106b3d Add link to workflow run logs from results view 2022-02-25 21:08:16 +00:00
Andrew Eisenberg
cdb9506583 Merge branch 'main' into aeisenberg/graph-viewer 2022-02-25 10:38:19 -08:00
Andrew Eisenberg
94a311a550 Merge pull request #1166 from github/aeisenberg/remote-queries-unit-tests
Add unit tests for query history and remote queries
2022-02-25 10:07:56 -08:00
Andrew Eisenberg
791e7e9c4d Small cleanups around remote-queries tests
- More explicit test helper module names
- Fix unit test names
- Better sanitization of repo names in tests
2022-02-25 09:57:15 -08:00
Angela P Wen
6cfa7e2cd3 Integrate evaluator structured logging per query server instance (#1151)
Adds structured evaluator logging on a per-query instance to CLI v 2.8.2+. The newline-delimited JSON logs are emitted to `structured-evaluator-log.json` in the directory with other query artifacts, but not consumed by the extension.

Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2022-02-25 09:22:56 -08:00
Andrew Eisenberg
7196c26181 Merge pull request #1168 from github/aeisenberg/query-history-version
Preemptively add a version number to the query history json file
2022-02-25 08:11:14 -08:00
Charis Kyriakou
735f177283 Use webview CSP source for style-src (#1170) 2022-02-25 16:03:58 +00:00
Andrew Eisenberg
f857e5ec6c Ensure all tests are run
Co-authored-by: Charis Kyriakou <charisk@users.noreply.github.com>
2022-02-25 08:00:03 -08:00
Charis Kyriakou
a5e02950c2 Fixed unique key warning (#1169) 2022-02-25 10:58:24 +00:00
Andrew Eisenberg
4a928f1298 Add unit tests for query history and remote queries
Adds some tests for reading in the history and manipulating.
There are some more tests to come later. Maybe in another PR, maybe in
this one.

Note that this PR uses a new node 16 API String.prototype.replaceAll.
I think this is ok since vscode ships with node 16. If this causes
problems, I can separate to a different PR and we can discuss there.
2022-02-24 23:09:55 -08:00
Andrew Eisenberg
f59012862e Preemptively add a version number to the query history json file
Since we are now storing query history on disk, we will need to handle
situations where versions change. For now, there is only version 1. In
the future, we may need to make breaking changes to this format and we
need the flexibility to detect and possibly handle different versions.

In this case, users don't often downgrade their vscode versions, so
most likely, we only need to be forward compatible. Ie- we need to
handle moving from v1 to v2, but not the other way around.
2022-02-24 11:42:46 -08:00
Andrew Eisenberg
5f5418a297 Merge pull request #1164 from github/aeisenberg/avoid-download
Avoid downloading a result if it already exists
2022-02-24 08:11:02 -08:00
Andrew Eisenberg
548a216b56 Avoid downloading a result if it already exists
This commit adds a check if a results artifact already exists before
trying to download it again.

This is not a complete solution since the page icon will still have a
download button even if the artifact already exists. In this case,
clicking on it will avoid downloading it a second time.

The next step is to read in the downloaded artifacts and display them
appropriately.
2022-02-23 11:16:39 -08:00
Andrew Eisenberg
c943c89fc6 Merge pull request #1163 from github/aeisenberg/remote-multi-analyses
Allow multiple analyses for same repo to be downloaded
2022-02-23 11:13:37 -08:00
Andrew Eisenberg
06de6077ba Merge pull request #1162 from github/aeisenberg/remote-query-restart
Remember remote queries across restarts
2022-02-23 11:13:17 -08:00
Andrew Eisenberg
cef1fcc95d Merge pull request #1155 from github/aeisenberg/remote-query-save
Add remote query items to history view
2022-02-23 11:13:00 -08:00
Andrew Eisenberg
1ed8b225db Small cleanup and comment 2022-02-23 09:52:46 -08:00
Andrew Eisenberg
f0354c87f4 Allow multiple analyses for same repo to be downloaded
Removes the limitation specified in #1089 where analyses for the same
repo and different queries will overwrite each other.
2022-02-22 14:16:54 -08:00
Andrew Eisenberg
5e06a615cd Remember remote queries across restarts
Remote query items will be stored in query history and will remain
available across restarts.

When the extension is restarted, any `InProgress` remote queries will
be monitored until they complete.

When clicked on, a remote query is opened and its results can be
downloaded. The query text and the query file can be opened from the
history menu. A remote query can be deleted as well, which will purge
all results from global storage.

Limitations:

1. Labels are not editable
2. Running multiple queries that each run on the same repository
   will have conflicting results and there will be errors when trying
   to view the results of the second query. This limitation is not new,
   but it is easier to hit now. See #1089.

Both of these limitations will be addressed in future PRs.
2022-02-22 11:42:52 -08:00
Andrew Eisenberg
e11aa7af18 Merge branch 'main' into aeisenberg/remote-query-save 2022-02-22 10:13:16 -08:00
Andrew Eisenberg
f4ddc17851 Merge pull request #1158 from github/aeisenberg/open-query-logger
Open query server logger for query errors
2022-02-22 09:55:19 -08:00
Andrew Eisenberg
ebce2826cb Merge pull request #1143 from github/aeisenberg/refactor-query-history-info
Refactor query history to handle remote and local
2022-02-22 09:51:13 -08:00
Andrew Eisenberg
4c411acef4 Merge branch 'main' into aeisenberg/open-query-logger 2022-02-22 09:44:41 -08:00
Andrew Eisenberg
ddc941f464 Merge pull request #1157 from github/aeisenerg/empty-additional-packs
Ensure `--addtional-packs` arg not used for empty workspace
2022-02-22 09:44:10 -08:00
shati-patel
c5ff2c6f76 Fix styling for light mode 2022-02-22 17:14:15 +00:00
shati-patel
85ac16bb22 Change shape of result index 2022-02-22 17:14:15 +00:00
shati-patel
e7ee4a33c7 Add new component for displaying analysis failures 2022-02-22 17:14:15 +00:00
shati-patel
ac0da04542 Read analysis failures from index file 2022-02-22 17:14:15 +00:00
shati-patel
3337117970 Use more accurate primer icon 2022-02-22 12:04:21 +00:00
Charis Kyriakou
9b61ff5714 Use Primer octicons where possible (#1156) 2022-02-21 11:28:13 +00:00
Andrew Eisenberg
d25db48452 Open query server logger for query errors
Because errors when running queries tend to have better explanations
in the query server log instead of the extension log, by default open
the query server log for query errors.
2022-02-18 12:55:32 -08:00
Andrew Eisenberg
251f354076 Ensure --addtional-packs arg not used for empty workspace 2022-02-18 10:16:42 -08:00
Andrew Eisenberg
9c6ae226fb Merge branch 'aeisenberg/refactor-query-history-info' into aeisenberg/remote-query-save 2022-02-17 14:11:44 -08:00
Andrew Eisenberg
a502ee85d1 Fix unit test and add comments/todos 2022-02-17 13:07:10 -08:00
Andrew Eisenberg
eec72e0cbd Merge pull request #1142 from github/aeisenberg/remote-queries-history
Store remote query artifacts in global storage
2022-02-17 12:35:09 -08:00
Andrew Eisenberg
7a1acce133 Merge pull request #1150 from github/aeisenberg/clear-cache
Fix race condition where packs with no name errored during remote query
2022-02-17 10:04:53 -08:00
Andrew Eisenberg
84b4bfe663 Merge pull request #1149 from github/aeisenberg/avoid-redownload
Avoid re-downloading analyses
2022-02-17 08:14:58 -08:00
Andrew Eisenberg
16df990183 Add remote query items to history view
This is another incremental step on the way to saving history.

This commit adds remote items to the history view. It adds in progress
and completed icons. Users can explicitly remove items.

Here is what is _not_ working:

1. Any other query history commands like open results or open query.
2. Seeing items after a restart.
2022-02-16 18:43:00 -08:00
Andrew Eisenberg
969dd26041 Use QueryHistoryInfo instead of LocalQueryInfo
Also, rename RemoteQueryInfo -> RemoteQueryHistoryItem
2022-02-16 13:52:17 -08:00
Andrew Eisenberg
9df1f91318 Fix race condition where packs with no name errored during remote query
Uses the internal `clear-cache` CLI server command.
2022-02-16 12:46:38 -08:00
Andrew Eisenberg
48ddc66d47 Merge branch 'aeisenberg/remote-queries-history' into aeisenberg/refactor-query-history-info 2022-02-16 12:34:43 -08:00
Andrew Eisenberg
85e3869607 Avoid re-downloading analyses
Avoids re-downloading analyses when downloading all analyses.
2022-02-16 12:01:21 -08:00
Andrew Eisenberg
5bb2a763e3 Avoid artifactStorageDir and use queryId to build storage paths
This is still an intermediate step as we start to bring in more
abstractions. I plan to implement a storage handler that will
keep track of all the different bits for a remote query.
2022-02-16 11:46:10 -08:00
Andrew Eisenberg
2110709d72 Merge pull request #1140 from github/aeisenberg/contextual-storage
Store query results for contextual queries elsewhere
2022-02-16 08:11:49 -08:00
shati-patel
493033edc0 Remove unused code about validating DB uploads 2022-02-16 14:25:43 +00:00
Andrew Eisenberg
bf8e77b9b9 Ensure proper paths are used for retrieving artifacts
This change builds on the previous change to ensure that sarif results
can be displayed properly. Here is what it does:

- Move prepareDownloadDirectory to the RemoteQueryManager
- Store the queryResult to disk
- Use the `artifactStorageDir` as the location where artifacts are kept
- Add `artifactStorageDir` to DownloadLink
- Ensure the webview passes around the right links.
2022-02-15 20:24:17 -08:00
Andrew Eisenberg
c7e5581027 Store query results for contextual queries elsewhere
We want them to be deleted when the application exits. We don't want
them to be stored with user queries.
2022-02-15 13:18:45 -08:00
Andrew Eisenberg
c78802a1ed Refactor query history to handle remote and local
This is a step on the way towards storing remote query history across
restarts.

This PR adds a `QueryHistoryInfo` type that is a union of two types:
`LocalQueryInfo` and `RemoteQueryInfo`.

`LocalQueryInfo` used to be called `FullQueryInfo` and `RemoteQueryInfo`
is only a skeleton right now. The body will be added later. This PR
only introduces it and changes types to make future PRs simpler.

Also, `slurp` and `splat` have been moved to the `query-serialization.ts`
module.
2022-02-15 13:07:47 -08:00
Andrew Eisenberg
39f9c082b9 Store remote query artifacts in global storage
This moves all artifacts downloaded for a remote query into the global
storage directory. Each remote query gets its own directory. The
parent directory is the shared query storage directory.

Each remote query directory also gets a timestamp file.

With these changes, remote queries will be persisted across restarts
and deleted automatically on the same schedule as local queries.

Note: This does _not_ add remote queries to the query history view yet.
This part of the feature is coming next.
2022-02-15 13:05:51 -08:00
shati-patel
ca1ef5192d Update wording in results view 2022-02-15 17:36:39 +00:00
Shati Patel
1d6fef9169 Update CLI version in integration tests 2022-02-15 15:50:51 +00:00
shati-patel
81f80ddbe5 Clear SARIF viewer before showing new results 2022-02-15 14:33:15 +00:00
Andrew Eisenberg
b53657344c General refactoring and adding comments
There is no new behaviour added in this commit. Just some cleanup:

- Move some shared constants to the `helpers` module
- Add comments to some of the query related modules
- Some general formatting and tidying
2022-02-14 11:39:19 -08:00
Andrew Eisenberg
95e818898e Merge pull request #1130 from github/aeisenberg/save-query-history
Save query history across restarts
2022-02-14 11:25:35 -08:00
Andrew Eisenberg
a7e014a87e Merge branch 'main' into aeisenberg/save-query-history 2022-02-14 11:15:22 -08:00
Andrew Eisenberg
cca65e5a48 Rename and add comment 2022-02-14 10:54:17 -08:00
Robin Neatherway
a75249f3e4 Merge pull request #1132 from github/rneatherway/remove-repositories
Remove .repositories configuration approach
2022-02-14 17:43:24 +00:00
Henry Mercer
053a4b0392 Remove feature flag for loading ML models from packs
This functionality should now be enabled for all users.
2022-02-14 10:36:00 +00:00
Andrew Eisenberg
d1362bf44f More work on the graph viewer
The viewer is largely implemented now with the following features and
limitations:

1. Any query with `@kind graph` will be opened as a graph
2. Queries that are `@kind graph` and
   `@tags ide-contextual-queries/print-cfg` will be used in the
   `CodeQL: View CFG` context command. This will be visible
   similar to how the AST viewer works. If there is not exactly
   1 such query for a given language, then the extension will throw
   an error.
3. Unfortunately, the cg viewer assumes that the entire file will
   be added to the graph, so often this will be too big, That leads to
   the following limitation:
4. There is no size checking on the graph. Graphs that are too big will
   crash vscode.
5. Lastly, there is a small bug in how the `@id` is interpreted. Any
   `@id` with a `/` in it will place the `.dot` in a location that
   can't be found by vscode. So, just don't name your queries with any
   `/`.

This feature is only available in canary mode.
2022-02-11 15:55:01 -08:00
Tom Hvitved
580832ea7b Graph viewer support 2022-02-11 14:47:13 -08:00
Tom Hvitved
ddca0bb851 Address review comments 2022-02-11 14:45:15 -08:00
Tom Hvitved
d9a04ea895 Refactor interpreted-data interface in preparation for other interpretations than SARIF 2022-02-11 14:45:15 -08:00
Andrew Eisenberg
48ccb27e49 Update changelog 2022-02-11 13:54:09 -08:00
Andrew Eisenberg
a2b5ad07ff Fix upgrades path
Ensure that upgrades can be resolved even when the upgrades pack is not
in the workspace. This is the situation when the core libraries are
resolved from the package cache.

This change works because `qlProgram.libraryPath` is the resolved
search path for compiling the query. We are guaranteed that the
appropriate core libraries are included in this query.

Note that this change avoids using extra source folders from the
workspace. Previously without using packages, we assume that all
relevant query paths are already inside the workspace. With
packaging, this is no longer the case.

It is theoretically possible that there will be extra upgrade scripts
that are not on the resolved search path, but are included in the
workspace. This situation would have worked in the past.This is not a
situation that we expect to happen in practice. And if this does happen,
I believe this is an error and all upgrades should be added explicitly
to the search path.

An open question is if this will work with downgrade scripts. If it does
not, then I don't think this change makes things any worse than before.
2022-02-11 12:58:34 -08:00
dependabot[bot]
cc9cbf7f06 Bump pathval from 1.1.0 to 1.1.1 in /extensions/ql-vscode
Bumps [pathval](https://github.com/chaijs/pathval) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/chaijs/pathval/releases)
- [Changelog](https://github.com/chaijs/pathval/blob/master/CHANGELOG.md)
- [Commits](https://github.com/chaijs/pathval/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: pathval
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-11 11:40:21 +00:00
Andrew Eisenberg
ad5c43c9ba Fix failing tests 2022-02-10 20:00:46 -08:00
Andrew Eisenberg
9c27d01d47 Merge branch 'main' into aeisenberg/save-query-history 2022-02-10 16:03:56 -08:00
Andrew Eisenberg
64ac33e3bb Address comments from PR
- Rename queryStorageLocation -> queryStorageDir
- Extract scrubber to its own module
- Add more comments
- Rename source -> cancellationSource
- Ensure cancellatinSource is disposed
2022-02-10 16:03:46 -08:00
aeisenberg
329fb87e12 Bump version to v1.5.12 2022-02-10 12:50:03 -08:00
Andrew Eisenberg
bd5da2b0f0 Release preparation for v1.5.11
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
2022-02-10 12:26:24 -08:00
Henry Mercer
55c21888af Update Code Scanning workflow now that we no longer need tools: latest
The Actions VM image containing v2.7.6 of the CodeQL CLI has now fully
rolled out, so we no longer need to download the latest CodeQL bundle to
use this CLI and include this repo in the ML-powered queries beta.
2022-02-10 18:15:52 +00:00
Robin Neatherway
d49e6e19a6 Remove .repositories configuration approach
This was our first temporary attempt at configuration of a remote
query run. I don't think that we're using it anymore, so it simplifies
the code to remove it.
2022-02-10 12:47:48 +00:00
Charis Kyriakou
edb1af09c4 Hide analyses results until view is complete (#1126) 2022-02-10 08:13:59 +00:00
Charis Kyriakou
ab3822d1cc Use SARIF viewer extension for analysis results (#1125) 2022-02-10 08:13:31 +00:00
Andrew Eisenberg
69120e0799 Add extra delay in telemetry test
Some of our internal integration tests are failing occasionally. I
think extending the wait time here will fix.
2022-02-09 15:10:59 -08:00
Andrew Eisenberg
7785dfead2 Update changelog 2022-02-09 15:09:08 -08:00
Andrew Eisenberg
29c29f9e3a Save query history across restarts
Successfully completed queries will be stored on disk and available
across restarts.

- The query results are contained in global storage.
- Metadata and a summary about a query are stored in workspace storage.
- There is a job that runs every 2 hours to determine if any queries are
  old enough to be deleted.
2022-02-09 15:01:44 -08:00
Andrew Eisenberg
b7dafc31bb Better comments around splat and slurp functions
Also, address other small PR comments.
2022-02-08 12:43:38 -08:00
Andrew Eisenberg
2f5a306c2d Simplify the query history objects to make them serializable
The goal with this change is to simplify the query history to make it
possible to serialize and de serialize.

This change adds serialization support. Since query history objects are
complex, the de-serialization requires manipulation of the 
de serialized object prototypes.
2022-02-08 12:43:38 -08:00
Charis Kyriakou
0ef6b45b19 Remove use of all-results artifact (#1120) 2022-02-08 08:28:54 +00:00
dependabot[bot]
d9f33d34e3 Bump copy-props from 2.0.4 to 2.0.5 in /extensions/ql-vscode
Bumps [copy-props](https://github.com/gulpjs/copy-props) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/gulpjs/copy-props/releases)
- [Changelog](https://github.com/gulpjs/copy-props/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/copy-props/compare/2.0.4...2.0.5)

---
updated-dependencies:
- dependency-name: copy-props
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 15:39:23 -08:00
dependabot[bot]
5758e03a17 Bump nth-check from 2.0.0 to 2.0.1 in /extensions/ql-vscode
Bumps [nth-check](https://github.com/fb55/nth-check) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 15:38:18 -08:00
Andrew Eisenberg
5d9f80cce8 Update ensureCli.ts 2022-02-07 13:30:15 -08:00
Andrew Eisenberg
867ee530b1 Update CLI test version 2022-02-07 13:30:15 -08:00
Charis Kyriakou
27e6a55756 Add full screen modal component (#1117) 2022-02-07 08:24:29 +00:00
Andrew Eisenberg
b237bafa2f Avoid AST Viewer for invalid selections
When a directory is selected or there are multiple selections, do not
show the command.
2022-02-04 11:54:11 -08:00
Andrew Eisenberg
d0bde800f7 Update changelog 2022-02-04 11:54:11 -08:00
Andrew Eisenberg
da0090aa99 Fix ast view and command registration
Two small bugs:

1. The AST view command was viewing the wrong ast when the command was
   selected from the context menu. It was always selecting the active
   editor instead of the item selected in the file menu.
2. The `codeql.showLogs` command was not being registered properly.
   With this change, there is uniform error handling, telemetry,
   and disposal.
2022-02-04 11:54:11 -08:00
Charis Kyriakou
66c9879ce3 Fix package versions for react typings (#1118) 2022-02-04 13:39:29 +00:00
Charis Kyriakou
9c2585116a Show collapsible analyses results (#1116) 2022-02-04 08:02:21 +00:00
Andrew Eisenberg
e46c0e25e8 Update CODEOWNERS
code-scanning security experiences team should be helping to review the remote queries part of the extension.
2022-02-03 08:14:25 -08:00
Charis Kyriakou
658b0ce243 Convert re-usable components to styled-components (#1112) 2022-02-03 08:34:24 +00:00
Andrew Eisenberg
c084e31416 Simplify command expressions
Use only `||` and clearly specify when each item should be visible.
2022-02-02 13:39:12 -08:00
Andrew Eisenberg
9046844f0c Add cancellation from query history view
And tweak the commands visible from the view.
2022-02-02 13:39:12 -08:00
Charis Kyriakou
5a9b49b9bb Show remote analyses results status (#1108) 2022-02-01 17:55:10 +00:00
Andrew Eisenberg
0672133bca Ensure query text shows for empty selections
Fixes a bug where quick eval was showing empty query text.

Previously, `getQueryText` was looking up the query text when it was
called if the specified text was empty. This was removed with the
recent changes to query history. It was also a bug since the query file
could have changed after the query was run.

This change ensures that if the quick eval position is empty, the
entire line is returned as the quick eval location.
2022-02-01 06:34:48 -08:00
Andrew Eisenberg
c0de99bc42 Add tests for sort order and selection 2022-02-01 06:34:48 -08:00
Andrew Eisenberg
6dbb1a27b9 Fix sort order and selection
This commit fixes two related issues with the
history view.

1. Sort order was changing after a query item completed. The fix is a
   change in how we fire off the `onDidChangeTreeData` event. When the
   event is fired with a single item, that item is pushed to the top of
   the list. I'm not exactly sure why this wasn't happening before, but
   I suspect it was because we were refreshing the list at the same time
   as we were inserting the new item.

   The solution here is to always refresh the entire list, instead of
   single items. This is fine since re building the list is a trivial
   operation. See the `refreshTreeView()` method.

   With this change, the sort order is now stable.

2. Originally reported here: #1093
   The problem is that the internal treeView selection was not being
   updated when a new item was being added. Due to some oddities with
   the way selection works in the tree view (ie- the visible selection
   does not always match the internal selection).

   The solution is to use the current item from the `treeDataProvider`
   in `determineSelection`.

Also, this change makes the sorting more precise and fixes some typos.
2022-02-01 06:34:48 -08:00
Andrew Eisenberg
dc1bace4c6 Ensure Open Query Text works for in progress queries
Same with "Open query that produced these results".

In order to do this, needed to move the query id generation into the
InitialQueryInfo.
2022-02-01 06:34:48 -08:00
Andrew Eisenberg
afe3c56ca8 Update changelog 2022-02-01 06:34:48 -08:00
Andrew Eisenberg
a6f42e3eb3 Add query items immediately
This is a large commit and includes all the changes to add query
history items immediately. This also includes some smaller related 
changes that were hit while cleaning this area up.

The major part of this change is a refactoring of what we store in
the query history list. Previously, the `CompletedQuery` was stored.
Previously, objects of this type include all information about a query that was run
including:

- Its source file and text range (if a quick eval)
- Its database
- Its label
- The query results itself
- Metrics about the query run
- Metadata about the query itself

Now, the item stored is called a `FullQueryInfo`, which has two
properties:

- InitialQueryInfo: all the data about the query that we know _before_
  the query completes, eg- its source file and text range, database, and
  label
- CompletedQueryInfo: all the data about the query that we can only
  learn _after_ the query completes. This is an optional property.

There is also a `failureReason` property, which is an optional string
describing why the query failed.


There is also a `FullCompletedQueryInfo` type, which only exists to 
help with stronger typing. It is a `FullQueryInfo` with a non-optional
`CompletedQueryInfo`.

Most of the changes are around changing how the query history accesses
its history list.

There are some other smaller changes included here:

- New icon for completed query (previously, completed queries had no
  icons).
- New spinning icon for in progress queries.
- Better error handling in the logger to handle log messages when the
  extension is shutting down. This mostly helps clean up the output
  during tests.
- Add more disposables to subscriptions to be disposed of when the
  extension shuts down.
2022-02-01 06:34:48 -08:00
Charis Kyriakou
9c2bd2a57b Use streaming SARIF parser (#1109) 2022-01-31 16:39:20 +00:00
Charis Kyriakou
f42f474113 Use 'engines' to define required node and npm versions (#1106) 2022-01-28 15:37:04 +00:00
Henry Mercer
17c31e1539 Run CodeQL analysis with latest CLI to opt into ML-powered queries beta 2022-01-28 14:14:00 +00:00
Charis Kyriakou
b0fb4d6bc9 Upgrade React version (#1103) 2022-01-28 10:37:59 +00:00
Charis Kyriakou
f8690bcebc Auto-download analyses results (#1098) 2022-01-27 10:16:13 +00:00
shati-patel
b0410ec5de Update to VS Code 1.59.0 2022-01-26 12:31:09 +00:00
shati-patel
19e0058e61 Bump version to v1.5.11 2022-01-25 16:41:03 +00:00
shati-patel
6d64c8f031 v1.5.10
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
2022-01-25 16:19:57 +00:00
Charis Kyriakou
1216fce853 Download and process analyses results (#1089) 2022-01-25 08:28:53 +00:00
dependabot[bot]
c598306f49 Bump node-fetch from 2.6.1 to 2.6.7 in /extensions/ql-vscode
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 13:56:26 -08:00
Shati Patel
4f8d6e310c Bump CLI version for integration tests 2022-01-24 11:56:10 +00:00
Shati Patel
894eb7046e Make step for maintainers only 2022-01-20 09:32:07 +00:00
shati-patel
3d6515e807 Update documentation step in PR template 2022-01-20 09:32:07 +00:00
shati-patel
068d461c14 Update progress bar for "install pack dependencies" 2022-01-20 09:15:35 +00:00
shati-patel
8e20d01b4e Sleep earlier 2022-01-19 20:46:33 +00:00
shati-patel
8aaa2492f2 Wait a few seconds before monitoring remote query run 2022-01-19 20:46:33 +00:00
Shati Patel
c9a649f974 Update extensions/ql-vscode/CHANGELOG.md
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2022-01-19 20:43:25 +00:00
shati-patel
f07d9cff9b Update wording to be more clear 2022-01-19 20:43:25 +00:00
shati-patel
b7bfd9ea85 Add CLI version constraint for packaging 2022-01-19 20:43:25 +00:00
shati-patel
25f0e3ccab Add separate tests for valid/invalid pack install 2022-01-19 20:43:25 +00:00
shati-patel
e19addec60 Catch error in tests 2022-01-19 20:43:25 +00:00
shati-patel
a5bc25e211 Fix import + throw error 2022-01-19 20:43:25 +00:00
shati-patel
c90659fd92 First attempt at tests 2022-01-19 20:43:25 +00:00
shati-patel
30b7fe7472 Update changelog 2022-01-19 20:43:25 +00:00
shati-patel
d54fbdf4e6 Address review comments
1. Hard-code more common query packs
2. Correctly resolve workspace packs
3. Only install workspace packs
2022-01-19 20:43:25 +00:00
shati-patel
6d7b02583d Add "pack install" and "pack download" commands 2022-01-19 20:43:25 +00:00
shati-patel
51906cbbda Update dependencies in integration test runner 2022-01-19 16:16:53 +00:00
Shati Patel
d3da9d30f4 Make sure extension dependencies are installed 2022-01-19 16:16:53 +00:00
Charis Kyriakou
9b9a0cb64a Split download actions in remote queries view (#1083) 2022-01-19 09:41:04 +00:00
Andrew Eisenberg
1dde5af591 Bump CLI version to run integration tests against 2022-01-18 08:41:00 -08:00
Charis Kyriakou
4312d35743 Added paging to the listWorkflowRunArtifacts call (#1082) 2022-01-18 16:24:25 +00:00
Charis Kyriakou
2dcdbcbd32 Break remote queries view into more components (#1079) 2022-01-18 15:46:45 +00:00
Charis Kyriakou
e8e50c4381 Extract base react components (#1078) 2022-01-17 19:41:33 +00:00
Charis Kyriakou
0e6d85374f Rename analysis result to analysis summary (#1074) 2022-01-10 11:57:02 +00:00
Angela P Wen
54789613dc Merge pull request #1071 from angelapwen/standardize-integration-args
Standardize integration test args in VSCode debugger
2022-01-07 12:00:20 -08:00
Angela P Wen
43b3f72a41 Clarify instructions in comment 2022-01-07 11:51:44 -08:00
Angela P Wen
13742a4e9e Match integration test args with run-integration-tests.ts 2022-01-07 11:25:48 -08:00
Charis Kyriakou
6bd7f0ae12 Add helper command for working on the Remote Queries Results view (#1069) 2022-01-07 13:39:07 +00:00
shati-patel
fc51b336fa Update changelog 2022-01-06 19:34:29 +00:00
shati-patel
df16d1ab1d Results view: Don't reopen webview if it's already visible 2022-01-06 19:34:29 +00:00
Dominik Bamberger
b661b2be97 Update Docs ping in issue template (#1061)
* Update Docs ping in issue template

* Update .github/pull_request_template.md

Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>

Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2021-12-22 08:59:25 -08:00
Andrew Eisenberg
2d39bee416 Ensure all tests are run 2021-12-17 13:22:20 -08:00
Andrew Eisenberg
56eeb1badb Delete output folder before building 2021-12-17 13:09:02 -08:00
shati-patel
d547f81a55 Bump version to v1.5.10 2021-12-17 15:36:16 +00:00
Charis Kyriakou
e1b35cdbbc Fix CSS file paths
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
2021-12-17 15:14:08 +00:00
shati-patel
c01704b8aa v1.5.9 2021-12-17 12:04:27 +00:00
shati-patel
5a19042fc8 Update changelog 2021-12-16 17:58:55 +00:00
shati-patel
bdf8c0b9c2 Add setting to enable/disable Quick Eval codelens 2021-12-16 17:58:55 +00:00
Shati Patel
bc08cbe74f Tidy up and add test for getting query metadata (#1050)
* Move/rename query metadata function

* Add test for `tryGetQueryMetadata`

* Split into two tests
2021-12-15 20:11:59 +00:00
Andrew Eisenberg
6e2e72a500 Be nicer about where to open the results webview (#1037)
* Be nicer about where to open the results webview

Currently, the webview _always_ opens next to the currently active
editor. This is a pain if you already have 2 columns open since this
means that the webview will open in a third column, which is rarely
what you want.

This change uses a more sophisticated approach to opening the webview:

1. If there is only one column, open webview to the right of it
2. If there are multiple columns and the active editor is _not_ the
   last column, open to the right of the active editor
3. Otherwise open in the first column.

This will avoid opening a new column unless there is only one column
open right now.

There is no native API that vscode exposed to compare column locations,
so this uses the `ViewColumn` api is a slightly non-standard way.

A limitation is that if the last column is empty and the active editor
is to the left of it, then the webview will not be opened there (which
would be nice). Instead, it will be opened in column 1.

Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2021-12-15 19:33:53 +00:00
shati-patel
d0953fb63c Remote queries: Get query name from metadata (if possible) 2021-12-15 11:00:41 +00:00
Charis Kyriakou
4dbd15c66d Remote queries: No results view (#1048) 2021-12-15 08:55:00 +00:00
Charis Kyriakou
e9e41e07d1 Implement download behaviour in remote queries view (#1046) 2021-12-15 08:34:34 +00:00
Andrew Eisenberg
b435df4682 Fix type in comment
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2021-12-14 09:39:43 -08:00
Andrew Eisenberg
a3bf9f1c71 Handle different dependencies in remote queries tests
Starting in CLI 2.7.5, there will no longer be any
`codeql/javascript-upgrades` pack. Change the test so that it passes
using both old and new packs.
2021-12-14 09:39:43 -08:00
shati-patel
72ff828b57 Style link text + tidy up functions 2021-12-14 12:13:20 +00:00
shati-patel
b7f86ae7a9 Display query text in "virtual" (readonly) file 2021-12-14 12:13:20 +00:00
shati-patel
3c73390a44 Save query text in a temporary file 2021-12-14 12:13:20 +00:00
shati-patel
7117faa92b Rename properties and handle missing files 2021-12-14 12:13:20 +00:00
shati-patel
4257555c88 Remote queries: Open query file/text from webview 2021-12-14 12:13:20 +00:00
Angela P Wen
33b1465ccc Docs: add clarification on directory for running tests via CLI 2021-12-10 12:29:15 -08:00
Andrew Eisenberg
c8ed8b2591 Add code lens for quick evaluation (#1035)
* Add code lens for quick eval command

* Ensure commented out predicates do not have code lens

* Improve conditional check for commented out predicate  detection

* Refactor regex

* Move comment check to eliminate evaluating regex more than once

Co-authored-by: marcnjaramillo <mnj.webdeveloper@gmail.com>
2021-12-10 19:17:21 +00:00
Andrew Eisenberg
58f4a82616 Update changelog 2021-12-10 07:50:08 -08:00
Andrew Eisenberg
d5f0a659af Avoid showing the alert option in the drop down
Only show it when there really is an alert table to see.
2021-12-10 07:50:08 -08:00
Charis Kyriakou
60c977bff9 Move GitHub actions code to separate module (#1044) 2021-12-10 13:59:20 +00:00
Andrew Eisenberg
73f1beac6a Bump cli version for integration tests 2021-12-09 13:50:41 -08:00
Charis Kyriakou
6195c6552f Made the repo list in the remote query view expandable (#1039) 2021-12-09 10:38:18 +00:00
Charis Kyriakou
e365744dbc Monitor remote query run and render results (#1033) 2021-12-09 10:05:51 +00:00
Andrew Eisenberg
68f566dd1a Pass --old-eval-stats to query server
This is in preparation of supporting structured query logs.
When passing this option, use the old format of query logs.
Later, when we want to add support for structured query
logs, we can add remove this option.
2021-12-07 07:53:58 -08:00
Charis Kyriakou
bf350779c9 Merge pull request #1032 from github/remote-query-submission-result
Expose remote query submission result
2021-12-06 09:28:14 +00:00
Charis Kyriakou
07329c9ea5 Expose remote query submission result 2021-12-03 16:16:48 +00:00
Shati Patel
7e6483490a Merge remote queries webview outline into main (#1027) 2021-12-03 10:48:54 +00:00
shati-patel
749565828d Bump version to v1.5.9 2021-12-02 14:27:37 +00:00
shati-patel
ff751cc877 v1.5.8
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
2021-12-02 11:26:36 +00:00
Robin Neatherway
d7ba941803 Merge pull request #1009 from github/aeisenberg/remote-nested-queries
Remote queries: Handle nested queries
2021-12-01 19:24:10 +00:00
Andrew Eisenberg
e58201e24b Ensure server uses a well-known query pack name 2021-12-01 10:53:51 -08:00
Henry Mercer
81e60286f2 Require canary for loading models from packs 2021-12-01 09:40:06 +00:00
Henry Mercer
8e156d69d7 Apply suggestions from code review
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2021-12-01 09:40:06 +00:00
Henry Mercer
dfcaa27235 Update lockfile 2021-12-01 09:40:06 +00:00
Henry Mercer
ed0553c6b6 Gate loading ML models behind a hidden setting 2021-12-01 09:40:06 +00:00
Henry Mercer
84ecbfc7a1 Resolve ML models and pass them to the queryserver 2021-12-01 09:40:06 +00:00
Andrew Eisenberg
e13349ceb0 Update changelog 2021-11-29 11:16:49 -08:00
Andrew Eisenberg
a1bcb7519f Ensure src.zip is prioritized over src folder
Fixes a bug where legacy databases with both unzipped and zipped sources
were incorrectly being loaded with the src folder.
2021-11-29 11:16:49 -08:00
Andrew Eisenberg
b481441052 Emit more relevant error message when failing to add source folder (#1021)
* Emit more relevant error message when failing to add source folder

Fixes #1020

* Update changelog

* Clarify changelog and error message

Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>

Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2021-11-29 16:28:07 +00:00
github-actions[bot]
6a1d1a492e Bump version to v1.5.8 (#1017)
Co-authored-by: aeisenberg <aeisenberg@users.noreply.github.com>
2021-11-23 21:13:41 +00:00
Andrew Eisenberg
1dcd9c495c Prepare for the 1.5.7 release (#1016)
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
2021-11-23 12:52:40 -08:00
Andrew Eisenberg
a9b9502dbd Fix failing tests on windows
1. Acknowledge that the CLI has a bug for path serialization on <=2.7.2.
   Avoid testing the query path on that version.
2. Fix calculation of root path on windows.
2021-11-22 12:53:52 -08:00
Andrew Eisenberg
16c0bea799 Merge pull request #1004 from marcnjaramillo/fix-large-sarif-handling
Fix large sarif handling
2021-11-22 12:24:17 -08:00
marcnjaramillo
ad81127267 Move test files into data directory 2021-11-22 11:49:40 -08:00
Andrew Eisenberg
30d01cb0e0 Merge pull request #1007 from github/aeisenberg/sorted-result-sets
Ensure all result set names are loaded
2021-11-22 09:08:55 -08:00
Shati Patel
2584971a07 Update CLI version in tests 2021-11-22 14:02:09 +00:00
marcnjaramillo
9d9f48bcf8 Fix tests for sarif parser 2021-11-19 20:43:22 -08:00
marcnjaramillo
0bb1501e72 Move sarif parser and tests, build completing 2021-11-19 17:21:42 -08:00
marcnjaramillo
d53abd815d Make suggested changes, build currently failing 2021-11-19 16:01:18 -08:00
Andrew Eisenberg
d9c5ecf462 Fix failing test and remove changelog note 2021-11-19 14:32:38 -08:00
Shati Patel
51ed2cd480 Delete outdated issue template
I don't think we need this issue template any more... The release process is in https://github.com/github/vscode-codeql/blob/main/CONTRIBUTING.md#releasing-write-access-required.
2021-11-19 17:47:23 +00:00
Andrew Eisenberg
4c83805030 Update changelog 2021-11-18 18:13:29 -08:00
marcnjaramillo
c3eca5b1b7 Update test for valid SARIF file 2021-11-18 16:05:31 -08:00
Andrew Eisenberg
742bca1cf5 Remote queries: Handle nested queries
This change allows remote queries to run a query from a directory that
is not in the root of the qlpack.

The change is the following:

1. walk up the directory hierarchy to check for a non-local qlpack.yml
2. Copy over the files as before, but keep track of the relative
   location of the query compared to the location of the qlpack.yml.
3. Change the defaultSuite of the qlpack.yml so that _only_ this query
   is run as part of the default query.

Also, this adds a new integration test to ensure the nested query is
packaged appropriately.
2021-11-18 15:27:29 -08:00
Andrew Eisenberg
5ab55bb5a5 Merge branch 'main' into aeisenberg/sorted-result-sets 2021-11-18 10:49:44 -08:00
Musab Guma'a
3743895b66 Add "Preview Query Help" command 2021-11-18 16:10:26 +00:00
marcnjaramillo
ca5e5e23e6 Finish tests 2021-11-17 16:37:56 -08:00
marcnjaramillo
a666619289 Remove error handling for now 2021-11-17 16:37:56 -08:00
marcnjaramillo
63129236d0 Work on tests for new behavior 2021-11-17 16:37:56 -08:00
marcnjaramillo
4374f409a8 Add changelog entry and add missing dependencies 2021-11-17 16:37:37 -08:00
marcnjaramillo
c49aa8e05e Fix issue with large SARIF files crashing view
Authored by: Marc Jaramillo marcnjaramillo@github.com
Authored by: Musab Guma'a mgsium@github.com
2021-11-17 16:33:49 -08:00
Andrew Eisenberg
c590e2f36c Update package lock (#1003)
* Add leniency in how positions are handled

Previously, positions with end column of 0 were rejected by the
extension. CodeQL positions are supposed to be 1-based, but the CLI
does handle 0-based and negative positions by using character offsets
from the current line start.

Instead of rejecting these kinds of positions, the extension should
handle them as gracefully as possible.

Fixes #999

* Update package lock
2021-11-17 16:24:59 -08:00
Andrew Eisenberg
03d4aca639 Add leniency in how positions are handled (#1002)
* Add leniency in how positions are handled

Previously, positions with end column of 0 were rejected by the
extension. CodeQL positions are supposed to be 1-based, but the CLI
does handle 0-based and negative positions by using character offsets
from the current line start.

Instead of rejecting these kinds of positions, the extension should
handle them as gracefully as possible.

Fixes #999

* Add changelog entry
2021-11-17 16:24:48 -08:00
Andrew Eisenberg
01f24523ac Update changelog 2021-11-17 09:39:49 -08:00
Andrew Eisenberg
98312a72a7 Ensure all result set names are loaded
When the extension loads a sorted result set, it takes a shortcut and
avoids loads a file with only the bqrs results for that sorted table.

However, it does not load the results for any other table. This causes
result set names to go away. This change ensures that if we are loading
a sorted table, we also load the result set names for all other tables
in that query.

Fixes #1005.
2021-11-17 09:37:42 -08:00
Shati Patel
d579cd6541 Update CLI version
Version 2.7.1 was released this week
2021-11-17 13:30:34 +00:00
shati-patel
38e5d8babc Attempt to fix tests 2021-11-12 16:03:17 +00:00
shati-patel
c1fceab8d9 Re-use dbSchemeToLanguage 2021-11-12 16:03:17 +00:00
shati-patel
ae555969b5 Tidy up language dropdown 2021-11-12 16:03:17 +00:00
Andrew Eisenberg
3e0ea1ba77 Merge pull request #997 from github/aeisenberg/cli-version-test
Avoid testing for the correct CLI_VERSION if CLI_PATH is set
2021-11-08 09:37:18 -08:00
Andrew Eisenberg
ce1ebd2218 Avoid testing for the correct CLI_VERSION if CLI_PATH is set 2021-11-08 09:26:25 -08:00
Andrew Eisenberg
6215c2763e Merge pull request #996 from rneatherway/rneatherway/use-workflow-run-id
Start linking to the exact workflow run
2021-11-08 08:20:58 -08:00
Robin Neatherway
07437000ce Start linking to the exact workflow run
Now that the queries endpoint returns the id we can link straight
there. We'll make more changes to the progress tracking, but I think
it's worth making this minimal change now.
2021-11-08 15:19:29 +00:00
Andrew Eisenberg
0ef635bc68 Use the correct environment variable in integration tests (#994)
Also, fix documentation and launch configs to specify the correct and
complete set of environment variables we should be using.
2021-11-03 23:04:14 +00:00
Andrew Eisenberg
e9574d33a9 Merge pull request #985 from github/qc-packs
Remote Queries: Create packs for remote queries
2021-11-03 08:49:08 -07:00
Musab Guma'a
848869e3f4 Make "Open Referenced File" command functional on open .qlref 2021-11-02 11:19:39 +00:00
Andrew Eisenberg
4a65b6a8b2 Ensure anonymous and scope-less packs can be used as remote queries
When we generate the synthetic pack, just ensure that there is a valid name.
2021-11-01 15:18:23 -07:00
Andrew Eisenberg
28c76bece0 Change to 0.0.0 as synthetic version 2021-11-01 14:45:55 -07:00
shati-patel
56faf36edf Fix Windows path issue 2021-10-29 17:56:23 +01:00
Andrew Eisenberg
440044d2aa Add more debugging logic
Help understand why tests are failing.
2021-10-29 09:18:34 -07:00
Andrew Eisenberg
48468ff354 Allow custom setting of codeql cli for integration tessts 2021-10-29 08:29:45 -07:00
shati-patel
31dc11ed73 Fix recommended extensions 2021-10-29 14:53:45 +01:00
Andrew Eisenberg
903f5db707 Avoid running remote queries on v2.6.3 cli or earlier
Also:

- Fix the count of copied files
- A few typos
- Ensure the correct settings are applied for remote queries before
  running tests.
2021-10-28 16:08:43 -07:00
shati-patel
8317f39459 Update CLI version in ensureCli.ts 2021-10-28 15:25:22 +01:00
Andrew Eisenberg
42051f1620 Remote Queries: Create packs for remote queries
This is still a bit rough, but handles two cases:

1. There is a qlpack.yml or codeql-pack.yml file in the same directory
   as the query to run remotely. In this case, run `codeql pack
   packlist` to determine what files to include (and also always include
   the lock file and the query itself. Copy to a temp folder and run
   `pack install`, then `pack bundle`. Finally upload.
2. There is no qlpack in the current directory. Just copy the single
   file to the temp folder and generate a synthetic qlpack before
   installing, bundling and uploading.

Two cases that are not handled:

1. The query file is part of a workspace. Peer dependencies will not be
   found.
2. The query file and its qlpack file are not in the same directory.

These should be possible to handle later.  Also, need to create some
unit and integration tests for this.
2021-10-27 21:09:34 -07:00
Andrew Eisenberg
9b90579160 Merge pull request #984 from github/aeisenberg/add270
Add v2.7.0 CLI run tests against
2021-10-27 15:30:23 -07:00
Andrew Eisenberg
541367122e Add v2.7.0 CLI run tests against 2021-10-27 15:23:06 -07:00
Andrew Eisenberg
0a0500a60d Merge pull request #979 from mgsium/windows-paths
Fix the "CodeQL: Open Referenced File" command for windows paths
2021-10-27 09:00:23 -07:00
Musab Guma'a
746086b761 Fix "Open Referenced File" command for windows paths 2021-10-27 16:54:21 +01:00
Andrew Eisenberg
412d96409e Merge pull request #982 from github/aeisenberg/debug-cli-server
Add support for debugging the CLI server
2021-10-26 09:59:35 -07:00
Andrew Eisenberg
93e15b43a3 Remove hunks
Accidentally included from a different PR.
2021-10-26 09:42:32 -07:00
Andrew Eisenberg
dbc8198daa Add support for debugging the CLI server 2021-10-24 16:39:08 -07:00
Andrew Eisenberg
b3a51d7afd Merge pull request #978 from mgsium/case-insensitive-slugs
Case insensitive fallback check for GitHub repositories
2021-10-20 13:50:48 -07:00
Musab Guma'a
3d24328402 Update CHANGELOG.md 2021-10-20 20:46:22 +01:00
Musab Guma'a
1014c4bdda Added case-insensitive fallback check for GitHub repos when downloading an LGTM project. 2021-10-20 19:35:41 +01:00
shati-patel
b2a6263431 Send a query pack 2021-10-20 15:06:33 +01:00
Andrew Eisenberg
20cdca77a3 Merge pull request #977 from marcnjaramillo/fix-changelog-entries
Update CHANGELOG.md entries for most recent changes
2021-10-19 11:25:46 -07:00
marcnjaramillo
98d48a3709 Update CHANGELOG.md entries for most recent changes 2021-10-19 11:14:19 -07:00
Andrew Eisenberg
6b57993b2a Merge pull request #976 from marcnjaramillo/selected-database-view
Fix issue where 'Set current database' shows on selected database in …
2021-10-19 11:04:22 -07:00
marcnjaramillo
34ac30e403 Fix issue where 'Set current database' shows on selected database in the database view 2021-10-19 10:52:06 -07:00
Andrew Eisenberg
b8618aa87e Merge pull request #971 from marcnjaramillo/integrate-codeql-database-unbundle
Integrate codeql database unbundle
2021-10-19 10:01:40 -07:00
marcnjaramillo
7d8e63c1d1 Make changes requested by @aeisenberg
Co-authored by: Marc Jaramillo mnj.webdeveloper@gmail.com
Co-authored by: Musab Guma'a mgsium@github.com
2021-10-18 18:38:25 -07:00
marcnjaramillo
b22a8692c8 Integrated CLI database unbundle command for archive download
Co-authored by: Marc Jaramillo mnj.webdeveloper@gmail.comm
Co-authored by: Musab Guma'a mgsium@github.com
2021-10-18 14:55:17 -07:00
Marc Jaramillo
b5cdd833e2 Merge branch 'github:main' into main 2021-10-14 10:41:05 -07:00
Andrew Eisenberg
81a2f9c428 Merge pull request #963 from marcnjaramillo/handle-nonprint-chars
Handle nonprint chars
2021-10-08 14:35:24 -07:00
Marc Jaramillo
b43b824da6 Simplify changelog entry
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2021-10-08 13:52:07 -07:00
marcnjaramillo
22616c5582 Add changes to changelog 2021-10-08 13:05:48 -07:00
Marc Jaramillo
2570d179bc Merge branch 'github:main' into handle-nonprint-chars 2021-10-08 13:00:10 -07:00
Marc Jaramillo
1980f862c6 Merge branch 'github:main' into main 2021-10-08 12:57:36 -07:00
marcnjaramillo
d1eb31e231 Finish creating check for non-printing characters 2021-10-08 12:56:56 -07:00
shati-patel
68863e3b90 Bump version to v1.5.7 2021-10-07 18:47:29 +01:00
shati-patel
b38b884715 Add 1.5.6 header
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
2021-10-07 18:31:36 +01:00
shati-patel
cc6f2d8886 Prepare release 2021-10-07 18:31:36 +01:00
marcnjaramillo
245db7ca28 Add a check for strings with only new line chars 2021-10-07 09:09:48 -07:00
Andrew Eisenberg
197ab99db8 Merge pull request #959 from alexet/fix-db-remove
Delete database after removing it from query server control.
2021-10-07 08:50:22 -07:00
Andrew Eisenberg
6292adf491 Merge branch 'main' into fix-db-remove 2021-10-07 08:17:24 -07:00
Andrew Eisenberg
112d40ff1c Update CHANGELOG.md 2021-10-07 08:15:35 -07:00
Andrew Eisenberg
b92d6bab7c Merge pull request #965 from github/aeisenberg/logo-update
Update CodeQL logo
2021-10-07 08:08:51 -07:00
Andrew Eisenberg
0a4879c9a8 Merge branch 'main' into aeisenberg/logo-update 2021-10-07 08:01:05 -07:00
shati-patel
7d4d57104a Update integration test version 2021-10-07 10:13:53 +01:00
Andrew Eisenberg
f06c9abb35 Update integration test versions 2021-10-07 10:13:53 +01:00
marcnjaramillo
2f7d175a76 Make edits per feedback 2021-10-06 14:01:39 -07:00
Andrew Eisenberg
85eaa8b275 Update CodeQL logo
Both the marketplace icon and the sideview svg
2021-10-06 11:12:53 -07:00
marcnjaramillo
4783ad6bff Create rough solution for handling non-printing characters in results 2021-10-05 19:37:14 -07:00
Marc Jaramillo
9f0a975a0c Merge pull request #1 from github/main
Add progress messages to LGTM download option. (#960)
2021-10-05 19:22:49 -07:00
Marc Jaramillo
21dda65871 Add progress messages to LGTM download option. (#960)
* Add progress messages to LGTM download option.

* Add additional argument to get test passing again.

* Make edits requested by @aeisenerg

* Fix assertion in test case

* Update extensions/ql-vscode/CHANGELOG.md
2021-10-04 17:22:11 +01:00
Andrew Eisenberg
39fdd0cad5 Merge pull request #957 from marcnjaramillo/fix-lgtm-download-message
Remove line about selecting a language from the dropdown.
2021-10-01 12:07:46 -07:00
Marc Jaramillo
3fb2c71390 Merge branch 'main' into fix-lgtm-download-message 2021-10-01 11:19:57 -07:00
marcnjaramillo
b40f648a87 Remove line about selecting a language from the dropdown. 2021-10-01 11:07:37 -07:00
alexet
57216249c2 Delete database after removing it from query server control. 2021-10-01 18:40:07 +01:00
shati-patel
fbadc15ae9 Also prompt user if the repo in the config is invalid 2021-10-01 18:11:53 +01:00
shati-patel
89b00eaef8 Show input box if there's no controller repo defined in settings 2021-10-01 18:11:53 +01:00
shati-patel
4bc5086cfb Update test 2021-10-01 18:11:53 +01:00
shati-patel
7a79d39e23 Add new setting to specify controller repo 2021-10-01 18:11:53 +01:00
shati-patel
41ae5a4b5f Create new "remoteQueries" setting 2021-10-01 18:11:53 +01:00
Musab Guma'a
0493e316c0 Update extensions/ql-vscode/CHANGELOG.md
Co-authored-by: Edoardo Pirovano <6748066+edoardopirovano@users.noreply.github.com>
2021-10-01 17:37:54 +01:00
Musab Guma'a
137e17c2e1 Add fix entry to CHANGELOG.md 2021-10-01 17:37:54 +01:00
Musab Guma'a
31db2ffb82 Fix version copy for missing cli 2021-10-01 17:37:54 +01:00
Aditya Sharad
df18ff3052 Update CLI tests to use 2.6.2 (#955)
* Actions: Update tests to use CLI 2.6.2

* Integration tests: Update to CLI 2.6.2
2021-09-21 22:44:01 +00:00
Benjamin Muskalla
74555510b4 Fix formatting for history format preference
There was a space missing for one of the items, making it not rendered as part of the list
2021-09-16 07:53:23 -07:00
shati-patel
a2b8e7d193 Rename function 2021-09-15 10:52:47 +01:00
shati-patel
b59638bd2e Test the regex for "getRepositories" 2021-09-15 10:52:47 +01:00
shati-patel
b0e19926da Tests for "validateRepositories" 2021-09-15 10:52:47 +01:00
shati-patel
2e1b83588c Put error handling into separate "validateRepositories" function 2021-09-15 10:52:47 +01:00
shati-patel
ab441ef75c Tests for "getRepositories" 2021-09-15 10:52:47 +01:00
shati-patel
b4478e9b54 Remove token for running a remote query 2021-09-09 14:06:29 +01:00
aeisenberg
a715ce13c9 Bump version to v1.5.6 2021-09-08 16:15:15 -07:00
Andrew Eisenberg
005372abba v1.5.5
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
2021-09-08 15:49:18 -07:00
Andrew Eisenberg
3f22587a7c Update changelog 2021-09-08 17:02:49 -04:00
Andrew Eisenberg
b95533e8c0 Remove support for 2.2.6 CLI
This is old enough that we don't need to support it.
2021-09-08 17:02:49 -04:00
Andrew Eisenberg
210d8a3c64 Expand qlpack resolution integration test to all languages
Go is not yet supported since we do not include the go submodule in the
integration tests.
2021-09-08 17:02:49 -04:00
Andrew Eisenberg
c2d3829a72 Fix AST Viewer
The previous synthetic query suite was not finding the ast query because
the `qlpack` directive in a query suite only matches queries from the
default suite, which `printAST.ql` is not part of.

This changes to using `from` and `queries` directives.

Also, adds an integration test to ensure we find the queries using
different CLIs. However, this only tests using the latest `main` from
the codeql repository. I wonder if we should start testing using
different versions of the repo.
2021-09-08 17:02:49 -04:00
Robert
cd427ee119 fix strings again 2021-09-08 18:21:41 +01:00
Robert
ad4c30ecf8 Include clickable link to show logs in message 2021-09-08 18:21:41 +01:00
Robert
db7f5f5114 Add spaces to printed array 2021-09-08 18:21:41 +01:00
Robert
7c9fa03da8 update messages 2021-09-08 18:21:41 +01:00
Robert
615dd691bf offer option to rerun on subset of valid repositories 2021-09-08 18:21:41 +01:00
shati-patel
64ba2cabad Attempt to fix quick query test 2021-09-08 13:02:57 +01:00
shati-patel
a9dcb2d705 Attempt to fix qlpack test 2021-09-08 13:02:57 +01:00
Shati Patel
4c81cdec98 Update CLI version for integration tests 2021-09-08 13:02:57 +01:00
Harry Maclean
db529d5247 Update changelog 2021-09-07 15:58:33 +01:00
Harry Maclean
4f568ea331 Wait for document to be saved before running query
This prevents a race condition where the query runs before the editor has saved the file.
2021-09-07 15:58:33 +01:00
Shati Patel
6d41362251 Configure correct TypeScript version to use in VS Code 2021-09-07 09:38:16 +01:00
Andrew Eisenberg
7f65a54060 Fix dependabot warning 2021-09-03 09:53:59 +01:00
aeisenberg
0c6ca81437 Bump version to v1.5.5 2021-09-02 12:40:04 -07:00
Andrew Eisenberg
b2422216b5 Update changelog for v2.5.4 release
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
2021-09-02 11:51:35 -07:00
Andrew Eisenberg
71f374d797 Fix unit test and add new test
Test that old CLIs properly ignore the library packs.
2021-09-02 11:14:32 -07:00
Dave Bartolomeo
7e78a6bc5c Fix PR feedback 2021-09-02 11:14:32 -07:00
Dave Bartolomeo
a4532fdc61 Add changenote for AST viewer failure fix 2021-09-02 11:14:32 -07:00
Dave Bartolomeo
7c5135d7d0 Fix AST viewer for refactored language packs
Most of the languages have recently been refactored into separate library and query packs, with the contextual queries defined in the query pack. In the near future, these contextual queries will move to the library pack.

Current CLI releases throw an error in `codeql resolve queries` when the extension tries to search the library pack for contextual queries. This change makes two related fixes:

1. If the queries are not found in the library pack, it then scans the corresponding standard query pack as a fallback.
2. It detects the problematic combination of CLI and packs, and avoids scanning the library pack at all in those cases. If no queries are found in the problematic scenario, the error message instructs the user to upgrade to the latest CLI version, instead of claiming that the language simply doesn't support the contextual queries yet.

This change depends on CLI 2.6.1, which is being released soon, adding the `--allow-library-packs` option to `codeql resolve queries`. That PR is already open against the CLI.
2021-09-02 11:14:32 -07:00
shati-patel
cdd6738748 Try using a separate test query 2021-09-02 16:44:22 +01:00
shati-patel
6f16192865 Add test for resolveQueryByLanguage 2021-09-02 16:44:22 +01:00
shati-patel
8151739f87 Update syntax 2021-09-01 11:02:21 +01:00
shati-patel
72fc53ba9c Add "resolveLanguages" test 2021-09-01 11:02:21 +01:00
shati-patel
3e6ee01c4e Move findLanguage function into helpers.ts 2021-09-01 11:02:21 +01:00
Benjamin Muskalla
f6485dac95 Add changelog for sticky pagination controls 2021-08-26 08:14:25 -07:00
Benjamin Muskalla
48f15b5fc7 Stick result pagination to top 2021-08-26 08:14:25 -07:00
shati-patel
f856e3ac2c Address review comments 2021-08-25 09:27:37 +01:00
shati-patel
38a64017f2 New setting to specify number of paths per alert 2021-08-25 09:27:37 +01:00
Andrew Eisenberg
20b15b6e1d Add v2.6.0 to list of versions we use for integration testing 2021-08-24 21:03:27 -07:00
Edoardo Pirovano
e119218828 Update extensions/ql-vscode/CHANGELOG.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2021-08-23 21:30:30 +01:00
Edoardo Pirovano
f494988ba6 Address PR comments from @aeisenberg and @shati-patel 2021-08-23 21:30:30 +01:00
Edoardo Pirovano
2561db1721 Allow exporting of results for non-alert queries 2021-08-23 21:30:30 +01:00
Benjamin Muskalla
089b23f0aa Remove old changelog entry 2021-08-19 13:04:16 +01:00
Benjamin Muskalla
fbed7dd1ca Mention filename pattern in changelog 2021-08-19 13:04:16 +01:00
Benjamin Muskalla
06ef67f22d Add support for filename pattern in history view 2021-08-19 13:04:16 +01:00
shati-patel
3d647f68e1 Bump version to v1.5.4 2021-08-18 16:43:37 +01:00
shati-patel
6a36dc34cc v1.5.3
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
2021-08-18 16:29:41 +01:00
Shati Patel
b48aaeac7b Fix type for "remote repo list" setting 2021-08-18 15:33:26 +01:00
Edoardo Pirovano
2da1065027 PR Checks: Use version of codeql repo suitable for CLI version 2021-08-18 15:25:21 +01:00
shati-patel
3536124fbc Tweak the regex 2021-08-17 16:54:48 +01:00
shati-patel
10b4e08bf8 Validate user input for "owner/repo" 2021-08-17 16:54:48 +01:00
shati-patel
b1f426672c Add option to enter a single repo 2021-08-17 16:54:48 +01:00
shati-patel
087cae287f Add a new "remote repository lists" setting 2021-08-17 16:54:48 +01:00
Eric Kim
3d8032c9b7 Update Changelog 2021-08-17 08:28:25 -07:00
Eric Kim
6470238311 Adjust test-adapter to display diffs only for failing tests 2021-08-17 08:28:25 -07:00
Andrew Eisenberg
0093af8994 Update the CLI versions to run integration tests against 2021-08-09 15:00:01 -07:00
shati-patel
2bfcd119db Don't show empty list of DBs 2021-08-04 20:00:49 +01:00
shati-patel
5932bdba96 Address review comments
- Tweak return types + logging
- Update changelog
2021-08-04 20:00:49 +01:00
shati-patel
1afe6b56fa Autodetect language using "resolve queries"
Also use autodection in relevant places
- When running on multiple databases
- When running a remote query
2021-08-04 20:00:49 +01:00
Andrew Eisenberg
72776e8254 Update the CLI versions to run integration tests against 2021-07-26 19:24:10 +01:00
shati-patel
d2d1a09723 Update changelog 2021-07-16 09:34:45 +01:00
shati-patel
793b82333f Rename variable and tweak error display 2021-07-16 09:34:45 +01:00
shati-patel
b3abff3e88 Add some error handling 2021-07-16 09:34:45 +01:00
shati-patel
890549f9e7 Fix database selection 2021-07-16 09:34:45 +01:00
shati-patel
66825d6a37 Add command for running queries on multiple databases 2021-07-16 09:34:45 +01:00
Andrew Eisenberg
d42982ee4c Fix dependabot errors
Updates the package dependencies to avoid dependabot errors.

I updated the `@types/gulp` to avoid some typings errors that were
introduced by incompatible versions of `@types/undertaker`.

Also, I forced resolution on `"glob-parent": "~6.0.0"` that avoids
a vulnerability on earlier versions.

I did a smoke test of features that use glob, as well as running a few
queries. All looks good.
2021-07-15 20:03:48 -07:00
shati-patel
7df634f050 Bump version to v1.5.3 2021-07-13 18:50:52 +01:00
shati-patel
46606aa7b5 v1.5.2
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
2021-07-13 18:27:28 +01:00
shati-patel
de5704974d Use new endpoint for running remote queries 2021-07-13 18:09:21 +01:00
shati-patel
977b061048 Fix error from "Open Query Results" button 2021-07-13 09:04:00 +01:00
Chuan-kai Lin
560f694f73 Calculate databasesUnderTest with a loop
Currently QLTestAdapter.run() calculates the databases affected by a set of
tests (those databases will be deleted and then reopened after test completion)
using a nested filter-find expression. Which does not work because the
predicate is an async function, so the expression is testing the truthiness of
a Promise instead of the async result.

This commit fixes the problem by implementing the same check with a loop so
that we can invoke the async predicate using await.
2021-07-12 16:00:46 -07:00
shati-patel
7a58d360fd Update changelog 2021-07-12 09:36:10 +01:00
shati-patel
9601d6c140 Render command description as markdown 2021-07-12 09:36:10 +01:00
Edoardo Pirovano
db66184c35 Run tests with nightly CLI 2021-07-02 17:21:03 +01:00
Shati Patel
93e7daea49 Update CLI integration tests with latest version of CLI
CodeQL CLI v2.5.7 is now released 🎉
2021-07-02 15:34:54 +01:00
shati-patel
1a18c6d056 Update changelog 2021-06-25 16:14:12 -07:00
shati-patel
7eb12e0004 Loop through DBs individually, instead of adding multiple DBs in parallel 2021-06-25 16:14:12 -07:00
shati-patel
d3192b7e3b New command to add database source folder to workspace 2021-06-25 16:14:12 -07:00
Shati Patel
e7ab2969d7 Update CLI integration tests with latest version of CLI (#889)
CodeQL CLI v2.5.6 was released yesterday 🎉
2021-06-23 12:06:31 -07:00
Shati Patel
49a35343f6 Run PR checks on "ready_for_review" 2021-06-23 19:53:21 +01:00
shati-patel
c361671e36 Bump version to v1.5.2 2021-06-23 19:28:31 +01:00
shati-patel
b71452b87c v1.5.1
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
2021-06-23 16:11:05 +01:00
Shati Patel
06170f9713 Changes from dev branch (#882)
Two new "canary" commands:
* GitHub authentication (from #874)
* Workflow dispatch (run remote query)
2021-06-23 09:14:42 +01:00
Andrew Eisenberg
920515c071 Add CODEOWNERS 2021-06-17 10:01:31 -07:00
Shati Patel
6a124685bd Don't run on pull requests
I don't think we ever need to run on PRs 🤔
2021-06-15 18:19:32 +01:00
shati-patel
75f76ecd23 Create version bump PRs in draft mode
Currently, the token we use to create these PRs doesn't have sufficient permissions to set off PR checks. Maybe if we create the PR as a draft and have a real person mark the PR as "ready-for-review", this will be enough to start PR checks.
2021-06-15 18:19:32 +01:00
shati-patel
5a0b1b290f Bump version to v1.5.1 2021-06-14 20:23:08 +01:00
shati-patel
472008888c v1.5.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
2021-06-14 20:00:34 +01:00
shati-patel
aa0d844dc1 Add more context in changelog 2021-06-14 18:42:42 +01:00
shati-patel
2523f81640 Update changelog 2021-06-14 18:42:42 +01:00
shati-patel
9e8b1ffd50 Update to VS Code 1.57.0
This version of VS Code has workspace trust enabled by default
2021-06-14 18:42:42 +01:00
shati-patel
06b22511a7 Update to VS Code 1.48.0
partial cherry-pick from `qc-development` branch
2021-06-14 18:42:42 +01:00
shati-patel
61373209ff Use the workspace trust feature 2021-06-14 18:42:42 +01:00
Andrew Eisenberg
b1e28f6b7d Fix running integration tests
The main fix is in `telemetry.ts:213`.
2021-06-11 14:08:25 -07:00
Andrew Eisenberg
1d414bac55 Update linting rules
Add the `@typescript-eslint/no-floating-promises` rule with an allowance
for floating promises if `void` is used.

This increases safety and ensures that we are explicit when we avoid
awaiting a promise. I already caught a few bugish locations.

In general, we don't need to await the results of logging calls.

databases-ui, we were using a deprecated method for removing a
directory. `fs.rmdir` instead of `fs.remove`.
2021-06-11 14:08:25 -07:00
shati-patel
2f3be92a71 Make functions async + other review comments 2021-05-21 21:41:40 +01:00
shati-patel
a8fd6cc0ee Add changelog note 2021-05-21 21:41:40 +01:00
shati-patel
e591236c4e Update tests 2021-05-21 21:41:40 +01:00
shati-patel
41f4e04379 Create custom log directory, if possible
(I haven't got the error handling to work asynchronously, so I stuck with `mkdirSync` for now)
2021-05-21 21:41:40 +01:00
shati-patel
7e27f20e0e Specify custom directory for storing query server logs 2021-05-21 21:41:40 +01:00
Eric Kim
f550cbe98f Increase font size and add margins to empty query message 2021-05-21 12:35:29 -07:00
Eric Kim
5315c16338 Adjust empty query message 2021-05-21 12:35:29 -07:00
Chuan-kai Lin
540cb99de4 Reregister testproj databases around test runs
To deal with the problem of CodeQL tests modifying open testproj databases,
this commit removes open databases from the extension prior to running tests,
and tries to open those databases again after tests finish running.
2021-05-20 16:00:45 -07:00
Eric Kim
3abc8df8fc Update ChangeLog 2021-05-17 19:01:03 -07:00
Eric Kim
ca93f0e84b Add link to language guides for empty query results 2021-05-17 19:01:03 -07:00
Andrew Eisenberg
d9ff5bdca4 Update cli integration tests with new cli version 2021-05-17 12:39:25 -07:00
Andrew Eisenberg
c4b12250ba Update ChangeLog 2021-05-14 08:00:25 -07:00
Andrew Eisenberg
d73f00196b Add version info while downloading 2021-05-14 08:00:25 -07:00
Andrew Eisenberg
6bf616ff4d Fix code scanning errors and dependabot issues
* Log injection errors
* Also, ran `npm audit fix`
2021-05-10 09:39:55 -07:00
Andrew Eisenberg
ff02d1da05 Add extra emphasis in contributing docs 2021-05-06 14:54:48 -07:00
shati-patel
72d57eec6e Bump version to v1.4.9 2021-05-05 10:04:39 -07:00
shati-patel
692e1235e8 v1.4.8
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
2021-05-05 17:41:02 +01:00
Andrew Eisenberg
b69bbf5c5d Update integration test cli versions 2021-04-30 10:11:03 -07:00
Shati Patel
b64284c43e Apply suggestions from code review
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2021-04-29 10:31:51 -07:00
Shati Patel
67eaaadfce Update changelog 2021-04-29 10:31:51 -07:00
Shati Patel
a9545458b9 minor unrelated typo fixes 2021-04-29 10:31:51 -07:00
Shati Patel
3e1b121471 Prompt users to choose a DB language 2021-04-29 10:31:51 -07:00
Shati Patel
28d7a26b5f Fix syntax in CodeQL code scanning workflow 2021-04-28 16:19:24 +01:00
Andrew Eisenberg
1d49ae5b99 Actions: Add permissions block to code scanning workflow (#850) 2021-04-26 17:57:13 +00:00
Andrew Eisenberg
b00826d76a Use the main branch of the codeql action
This commit switches to the bleeding edge, main branch of the
codeql action. This helps us test the action before merging all
of the new changes into main, which occurs roughly once a week.

If there are commits that introduce bugs in codeql-action, then
we will be more likely to catch it before releasing to the world
if we are using it in this extension.
2021-04-26 08:50:42 -07:00
Shati Patel
eab5865a5c Fix conflict in changelog 2021-04-26 07:53:03 -07:00
Shati Patel
0e8cd0d2b1 Update changelog 2021-04-26 07:53:03 -07:00
Shati Patel
8281f408dc Add command to copy version information 2021-04-26 07:53:03 -07:00
Andrew Eisenberg
fce9bbce20 Update changelog 2021-04-23 14:57:28 -07:00
Andrew Eisenberg
dc5efcedba Watch for changes in directory structure
This ensures that directories renamed, added or deleted are
properly checked to see if they contain tests. The test tree
will be correctly updated when any directory changes.s
2021-04-23 14:57:28 -07:00
aeisenberg
f6c67bf696 Bump version to v1.4.8 2021-04-23 17:29:40 +01:00
Andrew Eisenberg
3fce04a24b v1.4.7
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
2021-04-23 08:11:50 -07:00
Henry Mercer
fba8f51d1b Add polyfill for path to fix a bug that prevented the results view from being loaded (#842)
* Add a polyfill for the Node.js path module

Webpack >v5 doesn't include polyfills for core modules from Node.js by
default. Since we use `path` in the results table UI, we need to include
our own polyfill. This commit adds `path-browserify` to the
distributed extension.

As future work, we could move SARIF location rendering into the core
extension so we don't need to use `path.basename` in the UI. This would
allow us to remove the polyfill.

* Add changelog note
2021-04-23 12:53:48 +01:00
aeisenberg
31ee3cb978 Bump version to v1.4.7 2021-04-23 03:57:48 -07:00
Andrew Eisenberg
4d99126994 v1.4.6
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
2021-04-21 11:33:47 -07:00
Henry Mercer
ced34ad704 Add changelog note 2021-04-21 15:43:57 +01:00
Henry Mercer
f5e0011aa1 Forward all query metadata to the queryserver 2021-04-21 15:43:57 +01:00
Andrew Eisenberg
a0b759ecd8 Avoid printing a stack trace when there is no resultsPath
I don't know exactly when this can happen, but a customer has just
shown me a stack trace like this:

```
TypeError: Cannot destructure property 'resultsPath' of 'resultsPaths' as it is undefined.
    at Object.interpretResults (/xxx/.vscode/extensions/github.vscode-codeql-1.4.5/out/query-results.js:120:13)
    at InterfaceManager._getInterpretedResults (/xxx/.vscode/extensions/github.vscode-codeql-1.4.5/out/interface.js:377:45)
    at InterfaceManager.showResultsAsDiagnostics (/xxx/.vscode/extensions/github.vscode-codeql-1.4.5/out/interface.js:447:43)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async InterfaceManager.handleMsgFromView (/xxx/.vscode/extensions/github.vscode-codeql-1.4.5/out/interface.js:151:29)
```

This commit will avoid printing this stack trace and instead print
a more descriptive message to the logs.
2021-04-20 12:55:13 -07:00
Andrew Eisenberg
58cf4db9ee Add v2.5.1 to cli versions in integration test 2021-04-19 13:53:21 -07:00
Henry Mercer
e0c5ae815c Remove commented out code 2021-04-19 08:44:57 -07:00
Andrew Eisenberg
bf5ed193be Avoid opening the results panel on db deletion
Fixes https://github.com/github/vscode-codeql/issues/823
2021-04-19 08:05:27 -07:00
Aditya Sharad
aa60fbc213 Actions: Simplify code scanning workflow
Run only on pushes and PRs against `main`.
2021-04-14 11:58:46 -07:00
Andrew Eisenberg
bdb2feb559 Refactor version constraints
A simple refactoring that simplifies and unifies how we check if a
feature is supported by a specific cli version.
2021-04-13 10:36:54 -07:00
Andrew Eisenberg
5b08fd0df1 Fix CHANGELOG 2021-04-10 11:19:32 -07:00
Andrew Eisenberg
c83dbde20f Add cli version for message 2021-04-09 15:19:47 -07:00
Edoardo Pirovano
e033578cd2 Add feature to jump to the .ql file referenced by a .qlref 2021-04-09 15:19:47 -07:00
Andrew Eisenberg
c082a38b6b Add a canary setting to avoid caching AST viewer queries (#818)
When codeql library developers are working on PrintAST queries, it is
not easy to use the AST Viewer. The AST Viewer caches results so that
multiple calls to view the AST of the same file are nearly
instantaneous.

However, this breaks down if you are changing the actual queries that
perform AST viewing. In this case, you do not want the cache to be
active.

This commit adds an undocumented setting that prevents caching. To
enable, set:

```
"codeQL.isCanary": true,
"codeQL.astViewer.disableCache": true
```

Note that *both* settings must be true for this to work.

This behaviour and all canary behaviour should be documented somewhere.
I will add that later.
2021-04-01 14:12:13 -07:00
Andrew Eisenberg
bdda27703a Ensure snippets.json is copied when packaging the extension 2021-03-31 10:47:48 -07:00
Andrew Eisenberg
36bfb3987e Fix dependabot warnings (#816)
This commit updates to webpack 5 in order to fix some dependabot errors.
Because webpack 5 introduces some breaking changes, this commit also
makes some minor changes to the build code.
2021-03-29 19:46:20 +00:00
Andrew Eisenberg
6d26491243 Avoid displaying error message for @kind table queries
Also, add a unit test for this area.
2021-03-29 08:16:51 -07:00
Edoardo Pirovano
98a2bbbb47 Limit error messages shown in popups to 2 lines 2021-03-28 16:14:55 -07:00
Aditya Sharad
fb6bed6042 Actions: Test against CodeQL CLI 2.5.0 (#812) 2021-03-26 11:31:31 -07:00
github-actions[bot]
df0cc921fd Bump version to v1.4.6 (#805)
* Bump version to v1.4.6

* Update CHANGELOG.md

Co-authored-by: adityasharad <adityasharad@users.noreply.github.com>
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2021-03-23 00:40:39 +00:00
Aditya Sharad
cd7354446b v1.4.5 (#804)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-03-22 17:18:32 -07:00
Alexander Eyers-Taylor
d909f98fcb Fix running tests when ms-python is installed. (#803)
Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2021-03-22 16:54:02 -07:00
Andrew Eisenberg
8c2db75886 Avoid showing an error when query has not @kind metadata (#801)
Fixes #800
2021-03-22 08:03:13 -07:00
Aditya Sharad
73e560e6da Actions: Test against CodeQL 2.4.6
Deliberately keeping 2.4.5 as well, to keep testing enterprise compatibility.
2021-03-19 17:01:58 -07:00
aeisenberg
ada1180468 Bump version to v1.4.5 2021-03-19 15:39:32 -07:00
Shati Patel
d1e70816aa Update pull_request_template.md (#791) 2021-03-19 17:38:56 +00:00
Andrew Eisenberg
df936167d5 v1.4.4
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-03-19 10:24:35 -07:00
Andrew Eisenberg
0327ec358c Update Changelog 2021-03-19 08:58:10 -07:00
Edoardo Pirovano
7a78fca252 Report description for test failure when possible 2021-03-19 08:58:10 -07:00
Edoardo Pirovano
10e86f1835 Add some commonly used QL snippets 2021-03-17 08:43:00 -07:00
Edoardo Pirovano
dbaed3acd5 Implement viewing of query results as a CSV 2021-03-17 08:04:46 -07:00
Edoardo Pirovano
6830bdd28d Add option to pass additional arguments when running tests 2021-03-16 13:45:00 -07:00
Edoardo Pirovano
e316decae1 Implement sorting of query history by name, date, and result count 2021-03-15 11:18:47 -07:00
Marcono1234
a86c1ce69b Use HTTPS for links 2021-03-14 22:58:50 -07:00
Marcono1234
01418cba26 Update Semmle links in extension README 2021-03-14 22:58:50 -07:00
Edoardo Pirovano
35d98f62e8 Limit scope of save cache option. 2021-03-12 08:46:45 -08:00
Edoardo Pirovano
b30121b84c Apply suggestions from code review
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2021-03-12 08:46:45 -08:00
Edoardo Pirovano
fd15217a20 Expand disk cache evaluator options 2021-03-12 08:46:45 -08:00
Shati Patel
1d03702334 Docs: Update Telemetry links 2021-03-09 08:41:52 -08:00
Andrew Eisenberg
c47029e9eb Update cli version used in integration tests 2021-03-08 13:25:24 -08:00
Alexander Eyers-Taylor
5fdfb44c2e Use downgrades when fixing dbscheme mismatches where possible. (#765) 2021-03-04 10:48:12 +00:00
Andrew Eisenberg
6e40478440 Add error message when interpretation fails
One way it can fail is if the SARIF is too large. We explicitly call
out that error because the raw message received from the node runtime
is not very understandable.
2021-03-02 14:03:19 -08:00
Andrew Eisenberg
9e68b4f061 Use codeQL.runningQueries.numberOfThreads to run interpretation
When running `codeql bqrs interpret`, ensure the
`codeQL.runningQueries.numberOfThreads` setting is respected.
2021-03-02 13:47:12 -08:00
Andrew Eisenberg
0f82875b9d Allow raw project slugs for fetching lgtm dbs
The following is now acceptable for fetching the codeql lgtm database:

```
g/github/codeql
```
2021-03-02 11:40:51 -08:00
aeisenberg
fd52f66f6d Bump version to v1.4.4 2021-03-02 10:23:52 -08:00
Henry Mercer
42cfa45d7e Update page size setting description 2021-02-26 15:22:00 +00:00
Andrew Eisenberg
5023f91475 Bump test timeouts
Necessary because we just added some extra waiting
in order to ensure that config listeners have all
fired.
2021-02-22 12:50:39 -08:00
Andrew Eisenberg
48df77f673 v1.4.3 (#761)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-02-22 12:30:14 -08:00
Andrew Eisenberg
839665588f Avoid clobbering quick-query file when re-opened
Only recreate the qlpack.yml file.

Also, add an integration test for quick-query creation.
2021-02-22 12:05:25 -08:00
Andrew Eisenberg
ab31d86a8d Update cli version in integration test
Simplify description of executablePath setting

"This overrides all other CodeQL CLI settings" is a potential source of
confusion, since it suggests the RAM and threads settings may not be
passed to custom CLIs, when this is in fact the case.
2021-02-18 09:05:19 -08:00
Henry Mercer
f2d07729b9 Simplify description of executablePath setting
"This overrides all other CodeQL CLI settings" is a potential source of
confusion, since it suggests the RAM and threads settings may not be
passed to custom CLIs, when this is in fact the case.
2021-02-15 18:39:00 +00:00
Andrew Eisenberg
707cba4ac9 Fix issues with dynamic updating of the version status bar item
1. Wait a few seconds before updating the status bar after a version
   change.
2. Ensure we are watching the correct configuration items for changes.
3. Ensure the cli version is refreshed correctly.
2021-02-12 08:22:59 -08:00
Andrew Eisenberg
6304fe0e30 Update typings for mocha (#752)
* Update typings for mocha

This is includes an update of the lock file to the v2 format. It's a big
change, but not much is happening here. I thought it best to keep it
separate.

* Fix globalSetup/teardown for mocha

Updating the typings for mocha uncovered an error in how we were
registering global setups and teardowns.

When calling `mocha.globalSetup` or `mocha.globalTeardown`, any
previously registered globals are overwritten. The workaround
is to attach globals directly to the internal options object.

This is a requirement because we are now registering globals in
multiple files.

Unfortunately, the typings for mocha do not permit this and I may need
to fix them again.
2021-02-11 16:48:52 -08:00
Andrew Eisenberg
be9084e83e Fix error messages for ast viewers and update caching
This commit does two things:

1. Add more appropriate error messages when asts can't be viewed.
2. Make better use of cached operations for asts. In the past, we were
not actually using cached operations. Each time an ast view request
occurred, we created a new TemplatePrintAstProvider instance. With this
change, we reuse the TemplatePrintAstProvider between calls and ensure
that an AST that is called once is reused on subsequent calls.
2021-02-11 15:34:49 -08:00
Andrew Eisenberg
57d856ff5c Avoid displaying irrelevant error
Problem was misplaced parens. We were not waiting for
the call to `pathExists` to complete before making the call
to `stat` the directory. When the directory does not
exist, then `stat` throws an error.
2021-02-11 13:07:52 -08:00
Andrew Eisenberg
343e9e5466 Convert env.openExternal to a noop for testing
We should not be opening any external links during tests. This is
causing some builds to hang when running on CI.

See https://github.com/github/vscode-codeql/pull/750 for an example.
2021-02-11 12:32:42 -08:00
Andrew Eisenberg
f2620c65af Add disposeHandlers
These functions assist with object disposal. They add custom behaviour
during disposal. The primary usage of disposalHandlers is during testing
where some objects should not be disposed in order to avoid testing
errors.

Additionally, move DisposableObject to the pure folder and create unit
tests for it.

Also, add `--disable-gpu` to command line options when running tests.
It helps to avoid error messages like this:

```- [19141:19141:0425/011526.129520:ERROR:sandbox_linux.cc(374)] InitializeSandbox() called with multiple threads in process gpu-process.```

See also https://askubuntu.com/a/1288969
2021-02-11 12:32:42 -08:00
Andrew Eisenberg
c5fe58db37 Add workflow dispatch 2021-02-11 12:32:42 -08:00
aeisenberg
47b57c01f3 Bump version to v1.4.3 2021-02-02 14:34:19 -08:00
Andrew Eisenberg
27529bfc33 v1.4.2
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-02-02 14:23:49 -08:00
Andrew Eisenberg
0e4ae83e74 ` 2021-02-02 12:38:53 -08:00
Andrew Eisenberg
3b1ff0f4a3 Add a codeql status bar item
Includes the current cli version as well as the
canary status (codeQL.canary) in the settings.
2021-02-02 09:40:59 -08:00
Andrew Eisenberg
5079abd06f Fix version constraint
Non-destructive upgrades only exist in versions >= 2.4.2
2021-02-02 09:17:33 -08:00
aeisenberg
4e94f70e6f Bump version to v1.4.2 2021-01-29 21:45:42 -08:00
Andrew Eisenberg
79e2666586 v1.4.1
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-01-29 21:37:29 -08:00
Andrew Eisenberg
02080cd797 Change text and fix link of modal dialog
Modal dialogs do not allow for markdown text. The link was invalid.
Also, make CodeQL more prominent in the dialog.
2021-01-29 17:46:42 -08:00
aeisenberg
7347ff5512 Bump version to v1.4.1 2021-01-29 16:07:07 -08:00
Andrew Eisenberg
c26217df88 v1.4.0
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-01-29 15:32:50 -08:00
Andrew Eisenberg
31b445c8d2 Remove logic to only create release artifacts on PRs
Create them for appropriately named tags and workflow dispatch as well.
2021-01-29 15:21:12 -08:00
Andrew Eisenberg
7387ef6d2c Fix telemetry recording bug
When someone disables and then re-enables the global telemetry setting,
the telemetry recorder needs to be recreated in order to allow it to
respond to events again.

Also, write the telemetry log item in the same telemetry processor as
is used to remove unused fields. This ensures there is no race condition
on the order of telemetry processors being run. We always log after
fields are removed.
2021-01-29 15:21:12 -08:00
Andrew Eisenberg
091d36b1a0 Tweak telemetry page and changelog 2021-01-29 15:21:12 -08:00
Andrew Eisenberg
292e695646 Add telemetry for commands
This commit adds telemetry capturing for command execution. The data
captured explicitly captured and sent to application insights is only
the command id, execution time, and command completion status. We also
capture errors thrown by any command execution, but these are not sent
to application insights.

Telemetry capturing is opt-in. No data will be sent to application
insights unless the user explicitly allows it.

There are two new config settings added. The first controls whether or
not telemetry should be sent. This setting AND the global telemetry setting
must be enabled in order for telemetry to be sent.

The second setting controls whether or not telemetry event data should
be logged to the extension console. The hope here is that users can
inspect exactly what data is sent to the server and can have confidence
that nothing concerning is being leaked.

Note that the global setting for disabling telemetry collection is
handled inside the  `vscode-extension-telemetry` package implicitly, so
this extension doesn't touch that setting explicitly.

The `codeql.canary` setting is being used to add an additional flag to
telemetry events. This flag will help us determine if a user in internal
or not.

The application insights key is injected at build time through a
repository secret.

This commit also includes a new `TELEMETRY.md` file that explains what
is being captured, and why.
2021-01-29 15:21:12 -08:00
Andrew Eisenberg
f154206b47 Fix invalid property name on message 2021-01-29 11:24:07 -08:00
Andrew Eisenberg
07eb334e6c Ensure databases are re-registered when query server restarts
This commit fixes #733. It does it by ensuring that the query server
emits an event when it restarts the query server. The database manager
listens for this even and properly re-registers its databases.

A few caveats though:

1. Convert query restarts to using a command that includes progress.
   This will ensure that errors on restart are logged properly.
2. Because we want to log errors, we cannot use the vscode standard
   EventEmitters. They run in the next tick and therefore any errors
   will not be associated with this command execution.
3. Update the default cli version to run integration tests against to
   2.4.2.
4. Add a new integration test that fails if databases are not
   re-registered.
2021-01-29 11:24:07 -08:00
alexet
89b86055d7 Use asycy tmp 2021-01-28 16:13:33 -08:00
alexet
4dfec7014c Adress comments 2021-01-28 16:13:33 -08:00
alexet
fbff2df899 Remove unused variable 2021-01-28 16:13:33 -08:00
alexet
9cbe5ba2e8 Simplify query server interface. 2021-01-28 16:13:33 -08:00
alexet
70ddbd05be Adress comments on non-destructive upgrades. 2021-01-28 16:13:33 -08:00
alexet
ace92a4674 Remove uneeded argument 2021-01-28 16:13:33 -08:00
alexet
24b3e158b7 Set codeql version to required version. 2021-01-28 16:13:33 -08:00
alexet
a399041cba Fix rebase conflict 2021-01-28 16:13:33 -08:00
alexet
676546d32b Adress review comments 2021-01-28 16:13:33 -08:00
alexet
a25db9616f QueryServer: Use non-destructive upgrades where possible. 2021-01-28 16:13:33 -08:00
alexet
cb4d6f228b QueryServer: Add new commands to client. 2021-01-28 16:13:33 -08:00
alexet
424884b6b1 Add support for new cli feature 2021-01-28 16:13:33 -08:00
Henry Mercer
f741deb48b Forward scored query metadata property for canary users 2021-01-21 19:36:34 +00:00
Henry Mercer
ae6be79c51 Add config setting to enable canary features 2021-01-21 19:36:34 +00:00
Henry Mercer
154b4a2fe2 Fix missing call to showAndLogErrorMessage 2021-01-21 11:34:30 -08:00
aeisenberg
650f4ca047 Bump version to v1.3.11 2021-01-21 19:06:58 +00:00
Andrew Eisenberg
a7c73cc421 v1.3.10
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
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
2021-01-20 14:15:45 -08:00
Andrew Eisenberg
044bc30d96 Clarify how to run CLI tests locally
Also, remove an errant `only`, which was preventing some tests from
running.
2021-01-20 13:05:53 -08:00
Andrew Eisenberg
9c72e81264 Update changelog 2021-01-20 13:05:53 -08:00
Andrew Eisenberg
3a718ee6e0 Include the full stack in error log messages
Ensure we only show the truncated error message in the popup.

This will help with debugging.
2021-01-20 13:05:53 -08:00
Andrew Eisenberg
540124478b Refactor: Move commandRunner to its own module
Also, extract related functions and types. There are no behavioral
changes in this commit. Only refactorings.
2021-01-19 12:51:12 -08:00
Henry Mercer
6074a1a7c8 Fix minor typo in welcome content
Replace "Code QL database" with "CodeQL database" for consistency with [documentation](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/).
2021-01-19 06:51:18 -08:00
Andrew Eisenberg
093a51cee3 Fix Code Scanning warnings 2021-01-11 15:27:43 -08:00
Andrew Eisenberg
cace4acb1e Update internal docs for publishing
And remove unused file.
2021-01-11 13:38:21 -08:00
Andrew Eisenberg
696c16b5b4 Add workflow jobs to deploy extension
This adds two new jobs to the `Release` workflow. These
jobs are blocked behind an environment. When approved
by a committer, the extension will be deployed to
Open VSX and VS Code marketplace.

Also, update contributing docs for open-vsx publishing.
2021-01-11 13:38:21 -08:00
Andrew Eisenberg
7b439e4511 Make typing more explicit 2021-01-04 08:55:47 -08:00
alexet
402700f56f SingleFileUpgrades: Address comments 2021-01-04 08:55:47 -08:00
alexet
8eaeefb9ea Use single file upgrades where possible. 2021-01-04 08:55:47 -08:00
aeisenberg
49ac9796a1 Bump version to v1.3.9 2020-12-17 11:55:58 -08:00
Andrew Eisenberg
89b6b5a945 v1.3.8
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-12-17 11:52:33 -08:00
Andrew Eisenberg
53ac1ed70d Update changelog
Clarify a few entries.
2020-12-17 11:07:37 -08:00
Andrew Eisenberg
5824e3607a Add View AST Command to text editor where appropriate
Also, hide the `CodeQL: Run Queries` command inside of zip folders
since we do not allow queries to be in archives. I wish we could be more
specific about when to show that command, eg- only *.ql files and
directories, but I couldn't find a way to restrict a command to only
appear on directories.
2020-12-17 11:07:37 -08:00
Andrew Eisenberg
e6eb914783 Add unit tests for query-history 2020-12-16 13:26:09 -08:00
Andrew Eisenberg
b0e032be2c Fix set label command on history items
This removes the cached treeItem that is a property of the
completedQuery. We should not be caching them since they are cached by
the vscode api itself. Instead, we should recreate whenever requested.

Also, this change fixes #598 in a new way. Instead of adding the
context to the cached treeItem, we simply refresh only the item that has
changed. This is a fast operation.
2020-12-16 13:26:09 -08:00
Andrew Eisenberg
3ea3eda8aa Add descriptive text and a link at top of query results
The descriptive text is the same as the label in the query history view.
The link opens the file that ran the query.
2020-12-16 13:18:49 -08:00
Andrew Eisenberg
ca9510c08d Add unit test for test discovery
When directory is not present.
2020-12-14 17:47:49 -08:00
Andrew Eisenberg
303cb3284c Avoid uninteresting user facing errors
This change avoids popping up error messages in two cases:

1. When doing test discovery, do not run discovery on non-existant
   directories. Also, if there is an error, print to the log, and do not
   pop up an error window. The reason is that test discovery is a
   background operation and these should not normally cause pop-ups.
2. When looking for orphaned databases, don't pop up an error if the
   storagePath can't be found. This is normal when working in a new,
   single root workspace.
2020-12-14 17:47:49 -08:00
Andrew Eisenberg
5ad433775b Move query.test.ts to the cli integration tests
* Now query.test.ts runs on multiple cli versions
* Removed most `dispose` calls in cli tests because each test shares the
  same instance of the extension and all of its properties. So, we
  shouldn't be disposing until the last test completes. It's likely that
  we will need to be more careful about cleaning up state between test
  runs, but we haven't hit that yet and this can happen in a later
  commit.
2020-12-14 12:36:46 -08:00
Andrew Eisenberg
69ca0f55ba Re-enable the queries cli test
* Requires that QL_PATH environment variable is set and points to a
  checkout of github/codeql
* Adds the `quiet` flag to the cli. When set, this flag will prevent
  some modal dialogs from disrupting the flow. Currently, we only ensure
  that the upgrades dialog is avoided.
* Update the main.yml workflow to checkout the codeql repo
2020-12-14 12:36:46 -08:00
Andrew Eisenberg
b5e708796d Fix failing test
Also, small change to ensure `qlpackOfDatabase` never returns undefined.
It will either return a value or throw.
2020-12-14 10:20:28 -08:00
Tom Hvitved
2516a62469 Add missing call to getPrimaryDbscheme in qlpackOfDatabase 2020-12-14 10:20:28 -08:00
Andrew Eisenberg
9ffb3a14c7 Save downloaded DB archives to disk before unzipping (#700)
This fixes two classes of DBs that can't be installed directly from
downloading:

1. DBs whose central directories do not align with their file headers.
   We need to download and save the entire archive  before we can read
   the central directory and use that to guide the unzipping.
2. Large DBs require too much memory so can't be downloaded and unzipped
   in a single stream.

We also add proper progress notifications to the download progress
monitor so users are aware of how many more MBs are left to download.

It's not yet possible to do the same for unzipping using the current
unzipper library, since unzipping using the central directory does not
expose a stream.

Co-authored-by: Alexander Eyers-Taylor <alexet@github.com>
2020-12-14 16:26:29 +00:00
dependabot[bot]
51835a2466 Bump ini from 1.3.5 to 1.3.8 in /extensions/ql-vscode
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-13 15:52:35 -08:00
Andrew Eisenberg
b470e41431 Make error messages clearer for some common problems
1. Clicking on query history menu items when nothing is selected. Error
   message is clearer. It would be better to disable when nothing is
   selected, but waiting on
   https://github.com/microsoft/vscode/issues/99767 to be released.
2. Trying to run query with a missing or invalid qlpack has better
   message.
3. Better hover text for "Open query".

Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
2020-12-09 15:35:59 -08:00
Andrew Eisenberg
370dbcbfae Update integration test version numbers
Also, fix a typo and remove comment.
2020-12-04 10:08:15 -08:00
Andrew Eisenberg
6046cf1472 Add integration test for running a query
In order to do this, needed to add a few extra pieces:

* extracted the simple database download so that it only happens
  once and is shared across all tests.
* needed to update mocha to latest version since that has the new API
* But typings isn't updated yet, so submitted a PR into DefinitelyTyped
  for that.
* Added a concept of helper files for test runs. These helper files
  will contain all the shared global setup.

Unfortunately, at this point, we can't run using a language pack since
we would also need to download the the ql repository from somewhere.
2020-12-04 10:08:15 -08:00
Andrew Eisenberg
864041efcb Add integration tests for database fetching 2020-12-04 10:08:15 -08:00
Andrew Eisenberg
16eac45822 Add integration tests with the CLI
This commit adds integration tests that run commands using the CLI. This
change introduces a number of enhancements in order to get there.

1. Augments the index-template.ts file so that it downloads an
appropriate cli version if requested.
2. Adds the ensureCli.ts that performs the download if a a suitable
version is not already installed. See the comments in the file for how
this is done.
3. Changes how run-integration-tests is done so that the directories
run are specified through a cli argument.
4. Updates the main.yml workflow so that it also runs the
cli-integration tests.
5. Takes advantage of the return value of the call to `activate` on the
extension. This allows the integration tests to have access to internal
variables of the extension like the context, cli, and query server.
6. And of course, adds a handful of simple tests that ensure we have a
cli installed of the correct version.
2020-12-04 10:08:15 -08:00
Andrew Eisenberg
06a1fd91e4 Fix typos and augment comments around language labels 2020-12-04 09:36:54 -08:00
Andrew Eisenberg
67e8c86ccc Use codeql resolve database to get language
This commit moves to using codeql resolve database instead of inspecting
the `codeql-database.yml` file.

When the extension starts and if the cli supports it, the extension will
attempt to get the name for any databases that don't yet have a name.
Once a name is searched for once by the cli, it will be cached so we
don't need to rediscover the name again.
2020-12-04 09:36:54 -08:00
Andrew Eisenberg
43ef44ff12 Add a language label next to databases in the UI
This change will only work on databases created by cli >= 2.4.1. In that
version, a new `primaryLanguage` field in the `codeql-database.yml`
file. We use this property as the language.

This change also includes a refactoring of the logic around extracting
database information heuristically based on file location. As much
as possible, it is extracted to the `helpers` module. Also, the
initial quick query text is generated based on the language (if known)
otherwise it falls back to the old style of generation.
2020-12-04 09:36:54 -08:00
Andrew Eisenberg
0d04c5d463 Ensure all tests run (#696)
Accidentally committed `.only`.
2020-12-02 15:57:40 -08:00
Andrew Eisenberg
b6c7837fd7 Pluralize registration message names
And do a version check before adding `--require-db-registration` flag.
2020-12-02 11:49:56 -08:00
Andrew Eisenberg
d76f912903 Add version check for db registration
Database registration is available in versions >= 2.4.1
2020-12-02 11:49:56 -08:00
Andrew Eisenberg
1b4a992182 Update changelog 2020-12-02 11:49:56 -08:00
Andrew Eisenberg
2795184e70 Add support for database registration 2020-12-02 11:49:56 -08:00
Andrew Eisenberg
3c08baf062 Add query running test for computeDefaultStrings flag 2020-12-01 14:31:39 -08:00
Andrew Eisenberg
6afb946200 Update changelog 2020-12-01 14:31:39 -08:00
Andrew Eisenberg
bfe4aa386c Pass computeDefaultStrings to query server when compiling queries 2020-12-01 14:31:39 -08:00
Max Schaefer
f4624f3dbf Fix dubious index check (#692)
* Fix dubious index check

* Add unit tests for add/remove database

In order to do this, needed to move `databases.test.ts` to the
`minimal-workspace` test folder because these tests require that there
be some kind of workspace open in order to check on workspace folders.

Unfortunately, during tests vscode does not allow you to convert from a
single root workspace to multi-root and so several of the workspace
functions needed to be stubbed out.

* Update changelog

Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2020-11-30 11:34:47 -08:00
aeisenberg
1b4d8e303d Bump version to v1.3.8 2020-11-24 14:11:16 -08:00
451 changed files with 133399 additions and 7749 deletions

6
.gitattributes vendored
View File

@@ -18,4 +18,8 @@ yarn.lock merge=binary
# https://mirrors.edge.kernel.org/pub/software/scm/git/docs/gitattributes.html
# suggests that this might interleave lines arbitrarily, but empirically
# it keeps added chunks contiguous
CHANGELOG.md merge=union
CHANGELOG.md merge=union
# Mark some JSON files containing test data as generated so they are not included
# as part of diffs or language statistics.
extensions/ql-vscode/src/stories/remote-queries/data/*.json linguist-generated

View File

@@ -10,7 +10,12 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
**Version**
The CodeQL and VS Code version in which the bug occurs.
<!-- To copy version information for the CodeQL extension, click "CodeQL CLI vX.X.X" in the status bar at the bottom of the screen.
To copy detailed version information for VS Code itself, see https://code.visualstudio.com/docs/supporting/FAQ#_how-do-i-find-the-version. -->
**To reproduce**
Steps to reproduce the behavior.
**Expected behavior**

View File

@@ -1,18 +0,0 @@
---
name: New extension release
about: Create an issue with a checklist for the release steps (write access required
for the steps)
title: Release Checklist for version xx.xx.xx
labels: ''
assignees: ''
---
- [ ] Update this issue title to refer to the version of the release
- [ ] Trigger a release build on Actions by adding a new tag on branch `main` of the format `vxx.xx.xx`
- [ ] Monitor the status of the release build in the `Release` workflow in the Actions tab.
- [ ] Download the VSIX from the draft GitHub release that is created when the release build finishes.
- [ ] Log into the [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage/publishers/github).
- [ ] Click the `...` menu in the CodeQL row and click **Update**.
- [ ] Drag the `.vsix` file you downloaded from the GitHub release into the Marketplace and click **Upload**.
- [ ] Publish the draft GitHub release and confirm the new release is marked as the latest release at https://github.com/github/vscode-codeql/releases.

22
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "extensions/ql-vscode"
schedule:
interval: "weekly"
day: "thursday" # Thursday is arbitrary
labels:
- "Update dependencies"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
- package-ecosystem: "github-actions"
directory: ".github"
schedule:
interval: "weekly"
day: "thursday" # Thursday is arbitrary
labels:
- "Update dependencies"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]

View File

@@ -9,4 +9,4 @@ Replace this with a description of the changes your pull request makes.
- [ ] [CHANGELOG.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md) has been updated to incorporate all user visible changes made by this pull request.
- [ ] Issues have been created for any UI or other user-facing changes made by this pull request.
- [ ] `@github/docs-content-dsp` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
- [ ] _[Maintainers only]_ If this pull request makes user-facing changes that require documentation changes, open a corresponding docs pull request in the [github/codeql](https://github.com/github/codeql/tree/main/docs/codeql/codeql-for-visual-studio-code) repo and add the `ready-for-doc-review` label there.

View File

@@ -2,24 +2,31 @@ name: "Code Scanning - CodeQL"
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 0'
- cron: '21 17 * * 0'
jobs:
codeql:
strategy:
fail-fast: false
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@main
with:
languages: javascript
config-file: ./.github/codeql/codeql-config.yml
tools: latest
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@main

16
.github/workflows/dependency-review.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: 'Dependency Review'
on:
- pull_request
- workflow_dispatch
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v1

View File

@@ -1,6 +1,8 @@
name: Build Extension
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches:
- main
@@ -20,17 +22,19 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '10.18.1'
node-version: '16.14.2'
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
cd extensions/ql-vscode
npm install
shell: bash
- name: Build
working-directory: extensions/ql-vscode
env:
APP_INSIGHTS_KEY: '${{ secrets.APP_INSIGHTS_KEY }}'
run: |
cd extensions/ql-vscode
npm run build
shell: bash
@@ -47,9 +51,26 @@ jobs:
name: vscode-codeql-extension
path: artifacts
find-nightly:
name: Find Nightly Release
runs-on: ubuntu-latest
outputs:
url: ${{ steps.get-url.outputs.nightly-url }}
steps:
- name: Get Nightly Release URL
id: get-url
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
shell: bash
# This workflow step gets an unstable testing version of the CodeQL CLI. It should not be used outside of these tests.
run: |
LATEST=`gh api repos/dsp-testing/codeql-cli-nightlies/releases --jq '.[].tag_name' --method GET --raw-field 'per_page=1'`
echo "::set-output name=nightly-url::https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/$LATEST"
test:
name: Test
runs-on: ${{ matrix.os }}
needs: [find-nightly]
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
@@ -61,57 +82,116 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '10.18.1'
node-version: '16.14.0'
# We have to build the dependencies in `lib` before running any tests.
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
cd extensions/ql-vscode
npm install
shell: bash
- name: Build
working-directory: extensions/ql-vscode
env:
APP_INSIGHTS_KEY: '${{ secrets.APP_INSIGHTS_KEY }}'
run: |
cd extensions/ql-vscode
npm run build
shell: bash
- name: Lint
working-directory: extensions/ql-vscode
run: |
cd extensions/ql-vscode
npm run lint
- name: Install CodeQL
run: |
mkdir codeql-home
curl -L --silent https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql.zip -o codeql-home/codeql.zip
unzip -q -o codeql-home/codeql.zip -d codeql-home
unzip -q -o codeql-home/codeql.zip codeql/codeql.exe -d codeql-home
rm codeql-home/codeql.zip
shell: bash
- name: Run unit tests (Linux)
working-directory: extensions/ql-vscode
if: matrix.os == 'ubuntu-latest'
run: |
cd extensions/ql-vscode
CODEQL_PATH=$GITHUB_WORKSPACE/codeql-home/codeql/codeql npm run test
npm run test
- name: Run unit tests (Windows)
if: matrix.os == 'windows-latest'
working-directory: extensions/ql-vscode
run: |
cd extensions/ql-vscode
$env:CODEQL_PATH=$(Join-Path $env:GITHUB_WORKSPACE -ChildPath 'codeql-home/codeql/codeql.exe')
npm run test
- name: Run integration tests (Linux)
if: matrix.os == 'ubuntu-latest'
working-directory: extensions/ql-vscode
env:
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: |
cd extensions/ql-vscode
sudo apt-get install xvfb
/usr/bin/xvfb-run npm run integration
- name: Run integration tests (Windows)
if: matrix.os == 'windows-latest'
working-directory: extensions/ql-vscode
env:
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
run: |
cd extensions/ql-vscode
npm run integration
cli-test:
name: CLI Test
runs-on: ${{ matrix.os }}
needs: [find-nightly]
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
version: ['v2.6.3', 'v2.7.6', 'v2.8.5', 'v2.9.4', 'v2.10.5', 'v2.11.1', 'nightly']
env:
CLI_VERSION: ${{ matrix.version }}
NIGHTLY_URL: ${{ needs.find-nightly.outputs.url }}
TEST_CODEQL_PATH: '${{ github.workspace }}/codeql'
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16.14.0'
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
npm install
shell: bash
- name: Build
working-directory: extensions/ql-vscode
run: |
npm run build
shell: bash
- name: Decide on ref of CodeQL repo
id: choose-ref
shell: bash
run: |
if [[ "${{ matrix.version }}" == "nightly" ]]
then
REF="codeql-cli/latest"
else
REF="codeql-cli/${{ matrix.version }}"
fi
echo "::set-output name=ref::$REF"
- name: Checkout QL
uses: actions/checkout@v2
with:
repository: github/codeql
ref: ${{ steps.choose-ref.outputs.ref }}
path: codeql
- name: Run CLI tests (Linux)
working-directory: extensions/ql-vscode
if: matrix.os == 'ubuntu-latest'
run: |
/usr/bin/xvfb-run npm run cli-integration
- name: Run CLI tests (Windows)
working-directory: extensions/ql-vscode
if: matrix.os == 'windows-latest'
run: |
npm run cli-integration

View File

@@ -6,10 +6,6 @@
name: Release
on:
pull_request:
paths:
- '**/workflows/release.yml'
workflow_dispatch:
push:
@@ -20,23 +16,25 @@ jobs:
build:
name: Release
runs-on: ubuntu-latest
# TODO Share steps with the main workflow.
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '10.18.1'
node-version: '16.14.2'
- name: Install dependencies
run: |
cd extensions/ql-vscode
npm install
npm ci
shell: bash
- name: Build
env:
APP_INSIGHTS_KEY: '${{ secrets.APP_INSIGHTS_KEY }}'
run: |
echo "APP INSIGHTS KEY LENGTH: ${#APP_INSIGHTS_KEY}"
cd extensions/ql-vscode
npm run build -- --release
shell: bash
@@ -55,9 +53,6 @@ jobs:
REF_NAME="$(echo ${{ github.ref }} | sed -e 's:^refs/tags/::' | sed -e 's:/:-:g')"
echo "::set-output name=ref_name::$REF_NAME"
# Uploading artifacts is not necessary to create a release.
# This is just in case the release itself fails and we want to access the built artifacts from Actions.
# TODO Remove if not useful.
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
@@ -93,6 +88,10 @@ jobs:
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
###
# Do Post release work: version bump and changelog PR
# Only do this if we are running from a PR (ie- this is part of the release process)
# The checkout action does not fetch the main branch.
# Fetch the main branch so that we can base the version bump PR against main.
- name: Fetch main branch
@@ -126,3 +125,41 @@ jobs:
body: This PR was automatically generated by the GitHub Actions release workflow in this repository.
branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
base: main
draft: true
vscode-publish:
name: Publish to VS Code Marketplace
needs: build
environment: publish-vscode-marketplace
runs-on: ubuntu-latest
env:
VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
steps:
- name: Download artifact
uses: actions/download-artifact@v2
with:
name: vscode-codeql-extension
- name: Publish to Registry
run: |
npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix || \
echo "Failed to publish to VS Code Marketplace. \
If this was an authentication problem, please make sure the \
auth token hasn't expired."
open-vsx-publish:
name: Publish to Open VSX Registry
needs: build
environment: publish-open-vsx
runs-on: ubuntu-latest
env:
OPEN_VSX_TOKEN: ${{ secrets.OPEN_VSX_TOKEN }}
steps:
- name: Download artifact
uses: actions/download-artifact@v2
with:
name: vscode-codeql-extension
- name: Publish to Registry
run: |
npx ovsx publish -p $OPEN_VSX_TOKEN *.vsix

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# Generated files
/dist/
out/
build/
server/
node_modules/
gen/

View File

@@ -1,9 +1,9 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"eamodio.tsl-problem-matcher",
"amodio.tsl-problem-matcher",
"dbaeumer.vscode-eslint",
"eternalphane.tsfmt-vscode"
],

84
.vscode/launch.json vendored
View File

@@ -8,9 +8,10 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode"
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
// Add a reference to a workspace to open. Eg-
// "${workspaceRoot}/../vscode-codeql-starter/vscode-codeql-starter.code-workspace"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
@@ -19,6 +20,9 @@
// change to 'true' debug the IDE or Query servers
"IDE_SERVER_JAVA_DEBUG": "false",
"QUERY_SERVER_JAVA_DEBUG": "false",
"CLI_SERVER_JAVA_DEBUG": "false",
// Uncomment to set the JAVA_HOME for the codeql instance to use
// "CODEQL_JAVA_HOME": "/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home"
}
},
{
@@ -31,6 +35,9 @@
"runtimeArgs": [
"--inspect=9229"
],
"env": {
"LANG": "en-US"
},
"args": [
"--exit",
"-u",
@@ -39,9 +46,22 @@
"--diff",
"-r",
"ts-node/register",
"-r",
"test/mocha.setup.js",
"test/pure-tests/**/*.ts"
],
"port": 9229,
"stopOnEntry": false,
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Launch Unit Tests - React (vscode-codeql)",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/extensions/ql-vscode/node_modules/jest/bin/jest.js",
"showAsyncStacks": true,
"cwd": "${workspaceFolder}/extensions/ql-vscode",
"stopOnEntry": false,
"sourceMaps": true,
"console": "integratedTerminal",
@@ -54,9 +74,11 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index"
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index",
"--disable-workspace-trust",
"--disable-extensions",
"--disable-gpu"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
@@ -70,13 +92,63 @@
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/minimal-workspace/index",
"--disable-workspace-trust",
"--disable-extensions",
"--disable-gpu",
"${workspaceRoot}/extensions/ql-vscode/test/data"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
},
{
"name": "Launch Integration Tests - With CLI",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/cli-integration/index",
"--disable-workspace-trust",
"--disable-gpu",
"--disable-extension",
"eamodio.gitlens",
"--disable-extension",
"github.codespaces",
"--disable-extension",
"github.copilot",
"${workspaceRoot}/extensions/ql-vscode/src/vscode-tests/cli-integration/data",
// Uncomment the last line and modify the path to a checked out
// instance of the codeql repository so the libraries are
// available in the workspace for the tests.
// "${workspaceRoot}/../codeql"
],
"env": {
// Optionally, set the version to use for the integration tests.
// Use "nightly" to use the latest nightly build.
// "CLI_VERSION": "2.7.0",
// If CLI_VERSION is set to nightly, set this to the url of the nightly build.
// "NIGHTLY_URL": "some url to grab the nightly build",
// Optionally, add a path to the codeql executable to be used during these tests.
// If not specified, one will be downloaded automatically.
// This option overrides the CLI_VERSION option.
// "CLI_PATH": "${workspaceRoot}/../semmle-code/target/intree/codeql/codeql",
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
},
{
"name": "Launch Storybook",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}/extensions/ql-vscode",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "storybook"]
}
]
}

12
.vscode/settings.json vendored
View File

@@ -22,19 +22,19 @@
"common/temp": true,
"**/.vscode-test": true
},
"typescript.tsdk": "./common/temp/node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"typescript.tsdk": "./extensions/ql-vscode/node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"typescript.enablePromptUseWorkspaceTsdk": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.options": {
// This is necessary so that eslint can properly resolve its plugins
"resolvePluginsRelativeTo": "./extensions/ql-vscode"
},
// This is necessary to ensure that ESLint can find the correct configuration files and plugins.
"eslint.workingDirectories": ["./extensions/ql-vscode"],
"editor.formatOnSave": false,
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
"editor.wordWrapColumn": 100
"editor.wordWrapColumn": 100,
"jest.rootPath": "./extensions/ql-vscode"
}

3
CODEOWNERS Normal file
View File

@@ -0,0 +1,3 @@
**/* @github/codeql-vscode-reviewers
**/remote-queries/ @github/code-scanning-secexp-reviewers
**/variant-analysis/ @github/code-scanning-secexp-reviewers

View File

@@ -25,12 +25,13 @@ Here are a few things you can do that will increase the likelihood of your pull
* Follow the [style guide][style].
* Write tests. Tests that don't require the VS Code API are located [here](extensions/ql-vscode/test). Integration tests that do require the VS Code API are located [here](extensions/ql-vscode/src/vscode-tests).
* Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
* Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
* Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Setting up a local build
Make sure you have a fairly recent version of vscode (>1.32) and are using nodejs
version >=v10.13.0. (Tested on v10.15.1 and v10.16.0).
Make sure you have installed recent versions of vscode, node, and npm. Check the `engines` block in [`package.json`](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/package.json) file for compatible versions. Earlier versions may work, but we no longer test against them.
To automatically switch to the correct version of node, we recommend using [nvm](https://github.com/nvm-sh/nvm), which will pick-up the node version from `.nvmrc`.
### Installing all packages
@@ -57,7 +58,6 @@ We recommend that you keep `npm run watch` running in the backgound and you only
1. on first checkout
2. whenever any of the non-TypeScript resources have changed
3. on any change to files included in the webview
### Installing the extension
@@ -77,11 +77,25 @@ $ vscode/scripts/code-cli.sh --install-extension dist/vscode-codeql-*.vsix # if
You can use VS Code to debug the extension without explicitly installing it. Just open this directory as a workspace in VS Code, and hit `F5` to start a debugging session.
### Running the unit/integration tests
### Storybook
Ensure the `CODEQL_PATH` environment variable is set to point to the `codeql` cli executable.
You can use [Storybook](https://storybook.js.org/) to preview React components outside VSCode. Inside the `extensions/ql-vscode` directory, run:
Outside of vscode, run:
```shell
npm run storybook
```
Your browser should automatically open to the Storybook UI. Stories live in the `src/stories` directory.
Alternatively, you can start Storybook inside of VSCode. There is a VSCode launch configuration for starting Storybook. It can be found in the debug view.
More information about Storybook can be found inside the **Overview** page once you have launched Storybook.
### Running the unit tests and integration tests that do not require a CLI instance
Unit tests and many integration tests do not require a copy of the CodeQL CLI.
Outside of vscode, in the `extensions/ql-vscode` directory, run:
```shell
npm run test && npm run integration
@@ -89,39 +103,89 @@ npm run test && npm run integration
Alternatively, you can run the tests inside of vscode. There are several vscode launch configurations defined that run the unit and integration tests. They can all be found in the debug view.
Only the _With CLI_ tests require a CLI instance to run. See below on how to do that.
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.
### Running the integration tests
You will need to run CLI tests using a task from inside of VS Code called _Launch Integration Tests - With CLI_.
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.
From inside of VSCode, open the `launch.json` file and in the _Launch Integration Tests - With CLI_ task, uncomment the `"${workspaceRoot}/../codeql"` line. If necessary, replace value with a path to your checkout, and then run the task.
## 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.
1. Double-check that the extension `package.json` has 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. 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. Trigger a release build on Actions by adding a new tag on branch `main` named after the release, as above. Note that when you push to upstream, you will need to fully qualify the ref. A command like this will work:
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. Log into the [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage/publishers/github).
1. Click the `...` menu in the CodeQL row and click **Update**.
1. Drag the `.vsix` file you downloaded from the GitHub release into the Marketplace and click **Upload**.
1. Go to the draft GitHub release, click 'Edit', add some summary description, and publish it.
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.
## Resources
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)

View File

@@ -16,7 +16,6 @@ To see what has changed in the last few versions of the extension, see the [Chan
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/github/codeql).
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.
## Project goals and scope
This project will track new feature development in CodeQL and, whenever appropriate, bring that functionality to the Visual Studio Code experience.

View File

@@ -10,7 +10,7 @@ module.exports = {
node: true,
es6: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jest-dom/recommended"],
rules: {
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-unused-vars": [
@@ -22,8 +22,10 @@ module.exports = {
},
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-floating-promises": [ "error", { ignoreVoid: true } ],
"prefer-const": ["warn", { destructuring: "all" }],
indent: "off",
"@typescript-eslint/indent": "off",

View File

@@ -0,0 +1,2 @@
# Storybook requires this option to be set. See https://github.com/storybookjs/storybook/issues/18298
legacy-peer-deps=true

View File

@@ -0,0 +1 @@
v16.14.2

View File

@@ -0,0 +1,19 @@
import type { StorybookConfig } from '@storybook/core-common';
const config: StorybookConfig = {
stories: [
'../src/**/*.stories.mdx',
'../src/**/*.stories.@(js|jsx|ts|tsx)'
],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions'
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5'
}
};
module.exports = config;

View File

@@ -0,0 +1,7 @@
import { addons } from '@storybook/addons';
import { themes } from '@storybook/theming';
addons.setConfig({
theme: themes.dark,
enableShortcuts: false,
});

View File

@@ -0,0 +1,38 @@
import { themes } from '@storybook/theming';
import { action } from '@storybook/addon-actions';
// Allow all stories/components to use Codicons
import '@vscode/codicons/dist/codicon.css';
import '../src/stories/vscode-theme.css';
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
export const parameters = {
// All props starting with `on` will automatically receive an action as a prop
actions: { argTypesRegex: "^on[A-Z].*" },
// All props matching these names will automatically get the correct control
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
// Use a dark theme to be aligned with VSCode
docs: {
theme: themes.dark,
},
backgrounds: {
default: 'dark',
values: [
{
name: 'dark',
value: '#1e1e1e',
},
],
}
};
(window as any).acquireVsCodeApi = () => ({
postMessage: action('post-vscode-message'),
setState: action('set-vscode-state'),
});

View File

@@ -1,5 +1,244 @@
# CodeQL for Visual Studio Code: Changelog
## 1.7.2 - 14 October 2022
- Fix a bug where results created in older versions were thought to be unsuccessful. [#1605](https://github.com/github/vscode-codeql/pull/1605)
## 1.7.1 - 12 October 2022
- Fix a bug where it was not possible to add a database folder if the folder name starts with `db-`. [#1565](https://github.com/github/vscode-codeql/pull/1565)
- Ensure the results view opens in an editor column beside the currently active editor. [#1557](https://github.com/github/vscode-codeql/pull/1557)
## 1.7.0 - 20 September 2022
- Remove ability to download databases from LGTM. [#1467](https://github.com/github/vscode-codeql/pull/1467)
- Remove the ability to manually upgrade databases from the context menu on databases. Databases are non-destructively upgraded automatically so for most users this was not needed. For advanced users this is still available in the Command Palette. [#1501](https://github.com/github/vscode-codeql/pull/1501)
- Always restart the query server after a manual database upgrade. This avoids a bug in the query server where an invalid dbscheme was being retained in memory after an upgrade. [#1519](https://github.com/github/vscode-codeql/pull/1519)
## 1.6.12 - 1 September 2022
- Add ability for users to download databases directly from GitHub. [#1485](https://github.com/github/vscode-codeql/pull/1485)
- Fix a race condition that could cause a failure to open the evaluator log when running a query. [#1490](https://github.com/github/vscode-codeql/pull/1490)
- Fix an error when running a query with an older version of the CodeQL CLI. [#1490](https://github.com/github/vscode-codeql/pull/1490)
## 1.6.11 - 25 August 2022
No user facing changes.
## 1.6.10 - 9 August 2022
No user facing changes.
## 1.6.9 - 20 July 2022
No user facing changes.
## 1.6.8 - 29 June 2022
- Fix a bug where quick queries cannot be compiled if the core libraries are not in the workspace. [#1411](https://github.com/github/vscode-codeql/pull/1411)
- Fix a bug where quick evaluation of library files would display an error message when using CodeQL CLI v2.10.0. [#1412](https://github.com/github/vscode-codeql/pull/1412)
## 1.6.7 - 15 June 2022
- Prints end-of-query evaluator log summaries to the Query Log. [#1349](https://github.com/github/vscode-codeql/pull/1349)
- Be consistent about casing in Query History menu. [#1369](https://github.com/github/vscode-codeql/pull/1369)
- Fix quoting string columns in exported CSV results. [#1379](https://github.com/github/vscode-codeql/pull/1379)
## 1.6.6 - 17 May 2022
No user facing changes.
## 1.6.5 - 25 April 2022
- Re-enable publishing to open-vsx. [#1285](https://github.com/github/vscode-codeql/pull/1285)
## 1.6.4 - 6 April 2022
No user facing changes.
## 1.6.3 - 4 April 2022
- Fix a bug where the AST viewer was not synchronizing its selected node when the editor selection changes. [#1230](https://github.com/github/vscode-codeql/pull/1230)
- Avoid synchronizing the `codeQL.cli.executablePath` setting. [#1252](https://github.com/github/vscode-codeql/pull/1252)
- Open the directory in the finder/explorer (instead of just highlighting it) when running the "Open query directory" command from the query history view. [#1235](https://github.com/github/vscode-codeql/pull/1235)
- Ensure query label in the query history view changes are persisted across restarts. [#1235](https://github.com/github/vscode-codeql/pull/1235)
- Prints end-of-query evaluator log summaries to the Query Server Console. [#1264](https://github.com/github/vscode-codeql/pull/1264)
## 1.6.1 - 17 March 2022
No user facing changes.
## 1.6.0 - 7 March 2022
- Fix a bug where database upgrades could not be resolved if some of the target pack's dependencies are outside of the workspace. [#1138](https://github.com/github/vscode-codeql/pull/1138)
- Open the query server logs for query errors (instead of the extension log). This will make it easier to track down query errors. [#1158](https://github.com/github/vscode-codeql/pull/1158)
- Fix a bug where queries took a long time to run if there are no folders in the workspace. [#1157](https://github.com/github/vscode-codeql/pull/1157)
- [BREAKING CHANGE] The `codeQL.runningQueries.customLogDirectory` setting is deprecated and no longer has any function. Instead, all query log files will be stored in the query history directory, next to the query results. [#1178](https://github.com/github/vscode-codeql/pull/1178)
- Add a _Open query directory_ command for query items. This command opens the directory containing all artifacts for a query. [#1179](https://github.com/github/vscode-codeql/pull/1179)
- Add options to display evaluator logs for a given query run. Some information that was previously found in the query server output may now be found here. [#1186](https://github.com/github/vscode-codeql/pull/1186)
## 1.5.11 - 10 February 2022
- Fix a bug where invoking _View AST_ from the file explorer would not view the selected file. Instead it would view the active editor. Also, prevent the _View AST_ from appearing if the current selection includes a directory or multiple files. [#1113](https://github.com/github/vscode-codeql/pull/1113)
- Add query history items as soon as a query is run, including new icons for each history item. [#1094](https://github.com/github/vscode-codeql/pull/1094)
- Save query history items across restarts. Items will be saved for 30 days and can be overwritten by setting the `codeQL.queryHistory.ttl` configuration setting. [#1130](https://github.com/github/vscode-codeql/pull/1130)
- Allow in-progress query items to be cancelled from the query history view. [#1105](https://github.com/github/vscode-codeql/pull/1105)
## 1.5.10 - 25 January 2022
- Fix a bug where the results view moved column even when it was already visible. [#1070](https://github.com/github/vscode-codeql/pull/1070)
- Add packaging-related commands. _CodeQL: Download Packs_ downloads query packs from the package registry that can be run locally, and _CodeQL: Install Pack Dependencies_ installs dependencies for packs in your workspace. [#1076](https://github.com/github/vscode-codeql/pull/1076)
## 1.5.9 - 17 December 2021
- Avoid creating a third column when opening the results view. The results view will always open to the right of the active editor, unless the active editor is in the rightmost editor column. In that case open in the leftmost column. [#1037](https://github.com/github/vscode-codeql/pull/1037)
- Add a CodeLens to make the Quick Evaluation command more accessible. Click the `Quick Evaluation` prompt above a predicate definition in the editor to evaluate that predicate on its own. You can enable/disable this feature in the `codeQL.runningQueries.quickEvalCodelens` setting. [#1035](https://github.com/github/vscode-codeql/pull/1035) & [#1052](https://github.com/github/vscode-codeql/pull/1052)
- Fix a bug where the _Alerts_ option would show in the results view even if there is no alerts table available. [#1038](https://github.com/github/vscode-codeql/pull/1038)
## 1.5.8 - 2 December 2021
- Emit a more explicit error message when a user tries to add a database with an unzipped source folder to the workspace. [#1021](https://github.com/github/vscode-codeql/pull/1021)
- Ensure `src.zip` archives are used as the canonical source instead of `src` folders when importing databases. [#1025](https://github.com/github/vscode-codeql/pull/1025)
## 1.5.7 - 23 November 2021
- Fix the _CodeQL: Open Referenced File_ command for Windows systems. [#979](https://github.com/github/vscode-codeql/pull/979)
- Support large SARIF results files (>4GB) without crashing VS Code. [#1004](https://github.com/github/vscode-codeql/pull/1004)
- Fix a bug that shows 'Set current database' when hovering over the currently selected database in the databases view. [#976](https://github.com/github/vscode-codeql/pull/976)
- Fix a bug with importing large databases. Databases over 4GB can now be imported directly from LGTM or from a zip file. This functionality is only available when using CodeQL CLI version 2.6.0 or later. [#971](https://github.com/github/vscode-codeql/pull/971)
- Replace certain control codes (`U+0000` - `U+001F`) with their corresponding control labels (`U+2400` - `U+241F`) in the results view. [#963](https://github.com/github/vscode-codeql/pull/963)
- Allow case-insensitive project slugs for GitHub repositories when adding a CodeQL database from LGTM. [#978](https://github.com/github/vscode-codeql/pull/961)
- Add a _CodeQL: Preview Query Help_ command to generate Markdown previews of `.qhelp` query help files. This command should only be run in trusted workspaces. See [the CodeQL CLI docs](https://codeql.github.com/docs/codeql-cli/testing-query-help-files) for more information about query help. [#988](https://github.com/github/vscode-codeql/pull/988)
- Make "Open Referenced File" command accessible from the active editor menu. [#989](https://github.com/github/vscode-codeql/pull/989)
- Fix a bug where result set names in the result set drop-down were disappearing when viewing a sorted table. [#1007](https://github.com/github/vscode-codeql/pull/1007)
- Allow query result locations with 0 as the end column value. These are treated as the first column in the line. [#1002](https://github.com/github/vscode-codeql/pull/1002)
## 1.5.6 - 07 October 2021
- Add progress messages to LGTM download option. This makes the two-step process (selecting a project, then selecting a language) more clear. [#960](https://github.com/github/vscode-codeql/pull/960)
- Remove line about selecting a language from the dropdown when downloading database from LGTM. This makes the download progress visible when the popup is not expanded. [#957](https://github.com/github/vscode-codeql/pull/957)
- Fix a bug where copying the version information fails when a CodeQL CLI cannot be found. [#958](https://github.com/github/vscode-codeql/pull/958)
- Avoid a race condition when deleting databases that can cause occasional errors. [#959](https://github.com/github/vscode-codeql/pull/959)
- Update CodeQL logos. [#965](https://github.com/github/vscode-codeql/pull/965)
## 1.5.5 - 08 September 2021
- Fix bug where a query is sometimes run before the file is saved. [#947](https://github.com/github/vscode-codeql/pull/947)
- Fix broken contextual queries, including _View AST_. [#949](https://github.com/github/vscode-codeql/pull/949)
## 1.5.4 - 02 September 2021
- Add support for filename pattern in history view. [#930](https://github.com/github/vscode-codeql/pull/930)
- Add an option _View Results (CSV)_ to view the results of a non-alert query. The existing options for alert queries have been renamed to _View Alerts_ to avoid confusion. [#929](https://github.com/github/vscode-codeql/pull/929)
- Allow users to specify the number of paths to display for each alert. [#931](https://github.com/github/vscode-codeql/pull/931)
- Adjust pagination controls in _CodeQL Query Results_ to always be visible [#936](https://github.com/github/vscode-codeql/pull/936)
- Fix bug where _View AST_ fails due to recent refactoring in the standard library and query packs. [#939](https://github.com/github/vscode-codeql/pull/939)
## 1.5.3 - 18 August 2021
- Add a command _CodeQL: Run Query on Multiple Databases_, which lets users select multiple databases to run a query on. [#898](https://github.com/github/vscode-codeql/pull/898)
- Autodetect what language a query targets. This refines the _CodeQL: Run Query on Multiple Databases_ command to only show relevant databases. [#915](https://github.com/github/vscode-codeql/pull/915)
- Adjust test log output to display diffs only when comparing failed test results with expected test results. [#920](https://github.com/github/vscode-codeql/pull/920)
## 1.5.2 - 13 July 2021
- Add the _Add Database Source to Workspace_ command to the right-click context menu in the databases view. This lets users re-add a database's source folder to the workspace and browse the source code. [#891](https://github.com/github/vscode-codeql/pull/891)
- Fix markdown rendering in the description of the `codeQL.cli.executablePath` setting. [#908](https://github.com/github/vscode-codeql/pull/908)
- Fix the _Open Query Results_ command in the query history view. [#909](https://github.com/github/vscode-codeql/pull/909)
## 1.5.1 - 23 June 2021
No user facing changes.
## 1.5.0 - 14 June 2021
- Display CodeQL CLI version being downloaded during an upgrade. [#862](https://github.com/github/vscode-codeql/pull/862)
- Display a helpful message and link to documentation when a query produces no results. [#866](https://github.com/github/vscode-codeql/pull/866)
- Refresh test databases automatically after a test run. [#868](https://github.com/github/vscode-codeql/pull/868)
- Allow users to specify a custom directory for storing query server logs (`codeQL.runningQueries.customLogDirectory`). The extension will not delete these logs automatically. [#863](https://github.com/github/vscode-codeql/pull/863)
- Support the VS Code [Workspace Trust feature](https://code.visualstudio.com/docs/editor/workspace-trust). This extension is now enabled in untrusted workspaces, but it restricts commands that contain arbitrary paths. [#861](https://github.com/github/vscode-codeql/pull/861)
- Allow the `codeQL.cli.executablePath` configuration setting to be set in workspace-scoped configuration files. This means that each workspace can now specify its own CodeQL CLI compiler, a feature that is unblocked due to implementing Workspace Trust. [#861](https://github.com/github/vscode-codeql/pull/861)
## 1.4.8 - 05 May 2021
- Copy version information to the clipboard when a user clicks the CodeQL section of the status bar. [#845](https://github.com/github/vscode-codeql/pull/845)
- Ensure changes in directories that contain tests will be properly updated in the test explorer. [#846](https://github.com/github/vscode-codeql/pull/846)
- Remind users to choose a language when downloading a database from LGTM. [#852](https://github.com/github/vscode-codeql/pull/852)
## 1.4.7 - 23 April 2021
- Fix a bug that prevented the results view from being loaded. [#842](https://github.com/github/vscode-codeql/pull/842)
## 1.4.6 - 21 April 2021
- Avoid showing an error popup when running a query with `@kind table` metadata. [#814](https://github.com/github/vscode-codeql/pull/814)
- Add an option to jump from a .qlref file to the .ql file it references. [#815](https://github.com/github/vscode-codeql/pull/815)
- Avoid opening the results panel when a database is deleted. [#831](https://github.com/github/vscode-codeql/pull/831)
- Forward all query metadata to the CLI when interpreting results. [#838](https://github.com/github/vscode-codeql/pull/838)
## 1.4.5 - 22 March 2021
- Avoid showing an error popup when user runs a query without `@kind` metadata. [#801](https://github.com/github/vscode-codeql/pull/801)
- Fix running of tests when the `ms-python` extension is installed. [#803](https://github.com/github/vscode-codeql/pull/803)
## 1.4.4 - 19 March 2021
- Introduce evaluator options for saving intermediate results to the disk cache (`codeQL.runningQueries.saveCache`) and for limiting the size of this cache (`codeQL.runningQueries.cacheSize`). [#778](https://github.com/github/vscode-codeql/pull/778)
- Respect the `codeQL.runningQueries.numberOfThreads` setting when creating SARIF files during result interpretation. [#771](https://github.com/github/vscode-codeql/pull/771)
- Allow using raw LGTM project slugs for fetching LGTM databases. [#769](https://github.com/github/vscode-codeql/pull/769)
- Better error messages when BQRS interpretation fails to produce SARIF. [#770](https://github.com/github/vscode-codeql/pull/770)
- Implement sorting of the query history view by name, date, and results count. [#777](https://github.com/github/vscode-codeql/pull/777)
- Add a configuration option to pass additional arguments to the CLI when running tests. [#785](https://github.com/github/vscode-codeql/pull/785)
- Introduce option to view query results as CSV. [#784](https://github.com/github/vscode-codeql/pull/784)
- Add some snippets for commonly used QL statements. [#782](https://github.com/github/vscode-codeql/pull/782)
- More descriptive error messages on QL test failures. [#788](https://github.com/github/vscode-codeql/pull/788)
## 1.4.3 - 22 February 2021
- Avoid displaying an error when removing orphaned databases and the storage folder does not exist. [#748](https://github.com/github/vscode-codeql/pull/748)
- Add better error messages when AST Viewer is unable to create an AST. [#753](https://github.com/github/vscode-codeql/pull/753)
- Cache AST viewing operations so that subsequent calls to view the AST of a single file will be extremely fast. [#753](https://github.com/github/vscode-codeql/pull/753)
- Ensure CodeQL version in status bar updates correctly when version changes. [#754](https://github.com/github/vscode-codeql/pull/754)
- Avoid deleting the quick query file when it is re-opened. [#747](https://github.com/github/vscode-codeql/pull/747)
## 1.4.2 - 2 February 2021
- Add a status bar item for the CodeQL CLI to show the current version. [#741](https://github.com/github/vscode-codeql/pull/741)
- Fix version constraint for flagging CLI support of non-destructive updates. [#744](https://github.com/github/vscode-codeql/pull/744)
- Add a _More Information_ button in the telemetry popup that opens the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code) in a browser tab. [#742](https://github.com/github/vscode-codeql/pull/742)
## 1.4.1 - 29 January 2021
- Reword the telemetry modal dialog box. [#738](https://github.com/github/vscode-codeql/pull/738)
## 1.4.0 - 29 January 2021
- Fix bug where databases are not reregistered when the query server restarts. [#734](https://github.com/github/vscode-codeql/pull/734)
- Fix bug where upgrade requests were erroneously being marked as failed. [#734](https://github.com/github/vscode-codeql/pull/734)
- On a strictly opt-in basis, collect anonymized usage data from the VS Code extension, helping improve CodeQL's usability and performance. See the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code) for more information on exactly what data is collected and what it is used for. [#611](https://github.com/github/vscode-codeql/pull/611)
## 1.3.10 - 20 January 2021
- Include the full stack in error log messages to help with debugging. [#726](https://github.com/github/vscode-codeql/pull/726)
## 1.3.9 - 12 January 2021
- No changes visible to end users.
## 1.3.8 - 17 December 2020
- Ensure databases are unlocked when removing them from the workspace. This will ensure that after a database is removed from VS Code, queries can be run on it from the command line without restarting the IDE. Requires CodeQL CLI 2.4.1 or later. [#681](https://github.com/github/vscode-codeql/pull/681)
- Fix bug when removing databases where sometimes the source folder would not also be removed from the workspace or the database files would not be deleted from the workspace storage location. [#692](https://github.com/github/vscode-codeql/pull/692)
- Query results with no string representation will now be displayed with placeholder text in query results. Previously, they were omitted. [#694](https://github.com/github/vscode-codeql/pull/694)
- Add a label for the language of a database in the databases view. This will only take effect for new databases created with the CodeQL CLI v2.4.1 or later. [#697](https://github.com/github/vscode-codeql/pull/697)
- Add clearer error message when running a query using a missing or invalid qlpack. [#702](https://github.com/github/vscode-codeql/pull/702)
- Add clearer error message when trying to run a command from the query history view if no item in the history is selected. [#702](https://github.com/github/vscode-codeql/pull/702)
- Fix a bug where it is not possible to download some database archives. This fix specifically addresses large archives and archives whose central directories do not align with file headers. [#700](https://github.com/github/vscode-codeql/pull/700)
- Avoid error dialogs when QL test discovery or database cleanup encounters a missing directory. [#706](https://github.com/github/vscode-codeql/pull/706)
- Add descriptive text and a link in the results view. [#711](https://github.com/github/vscode-codeql/pull/711)
- Fix the _Set Label_ command in the query history view. [#710](https://github.com/github/vscode-codeql/pull/710)
- Add the _CodeQL: View AST_ command to the right-click context menu when a source file in a database source archive is open in the editor. [#712](https://github.com/github/vscode-codeql/pull/712)
## 1.3.7 - 24 November 2020
- Editors opened by navigating from the results view are no longer opened in _preview mode_. Now they are opened as a persistent editor. [#630](https://github.com/github/vscode-codeql/pull/630)

View File

@@ -1,6 +1,6 @@
# CodeQL extension for Visual Studio Code
This project is an extension for Visual Studio Code that adds rich language support for [CodeQL](https://help.semmle.com/codeql) and allows you to easily find problems in codebases. In particular, the extension:
This project is an extension for Visual Studio Code that adds rich language support for [CodeQL](https://codeql.github.com/docs/) and allows you to easily find problems in codebases. In particular, the extension:
- Enables you to use CodeQL to query databases generated from source code.
- Shows the flow of data through the results of path queries, which is essential for triaging security results.
@@ -12,7 +12,7 @@ To see what has changed in the last few versions of the extension, see the [Chan
## Quick start overview
The information in this `README` file describes the quickest way to start using CodeQL.
For information about other configurations, see the separate [CodeQL help](https://help.semmle.com/codeql/codeql-for-vscode.html).
For information about other configurations, see the separate [CodeQL help](https://codeql.github.com/docs/codeql-for-visual-studio-code/).
### Quick start: Installing and configuring the extension
@@ -22,7 +22,7 @@ For information about other configurations, see the separate [CodeQL help](https
### Quick start: Using CodeQL
1. [Import a database from LGTM](#importing-a-database-from-lgtm).
1. [Import a database from GitHub](#importing-a-database-from-github).
1. [Run a query](#running-a-query).
---
@@ -40,9 +40,9 @@ The CodeQL extension requires a minimum of Visual Studio Code 1.39. Older versio
### Checking access to the CodeQL CLI
The extension uses the [CodeQL CLI](https://help.semmle.com/codeql/codeql-cli.html) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command.
The extension uses the [CodeQL CLI](https://codeql.github.com/docs/codeql-cli/) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command.
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#configuring-access-to-the-codeql-cli).
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#configuring-access-to-the-codeql-cli).
If you have any difficulty with CodeQL CLI access, see the **CodeQL Extension Log** in the **Output** view for any error messages.
@@ -52,7 +52,7 @@ When you're working with CodeQL, you need access to the standard CodeQL librarie
Initially, we recommend that you clone and use the ready-to-use [starter workspace](https://github.com/github/vscode-codeql-starter/).
This includes libraries and queries for the main supported languages, with folders set up ready for your custom queries. After cloning the workspace (use `git clone --recursive`), you can use it in the same way as any other VS Code workspace—with the added advantage that you can easily update the CodeQL libraries.
For information about configuring an existing workspace for CodeQL, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html#updating-an-existing-workspace-for-codeql).
For information about configuring an existing workspace for CodeQL, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#updating-an-existing-workspace-for-codeql).
## Upgrading CodeQL standard libraries
@@ -73,18 +73,19 @@ If you're using your own clone of the CodeQL standard libraries, you can do a `g
You can find all the commands contributed by the extension in the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**) by typing `CodeQL`, many of them are also accessible through the interface, and via keyboard shortcuts.
### Importing a database from LGTM
### Importing a database from GitHub
While you can use the [CodeQL CLI to create your own databases](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html), the simplest way to start is by downloading a database from LGTM.com.
While you can use the [CodeQL CLI to create your own databases](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/), the simplest way to start is by downloading a database from GitHub.com.
1. Open [LGTM.com](https://lgtm.com/#explore) in your browser.
1. Search for a project you're interested in, for example [Apache Kafka](https://lgtm.com/projects/g/apache/kafka).
1. Copy the link to that project, for example `https://lgtm.com/projects/g/apache/kafka`.
1. In VS Code, open the Command Palette and choose the **CodeQL: Download Database from LGTM** command.
1. Find a project that you're interested in on GitHub.com, for example [Apache Kafka](https://github.com/apache/kafka).
1. Copy the link to that project, for example `https://github.com/apache/kafka`.
1. In VS Code, open the Command Palette and choose the **CodeQL: Download Database from GitHub** command.
1. Paste the link you copied earlier.
1. Select the language for the database you want to download (only required if the project has databases for multiple languages).
1. Once the CodeQL database has been imported, it is displayed in the Databases view.
For more information, see [Choosing a database](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#choosing-a-database) on codeql.github.com.
### Running a query
The instructions below assume that you're using the CodeQL starter workspace, or that you've added the CodeQL libraries and queries repository to your workspace.
@@ -100,13 +101,17 @@ If there are any problems running a query, a notification is displayed in the bo
## What next?
For more information about the CodeQL extension, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode.html). Otherwise, you could:
For more information about the CodeQL extension, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/). Otherwise, you could:
- [Create a database for a different codebase](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html).
- [Create a database for a different codebase](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/).
- [Try out variant analysis](https://help.semmle.com/QL/learn-ql/ql-training.html).
- [Learn more about CodeQL](https://help.semmle.com/QL/learn-ql/).
- [Learn more about CodeQL](https://codeql.github.com/docs/).
- [Read how security researchers use CodeQL to find CVEs](https://securitylab.github.com/research).
## License
The CodeQL extension for Visual Studio Code is [licensed](LICENSE.md) under the MIT License. The version of CodeQL used by the CodeQL extension is subject to the [GitHub CodeQL Terms & Conditions](https://securitylab.github.com/tools/codeql/license).
## Data and Telemetry
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days. For more information about telemetry, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code).

View File

@@ -0,0 +1,17 @@
import * as gulp from 'gulp';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const replace = require('gulp-replace');
/** Inject the application insights key into the telemetry file */
export function injectAppInsightsKey() {
if (!process.env.APP_INSIGHTS_KEY) {
// noop
console.log('APP_INSIGHTS_KEY environment variable is not set. So, cannot inject it into the application.');
return Promise.resolve();
}
// replace the key
return gulp.src(['out/telemetry.js'])
.pipe(replace(/REPLACE-APP-INSIGHTS-KEY/, process.env.APP_INSIGHTS_KEY))
.pipe(gulp.dest('out/'));
}

View File

@@ -1,5 +1,4 @@
import * as fs from 'fs-extra';
import * as jsonc from 'jsonc-parser';
import * as path from 'path';
export interface DeployedPackage {
@@ -13,6 +12,7 @@ const packageFiles = [
'CHANGELOG.md',
'README.md',
'language-configuration.json',
'snippets.json',
'media',
'node_modules',
'out'
@@ -27,7 +27,7 @@ async function copyPackage(sourcePath: string, destPath: string): Promise<void>
export async function deployPackage(packageJsonPath: string): Promise<DeployedPackage> {
try {
const packageJson: any = jsonc.parse(await fs.readFile(packageJsonPath, 'utf8'));
const packageJson: any = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
// Default to development build; use flag --release to indicate release build.
const isDevBuild = !process.argv.includes('--release');

View File

@@ -1,10 +1,27 @@
import * as gulp from 'gulp';
import { compileTypeScript, watchTypeScript, copyViewCss } from './typescript';
import { compileTypeScript, watchTypeScript, cleanOutput } from './typescript';
import { compileTextMateGrammar } from './textmate';
import { copyTestData } from './tests';
import { compileView } from './webpack';
import { compileView, watchView } from './webpack';
import { packageExtension } from './package';
import { injectAppInsightsKey } from './appInsights';
export const buildWithoutPackage = gulp.parallel(compileTypeScript, compileTextMateGrammar, compileView, copyTestData, copyViewCss);
export { compileTextMateGrammar, watchTypeScript, compileTypeScript };
exports.default = gulp.series(exports.buildWithoutPackage, packageExtension);
export const buildWithoutPackage =
gulp.series(
cleanOutput,
gulp.parallel(
compileTypeScript, compileTextMateGrammar, compileView, copyTestData
)
);
export {
cleanOutput,
compileTextMateGrammar,
watchTypeScript,
watchView,
compileTypeScript,
copyTestData,
injectAppInsightsKey,
compileView,
};
export default gulp.series(buildWithoutPackage, injectAppInsightsKey, packageExtension);

View File

@@ -1,6 +1,17 @@
import * as gulp from 'gulp';
export function copyTestData() {
copyNoWorkspaceData();
copyCliIntegrationData();
return Promise.resolve();
}
function copyNoWorkspaceData() {
return gulp.src('src/vscode-tests/no-workspace/data/**/*')
.pipe(gulp.dest('out/vscode-tests/no-workspace/data'));
}
function copyCliIntegrationData() {
return gulp.src('src/vscode-tests/cli-integration/data/**/*')
.pipe(gulp.dest('out/vscode-tests/cli-integration/data'));
}

View File

@@ -219,14 +219,14 @@ function transformFile(yaml: any) {
}
export function transpileTextMateGrammar() {
return through.obj((file: Vinyl, _encoding: string, callback: Function): void => {
return through.obj((file: Vinyl, _encoding: string, callback: (err: string | null, file: Vinyl | PluginError) => void): void => {
if (file.isNull()) {
callback(null, file);
}
else if (file.isBuffer()) {
const buf: Buffer = file.contents;
const yamlText: string = buf.toString('utf8');
const jsonData: any = jsYaml.safeLoad(yamlText);
const jsonData: any = jsYaml.load(yamlText);
transformFile(jsonData);
file.contents = Buffer.from(JSON.stringify(jsonData, null, 2), 'utf8');

View File

@@ -1,11 +1,11 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"declaration": true,
"strict": true,
"module": "commonjs",
"target": "es2017",
"lib": ["es6"],
"lib": ["ES2021"],
"moduleResolution": "node",
"sourceMap": true,
"rootDir": ".",
@@ -16,7 +16,8 @@
"noImplicitReturns": true,
"experimentalDecorators": true,
"noUnusedLocals": true,
"noUnusedParameters": true
"noUnusedParameters": true,
"esModuleInterop": true
},
"include": ["*.ts"]
}

View File

@@ -2,6 +2,7 @@ import * as colors from 'ansi-colors';
import * as gulp from 'gulp';
import * as sourcemaps from 'gulp-sourcemaps';
import * as ts from 'gulp-typescript';
import * as del from 'del';
function goodReporter(): ts.reporter.Reporter {
return {
@@ -20,6 +21,10 @@ function goodReporter(): ts.reporter.Reporter {
const tsProject = ts.createProject('tsconfig.json');
export function cleanOutput() {
return tsProject.projectDirectory ? del(tsProject.projectDirectory + '/out/*') : Promise.resolve();
}
export function compileTypeScript() {
return tsProject.src()
.pipe(sourcemaps.init())
@@ -34,9 +39,3 @@ export function compileTypeScript() {
export function watchTypeScript() {
gulp.watch('src/**/*.ts', compileTypeScript);
}
/** Copy CSS files for the results view into the output directory. */
export function copyViewCss() {
return gulp.src('src/view/*.css')
.pipe(gulp.dest('out'));
}

View File

@@ -1,11 +1,11 @@
import * as path from 'path';
import * as webpack from 'webpack';
import * as MiniCssExtractPlugin from 'mini-css-extract-plugin';
export const config: webpack.Configuration = {
mode: 'development',
entry: {
resultsView: './src/view/results.tsx',
compareView: './src/compare/view/Compare.tsx',
webview: './src/view/webview.tsx'
},
output: {
path: path.resolve(__dirname, '..', 'out'),
@@ -13,7 +13,10 @@ export const config: webpack.Configuration = {
},
devtool: 'inline-source-map',
resolve: {
extensions: ['.js', '.ts', '.tsx', '.json']
extensions: ['.js', '.ts', '.tsx', '.json'],
fallback: {
path: require.resolve('path-browserify')
}
},
module: {
rules: [
@@ -27,9 +30,7 @@ export const config: webpack.Configuration = {
{
test: /\.less$/,
use: [
{
loader: 'style-loader'
},
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
@@ -49,17 +50,31 @@ export const config: webpack.Configuration = {
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
}
]
},
{
test: /\.(woff(2)?|ttf|eot)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
// We need this to make Webpack use the correct path for the fonts.
// Without this, the CSS file will use `url([object Module])`
esModule: false
}
},
],
}
]
},
performance: {
hints: false
}
},
plugins: [new MiniCssExtractPlugin()],
};

View File

@@ -2,27 +2,50 @@ import * as webpack from 'webpack';
import { config } from './webpack.config';
export function compileView(cb: (err?: Error) => void) {
webpack(config).run((error, stats) => {
doWebpack(config, true, cb);
}
export function watchView(cb: (err?: Error) => void) {
const watchConfig = {
...config,
watch: true,
watchOptions: {
aggregateTimeout: 200,
poll: 1000,
}
};
doWebpack(watchConfig, false, cb);
}
function doWebpack(internalConfig: webpack.Configuration, failOnError: boolean, cb: (err?: Error) => void) {
const resultCb = (error: Error | undefined, stats?: webpack.Stats) => {
if (error) {
cb(error);
}
console.log(stats.toString({
errorDetails: true,
colors: true,
assets: false,
builtAt: false,
version: false,
hash: false,
entrypoints: false,
timings: false,
modules: false,
errors: true
}));
if (stats.hasErrors()) {
cb(new Error('Compilation errors detected.'));
return;
if (stats) {
console.log(stats.toString({
errorDetails: true,
colors: true,
assets: false,
builtAt: false,
version: false,
hash: false,
entrypoints: false,
timings: false,
modules: false,
errors: true
}));
if (stats.hasErrors()) {
if (failOnError) {
cb(new Error('Compilation errors detected.'));
return;
} else {
console.error('Compilation errors detected.');
}
}
cb();
}
};
cb();
});
webpack(internalConfig, resultCb);
}

View File

@@ -0,0 +1,214 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/6m/1394pht172qgd7dmw1fwjk100000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
// clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
moduleFileExtensions: [
'js',
'mjs',
'cjs',
'jsx',
'ts',
'tsx',
'json'
],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
'moduleNameMapper': {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/__mocks__/fileMock.ts',
'\\.(css|less)$': '<rootDir>/test/__mocks__/styleMock.ts'
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['<rootDir>/test/jest.setup.ts'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
testMatch: [
'**/__tests__/**/*.[jt]s?(x)'
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: 'src/view/tsconfig.spec.json',
},
],
'node_modules': [
'babel-jest',
{
presets: [
'@babel/preset-env'
],
plugins: [
'@babel/plugin-transform-modules-commonjs',
]
}
]
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
'transformIgnorePatterns': [
// These use ES modules, so need to be transformed
'node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6)/.*)'
],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,4 @@
<!-- From https://github.com/microsoft/vscode-icons -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97553 0C3.57186 0 0 3.57186 0 7.97553C0 11.4985 2.29969 14.4832 5.43119 15.5596C5.82263 15.6086 5.96942 15.3639 5.96942 15.1682C5.96942 14.9725 5.96942 14.4832 5.96942 13.7982C3.76758 14.2875 3.27829 12.7217 3.27829 12.7217C2.93578 11.792 2.39755 11.5474 2.39755 11.5474C1.66361 11.0581 2.44648 11.0581 2.44648 11.0581C3.22936 11.107 3.66972 11.8899 3.66972 11.8899C4.40367 13.1131 5.52905 12.7706 5.96942 12.5749C6.01835 12.0367 6.263 11.6942 6.45872 11.4985C4.69725 11.3028 2.83792 10.6177 2.83792 7.53517C2.83792 6.65443 3.1315 5.96942 3.66972 5.38226C3.62079 5.23547 3.32722 4.40367 3.76758 3.32722C3.76758 3.32722 4.4526 3.1315 5.96942 4.15902C6.6055 3.9633 7.29052 3.91437 7.97553 3.91437C8.66055 3.91437 9.34557 4.01223 9.98165 4.15902C11.4985 3.1315 12.1835 3.32722 12.1835 3.32722C12.6239 4.40367 12.3303 5.23547 12.2813 5.43119C12.7706 5.96942 13.1131 6.70336 13.1131 7.5841C13.1131 10.6667 11.2538 11.3028 9.49235 11.4985C9.78593 11.7431 10.0306 12.2324 10.0306 12.9664C10.0306 14.0428 10.0306 14.8746 10.0306 15.1682C10.0306 15.3639 10.1774 15.6086 10.5688 15.5596C13.7492 14.4832 16 11.4985 16 7.97553C15.9511 3.57186 12.3792 0 7.97553 0Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="none"
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
<g>
<g>
<g>
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" fill="#C5C5C5"/>
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" fill="#C5C5C5" />
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" fill="#C5C5C5"/>
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" fill="#C5C5C5"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 12.1952C15.5 12.9126 14.9137 13.4996 14.1957 13.4996H1.80435C1.08696 13.4996 0.5 12.9126 0.5 12.1952L0.5 9.80435C0.5 9.08696 1.08696 8.5 1.80435 8.5H14.1956C14.9137 8.5 15.5 9.08696 15.5 9.80435L15.5 12.1952Z" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.45654 11.5H13.5435" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 9.5C13.224 9.5 13 9.725 13 10C13 10.275 13.224 10.5 13.5 10.5C13.776 10.5 14 10.275 14 10C14 9.725 13.776 9.5 13.5 9.5" fill="#959DA5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 9.5C11.224 9.5 11 9.725 11 10C11 10.275 11.224 10.5 11.5 10.5C11.776 10.5 12 10.275 12 10C12 9.725 11.776 9.5 11.5 9.5" fill="#959DA5"/>
<path d="M15.5 9.81464L13.8728 2.76261C13.6922 2.06804 12.9572 1.5 12.2391 1.5H3.76087C3.04348 1.5 2.30848 2.06804 2.12783 2.76261L0.5 9.8" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,16 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7.5" cy="7.5" r="7" stroke="#959DA5"/>
<mask id="mask0_394_2982" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="15" height="15">
<circle cx="7.5" cy="7.5" r="7.5" fill="#C4C4C4"/>
</mask>
<g mask="url(#mask0_394_2982)">
<path d="M14.5 7.5C14.5 9.42971 13.6822 11.1907 12.5493 12.4721C11.4035 13.7683 10.0054 14.5 8.90625 14.5C7.84644 14.5 6.81131 13.8113 6.01569 12.5383C5.22447 11.2724 4.71875 9.49235 4.71875 7.5C4.71875 5.50765 5.22447 3.72765 6.01569 2.4617C6.81131 1.1887 7.84644 0.5 8.90625 0.5C10.0054 0.5 11.4035 1.23172 12.5493 2.52786C13.6822 3.80934 14.5 5.57029 14.5 7.5Z" stroke="#959DA5"/>
</g>
<mask id="mask1_394_2982" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="0" width="16" height="15">
<circle cx="9.375" cy="7.5" r="7.5" fill="#C4C4C4"/>
</mask>
<g mask="url(#mask1_394_2982)">
<path d="M10.2812 7.5C10.2812 9.49235 9.77553 11.2724 8.98431 12.5383C8.18869 13.8113 7.15356 14.5 6.09375 14.5C4.99456 14.5 3.5965 13.7683 2.45067 12.4721C1.31781 11.1907 0.5 9.42971 0.5 7.5C0.5 5.57029 1.31781 3.80934 2.45067 2.52786C3.5965 1.23172 4.99456 0.5 6.09375 0.5C7.15356 0.5 8.18869 1.1887 8.98431 2.4617C9.77553 3.72765 10.2812 5.50765 10.2812 7.5Z" stroke="#959DA5"/>
</g>
<line y1="7.5" x2="15" y2="7.5" stroke="#959DA5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,11 @@
<!-- From https://github.com/microsoft/vscode-icons -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97578 0C3.57211 0 0.000244141 3.57186 0.000244141 7.97553C0.000244141 11.4985 2.29994 14.4832 5.43144 15.5596C5.82287 15.6086 5.96966 15.3639 5.96966 15.1682C5.96966 14.9725 5.96966 14.4832 5.96966 13.7982C3.76783 14.2875 3.27853 12.7217 3.27853 12.7217C2.93602 11.792 2.3978 11.5474 2.3978 11.5474C1.66385 11.0581 2.44673 11.0581 2.44673 11.0581C3.2296 11.107 3.66997 11.8899 3.66997 11.8899C4.40391 13.1131 5.5293 12.7706 5.96966 12.5749C6.01859 12.0367 6.26324 11.6942 6.45896 11.4985C4.69749 11.3028 2.83816 10.6177 2.83816 7.53517C2.83816 6.65443 3.13174 5.96942 3.66997 5.38226C3.62104 5.23547 3.32746 4.40367 3.76783 3.32722C3.76783 3.32722 4.45284 3.1315 5.96966 4.15902C6.60575 3.9633 7.29076 3.91437 7.97578 3.91437C8.66079 3.91437 9.34581 4.01223 9.98189 4.15902C11.4987 3.1315 12.1837 3.32722 12.1837 3.32722C12.6241 4.40367 12.3305 5.23547 12.2816 5.43119C12.7709 5.96942 13.1134 6.70336 13.1134 7.5841C13.1134 10.6667 11.2541 11.3028 9.4926 11.4985C9.78618 11.7431 10.0308 12.2324 10.0308 12.9664C10.0308 14.0428 10.0308 14.8746 10.0308 15.1682C10.0308 15.3639 10.1776 15.6086 10.5691 15.5596C13.7495 14.4832 16.0002 11.4985 16.0002 7.97553C15.9513 3.57186 12.3794 0 7.97578 0Z" fill="#424242"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white" transform="translate(0.000244141)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
<g>
<g>
<g>
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" />
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" />
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" />
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,14 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="27px" height="16px" viewBox="0 0 27 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59 (86127) - https://sketch.com -->
<title>Slice</title>
<desc>Created with Sketch.</desc>
<g id="light" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="QL" transform="translate(1.000000, 1.000000)">
<rect id="Rectangle-41" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" x="0" y="0" width="25" height="14" rx="2"></rect>
<line x1="17" y1="5" x2="19" y2="5" id="Stroke-15" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></line>
<line x1="17" y1="9" x2="21" y2="9" id="Stroke-15" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></line>
<path d="M8.85227273,7 C8.85227273,7.51894199 8.76988719,7.97537682 8.60511364,8.36931818 C8.44034009,8.76325955 8.21591051,9.08711994 7.93181818,9.34090909 L8.76420455,10.3863636 L7.61647727,10.3863636 L7.14772727,9.80965909 C6.83143781,9.92897787 6.49147909,9.98863636 6.12784091,9.98863636 C5.61079287,9.98863636 5.14678236,9.8712133 4.73579545,9.63636364 C4.32480855,9.40151398 4.00000119,9.06108178 3.76136364,8.61505682 C3.52272608,8.16903186 3.40340909,7.63068497 3.40340909,7 C3.40340909,6.36552713 3.52272608,5.8257598 3.76136364,5.38068182 C4.00000119,4.93560384 4.32480855,4.59611859 4.73579545,4.36221591 C5.14678236,4.12831322 5.61079287,4.01136364 6.12784091,4.01136364 C6.642995,4.01136364 7.10605855,4.12831322 7.51704545,4.36221591 C7.92803236,4.59611859 8.2533132,4.93560384 8.49289773,5.38068182 C8.73248226,5.8257598 8.85227273,6.36552713 8.85227273,7 Z M5.70170455,7.88636364 L6.74715909,7.88636364 L7.17897727,8.44034091 C7.31344764,8.27935526 7.41808675,8.07859969 7.49289773,7.83806818 C7.56770871,7.59753668 7.60511364,7.31818341 7.60511364,7 C7.60511364,6.38257267 7.47064528,5.91145996 7.20170455,5.58664773 C6.93276381,5.2618355 6.57481284,5.09943182 6.12784091,5.09943182 C5.68086898,5.09943182 5.32291801,5.2618355 5.05397727,5.58664773 C4.78503653,5.91145996 4.65056818,6.38257267 4.65056818,7 C4.65056818,7.61553338 4.78503653,8.08617261 5.05397727,8.41193182 C5.32291801,8.73769102 5.68086898,8.90056818 6.12784091,8.90056818 C6.23958389,8.90056818 6.34564344,8.89015162 6.44602273,8.86931818 L5.70170455,7.88636364 Z M10.1813315,10 L10.1813315,4 L11.4114451,4 L11.4114451,8.98579545 L13.9057633,8.98579545 L13.9057633,10 L10.1813315,10 Z" fill="#2088FF" fill-rule="nonzero"></path>
</g>
</g>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.19789 8C8.19789 8.51894 8.1155 8.97538 7.95073 9.36932C7.78595 9.76326 7.56152 10.0871 7.27743 10.3409L8.10982 11.3864H6.96209L6.49334 10.8097C6.17705 10.929 5.83709 10.9886 5.47346 10.9886C4.95641 10.9886 4.4924 10.8712 4.08141 10.6364C3.67042 10.4015 3.34562 10.0611 3.10698 9.61506C2.86834 9.16903 2.74902 8.63068 2.74902 8C2.74902 7.36553 2.86834 6.82576 3.10698 6.38068C3.34562 5.9356 3.67042 5.59612 4.08141 5.36222C4.4924 5.12831 4.95641 5.01136 5.47346 5.01136C5.98861 5.01136 6.45167 5.12831 6.86266 5.36222C7.27365 5.59612 7.59893 5.9356 7.83851 6.38068C8.0781 6.82576 8.19789 7.36553 8.19789 8ZM5.04732 8.88636H6.09277L6.52459 9.44034C6.65906 9.27936 6.7637 9.0786 6.83851 8.83807C6.91332 8.59754 6.95073 8.31818 6.95073 8C6.95073 7.38257 6.81626 6.91146 6.54732 6.58665C6.27838 6.26184 5.92043 6.09943 5.47346 6.09943C5.02648 6.09943 4.66853 6.26184 4.39959 6.58665C4.13065 6.91146 3.99618 7.38257 3.99618 8C3.99618 8.61553 4.13065 9.08617 4.39959 9.41193C4.66853 9.73769 5.02648 9.90057 5.47346 9.90057C5.5852 9.90057 5.69126 9.89015 5.79164 9.86932L5.04732 8.88636ZM9.52695 11V5H10.7571V9.9858H13.2514V11H9.52695Z" fill="#24292F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 1.5H3C2.17157 1.5 1.5 2.17157 1.5 3V13C1.5 13.8284 2.17157 14.5 3 14.5H13C13.8284 14.5 14.5 13.8284 14.5 13V3C14.5 2.17157 13.8284 1.5 13 1.5ZM3 0C1.34315 0 0 1.34315 0 3V13C0 14.6569 1.34315 16 3 16H13C14.6569 16 16 14.6569 16 13V3C16 1.34315 14.6569 0 13 0H3Z" fill="#24292F"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
if grep -rq --include '*.test.ts' 'it.only\|describe.only' './test' './src'; then
echo 'There is a .only() in the tests. Please remove it.'
exit 1;
else
exit 0;
fi

View File

@@ -0,0 +1,134 @@
{
"Query Metadata": {
"prefix": "querymetadata",
"body": [
"/**",
" * @name $1",
" * @description $2",
" * @kind $3",
" * @id $4",
" * @tags $5",
" */"
],
"description": "Metadata for a query"
},
"Class": {
"prefix": "class",
"body": ["class $1 extends $2 {", "\t$0", "}"],
"description": "A class"
},
"From/Where/Select": {
"prefix": "from",
"body": ["from $1", "where $2", "select $3"],
"description": "A from/where/select statement"
},
"Predicate": {
"prefix": "predicate",
"body": ["predicate $1($2) {", "\t$0", "}"],
"description": "A predicate"
},
"Dataflow Tracking Class": {
"prefix": "dataflowtracking",
"body": [
"class $1 extends DataFlow::Configuration {",
"\t$1() { this = \"$1\" }",
"\t",
"\toverride predicate isSource(DataFlow::Node node) {",
"\t\t${2:none()}",
"\t}",
"\t",
"\toverride predicate isSink(DataFlow::Node node) {",
"\t\t${3:none()}",
"\t}",
"}"
],
"description": "Boilerplate for a dataflow tracking class"
},
"Taint Tracking Class": {
"prefix": "tainttracking",
"body": [
"class $1 extends TaintTracking::Configuration {",
"\t$1() { this = \"$1\" }",
"\t",
"\toverride predicate isSource(DataFlow::Node node) {",
"\t\t${2:none()}",
"\t}",
"\t",
"\toverride predicate isSink(DataFlow::Node node) {",
"\t\t${3:none()}",
"\t}",
"}"
],
"description": "Boilerplate for a taint tracking class"
},
"Count": {
"prefix": "count",
"body": ["count($1 | $2 | $3)"],
"description": "A count aggregate"
},
"Max": {
"prefix": "max",
"body": ["max($1 | $2 | $3)"],
"description": "A max aggregate"
},
"Min": {
"prefix": "min",
"body": ["min($1 | $2 | $3)"],
"description": "A min aggregate"
},
"Average": {
"prefix": "avg",
"body": ["avg($1 | $2 | $3)"],
"description": "An average aggregate"
},
"Sum": {
"prefix": "sum",
"body": ["sum($1 | $2 | $3)"],
"description": "A sum aggregate"
},
"Concatenation": {
"prefix": "concat",
"body": ["concat($1 | $2 | $3)"],
"description": "A concatenation aggregate"
},
"Rank": {
"prefix": "rank",
"body": ["rank[$1]($2 | $3 | $4)"],
"description": "A rank aggregate"
},
"Strict Sum": {
"prefix": "strictsum",
"body": ["strictsum($1 | $2 | $3)"],
"description": "A strict sum aggregate"
},
"Strict Concatenation": {
"prefix": "strictconcat",
"body": ["strictconcat($1 | $2 | $3)"],
"description": "A strict concatenation aggregate"
},
"Strict Count": {
"prefix": "strictcount",
"body": ["strictcount($1 | $2 | $3)"],
"description": "A strict count aggregate"
},
"Unique": {
"prefix": "unique",
"body": ["unique($1 | $2 | $3)"],
"description": "A unique aggregate"
},
"Exists": {
"prefix": "exists",
"body": ["exists($1 | $2 | $3)"],
"description": "An exists quantifier"
},
"For All": {
"prefix": "forall",
"body": ["forall($1 | $2 | $3)"],
"description": "A for all quantifier"
},
"For All and Exists": {
"prefix": "forex",
"body": ["forex($1 | $2 | $3)"],
"description": "A for all and exists quantifier"
}
}

View File

@@ -0,0 +1,129 @@
import {
WebviewPanel,
ExtensionContext,
window as Window,
ViewColumn,
Uri,
WebviewPanelOptions,
WebviewOptions
} from 'vscode';
import * as path from 'path';
import { DisposableObject } from './pure/disposable-object';
import { tmpDir } from './helpers';
import { getHtmlForWebview, WebviewMessage, WebviewView } from './interface-utils';
export type WebviewPanelConfig = {
viewId: string;
title: string;
viewColumn: ViewColumn;
view: WebviewView;
preserveFocus?: boolean;
additionalOptions?: WebviewPanelOptions & WebviewOptions;
}
export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMessage extends WebviewMessage> extends DisposableObject {
protected panel: WebviewPanel | undefined;
protected panelLoaded = false;
protected panelLoadedCallBacks: (() => void)[] = [];
constructor(
protected readonly ctx: ExtensionContext
) {
super();
}
public async restoreView(panel: WebviewPanel): Promise<void> {
this.panel = panel;
this.setupPanel(panel);
}
protected get isShowingPanel() {
return !!this.panel;
}
protected getPanel(): WebviewPanel {
if (this.panel == undefined) {
const { ctx } = this;
const config = this.getPanelConfig();
this.panel = Window.createWebviewPanel(
config.viewId,
config.title,
{ viewColumn: config.viewColumn, preserveFocus: config.preserveFocus },
{
enableScripts: true,
enableFindWidget: true,
retainContextWhenHidden: true,
...config.additionalOptions,
localResourceRoots: [
...(config.additionalOptions?.localResourceRoots ?? []),
Uri.file(tmpDir.name),
Uri.file(path.join(ctx.extensionPath, 'out'))
],
}
);
this.setupPanel(this.panel);
}
return this.panel;
}
protected setupPanel(panel: WebviewPanel): void {
const config = this.getPanelConfig();
this.push(
panel.onDidDispose(
() => {
this.panel = undefined;
this.panelLoaded = false;
this.onPanelDispose();
},
null,
this.ctx.subscriptions
)
);
panel.webview.html = getHtmlForWebview(
this.ctx,
panel.webview,
config.view,
{
allowInlineStyles: true,
}
);
this.push(
panel.webview.onDidReceiveMessage(
async (e) => this.onMessage(e),
undefined,
this.ctx.subscriptions
)
);
}
protected abstract getPanelConfig(): WebviewPanelConfig;
protected abstract onPanelDispose(): void;
protected abstract onMessage(msg: FromMessage): Promise<void>;
protected waitForPanelLoaded(): Promise<void> {
return new Promise((resolve) => {
if (this.panelLoaded) {
resolve();
} else {
this.panelLoadedCallBacks.push(resolve);
}
});
}
protected onWebViewLoaded(): void {
this.panelLoaded = true;
this.panelLoadedCallBacks.forEach((cb) => cb());
this.panelLoadedCallBacks = [];
}
protected postMessage(msg: ToMessage): Thenable<boolean> {
return this.getPanel().webview.postMessage(msg);
}
}

View File

@@ -0,0 +1,15 @@
/**
* The d3 library is designed to work in both the browser and
* node. Consequently their typings files refer to both node
* types like `Buffer` (which don't exist in the browser), and browser
* types like `Blob` (which don't exist in node). Instead of sticking
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
* stub definition of the affected types so that compilation
* succeeds.
*/
declare type RequestInit = Record<string, unknown>;
declare type ElementTagNameMap = any;
declare type NodeListOf<T> = Record<string, T>;
declare type Node = Record<string, unknown>;
declare type XMLDocument = Record<string, unknown>;

View File

@@ -115,7 +115,7 @@ class InvalidSourceArchiveUriError extends Error {
export function decodeSourceArchiveUri(uri: vscode.Uri): ZipFileReference {
if (!uri.authority) {
// Uri is malformed, but this is recoverable
logger.log(`Warning: ${new InvalidSourceArchiveUriError(uri).message}`);
void logger.log(`Warning: ${new InvalidSourceArchiveUriError(uri).message}`);
return {
pathWithinSourceArchive: '/',
sourceArchiveZipPath: uri.path
@@ -141,7 +141,7 @@ function ensureFile(map: DirectoryHierarchyMap, file: string) {
const dirname = path.dirname(file);
if (dirname === '.') {
const error = `Ill-formed path ${file} in zip archive (expected absolute path)`;
logger.log(error);
void logger.log(error);
throw new Error(error);
}
ensureDir(map, dirname);
@@ -167,21 +167,26 @@ type Archive = {
dirMap: DirectoryHierarchyMap;
};
async function parse_zip(zipPath: string): Promise<Archive> {
if (!await fs.pathExists(zipPath))
throw vscode.FileSystemError.FileNotFound(zipPath);
const archive: Archive = { unzipped: await unzipper.Open.file(zipPath), dirMap: new Map };
archive.unzipped.files.forEach(f => { ensureFile(archive.dirMap, path.resolve('/', f.path)); });
return archive;
}
export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
private readOnlyError = vscode.FileSystemError.NoPermissions('write operation attempted, but source archive filesystem is readonly');
private archives: Map<string, Archive> = new Map;
private archives: Map<string, Promise<Archive>> = new Map;
private async getArchive(zipPath: string): Promise<Archive> {
if (!this.archives.has(zipPath)) {
if (!await fs.pathExists(zipPath))
throw vscode.FileSystemError.FileNotFound(zipPath);
const archive: Archive = { unzipped: await unzipper.Open.file(zipPath), dirMap: new Map };
archive.unzipped.files.forEach(f => { ensureFile(archive.dirMap, path.resolve('/', f.path)); });
this.archives.set(zipPath, archive);
this.archives.set(zipPath, parse_zip(zipPath));
}
return this.archives.get(zipPath)!;
return await this.archives.get(zipPath)!;
}
root = new Directory('');
// metadata

View File

@@ -10,7 +10,8 @@ import {
TextEditorSelectionChangeEvent,
TextEditorSelectionChangeKind,
Location,
Range
Range,
Uri
} from 'vscode';
import * as path from 'path';
@@ -18,8 +19,9 @@ import { DatabaseItem } from './databases';
import { UrlValue, BqrsId } from './pure/bqrs-cli-types';
import { showLocation } from './interface-utils';
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './pure/bqrs-utils';
import { commandRunner } from './helpers';
import { DisposableObject } from './vscode-utils/disposable-object';
import { commandRunner } from './commandRunner';
import { DisposableObject } from './pure/disposable-object';
import { showAndLogErrorMessage } from './helpers';
export interface AstItem {
id: BqrsId;
@@ -40,7 +42,7 @@ class AstViewerDataProvider extends DisposableObject implements TreeDataProvider
public db: DatabaseItem | undefined;
private _onDidChangeTreeData =
new EventEmitter<AstItem | undefined>();
this.push(new EventEmitter<AstItem | undefined>());
readonly onDidChangeTreeData: Event<AstItem | undefined> =
this._onDidChangeTreeData.event;
@@ -55,7 +57,7 @@ class AstViewerDataProvider extends DisposableObject implements TreeDataProvider
}
refresh(): void {
this._onDidChangeTreeData.fire();
this._onDidChangeTreeData.fire(undefined);
}
getChildren(item?: AstItem): ProviderResult<AstItem[]> {
const children = item ? item.children : this.roots;
@@ -103,7 +105,7 @@ class AstViewerDataProvider extends DisposableObject implements TreeDataProvider
export class AstViewer extends DisposableObject {
private treeView: TreeView<AstItem>;
private treeDataProvider: AstViewerDataProvider;
private currentFile: string | undefined;
private currentFileUri: Uri | undefined;
constructor() {
super();
@@ -124,13 +126,18 @@ export class AstViewer extends DisposableObject {
this.push(window.onDidChangeTextEditorSelection(this.updateTreeSelection, this));
}
updateRoots(roots: AstItem[], db: DatabaseItem, fileName: string) {
updateRoots(roots: AstItem[], db: DatabaseItem, fileUri: Uri) {
this.treeDataProvider.roots = roots;
this.treeDataProvider.db = db;
this.treeDataProvider.refresh();
this.treeView.message = `AST for ${path.basename(fileName)}`;
this.treeView.reveal(roots[0], { focus: false });
this.currentFile = fileName;
this.treeView.message = `AST for ${path.basename(fileUri.fsPath)}`;
this.currentFileUri = fileUri;
// Handle error on reveal. This could happen if
// the tree view is disposed during the reveal.
this.treeView.reveal(roots[0], { focus: false })?.then(
() => { /**/ },
err => showAndLogErrorMessage(err)
);
}
private updateTreeSelection(e: TextEditorSelectionChangeEvent) {
@@ -168,7 +175,7 @@ export class AstViewer extends DisposableObject {
if (
this.treeView.visible &&
e.textEditor.document.uri.fsPath === this.currentFile &&
e.textEditor.document.uri.fsPath === this.currentFileUri?.fsPath &&
e.selections.length === 1
) {
const selection = e.selections[0];
@@ -178,7 +185,12 @@ export class AstViewer extends DisposableObject {
const targetItem = findBest(range, this.treeDataProvider.roots);
if (targetItem) {
this.treeView.reveal(targetItem);
// Handle error on reveal. This could happen if
// the tree view is disposed during the reveal.
this.treeView.reveal(targetItem)?.then(
() => { /**/ },
err => showAndLogErrorMessage(err)
);
}
}
}
@@ -188,6 +200,6 @@ export class AstViewer extends DisposableObject {
this.treeDataProvider.db = undefined;
this.treeDataProvider.refresh();
this.treeView.message = undefined;
this.currentFile = undefined;
this.currentFileUri = undefined;
}
}

View File

@@ -0,0 +1,103 @@
import * as vscode from 'vscode';
import * as Octokit from '@octokit/rest';
import { retry } from '@octokit/plugin-retry';
const GITHUB_AUTH_PROVIDER_ID = 'github';
// We need 'repo' scope for triggering workflows and 'gist' scope for exporting results to Gist.
// For a comprehensive list of scopes, see:
// https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps
const SCOPES = ['repo', 'gist'];
/**
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
*/
export class Credentials {
private octokit: Octokit.Octokit | undefined;
// Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class
// without also initializing the class.
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() { }
/**
* Initializes an instance of credentials with an octokit instance.
*
* Do not call this method until you know you actually need an instance of credentials.
* since calling this method will require the user to log in.
*
* @param context The extension context.
* @returns An instance of credentials.
*/
static async initialize(context: vscode.ExtensionContext): Promise<Credentials> {
const c = new Credentials();
c.registerListeners(context);
c.octokit = await c.createOctokit(false);
return c;
}
/**
* Initializes an instance of credentials with an octokit instance using
* a token from the user's GitHub account. This method is meant to be
* used non-interactive environments such as tests.
*
* @param overrideToken The GitHub token to use for authentication.
* @returns An instance of credentials.
*/
static async initializeWithToken(overrideToken: string) {
const c = new Credentials();
c.octokit = await c.createOctokit(false, overrideToken);
return c;
}
private async createOctokit(createIfNone: boolean, overrideToken?: string): Promise<Octokit.Octokit | undefined> {
if (overrideToken) {
return new Octokit.Octokit({ auth: overrideToken, retry });
}
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone });
if (session) {
return new Octokit.Octokit({
auth: session.accessToken,
retry
});
} else {
return undefined;
}
}
registerListeners(context: vscode.ExtensionContext): void {
// Sessions are changed when a user logs in or logs out.
context.subscriptions.push(vscode.authentication.onDidChangeSessions(async e => {
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
this.octokit = await this.createOctokit(false);
}
}));
}
/**
* Creates or returns an instance of Octokit.
*
* @param requireAuthentication Whether the Octokit instance needs to be authenticated as user.
* @returns An instance of Octokit.
*/
async getOctokit(requireAuthentication = true): Promise<Octokit.Octokit> {
if (this.octokit) {
return this.octokit;
}
this.octokit = await this.createOctokit(requireAuthentication);
if (!this.octokit) {
if (requireAuthentication) {
throw new Error('Did not initialize Octokit.');
}
// We don't want to set this in this.octokit because that would prevent
// authenticating when requireCredentials is true.
return new Octokit.Octokit({ retry });
}
return this.octokit;
}
}

View File

@@ -1,11 +0,0 @@
/**
* The npm library jszip is designed to work in both the browser and
* node. Consequently its typings @types/jszip refers to both node
* types like `Buffer` (which don't exist in the browser), and browser
* types like `Blob` (which don't exist in node). Instead of sticking
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
* stub definition of the type `Blob` here so that compilation
* succeeds.
*/
declare type Blob = string;

View File

@@ -1,6 +1,7 @@
import * as semver from 'semver';
import { runCodeQlCliCommand } from './cli';
import { Logger } from './logging';
import { getErrorMessage } from './pure/helpers-pure';
/**
* Get the version of a CodeQL CLI.
@@ -18,7 +19,7 @@ export async function getCodeQlCliVersion(codeQlPath: string, logger: Logger): P
} catch (e) {
// Failed to run the version command. This might happen if the cli version is _really_ old, or it is corrupted.
// Either way, we can't determine compatibility.
logger.log(`Failed to run 'codeql version'. Reason: ${e.message}`);
void logger.log(`Failed to run 'codeql version'. Reason: ${getErrorMessage(e)}`);
return undefined;
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/camelcase */
import * as cpp from 'child-process-promise';
import * as child_process from 'child_process';
import * as fs from 'fs-extra';
@@ -9,21 +8,28 @@ import { Readable } from 'stream';
import { StringDecoder } from 'string_decoder';
import * as tk from 'tree-kill';
import { promisify } from 'util';
import { CancellationToken, Disposable } from 'vscode';
import { CancellationToken, commands, Disposable, Uri } from 'vscode';
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
import { CliConfig } from './config';
import { allowCanaryQueryServer, CliConfig } from './config';
import { DistributionProvider, FindDistributionResultKind } from './distribution';
import { assertNever } from './pure/helpers-pure';
import { assertNever, getErrorMessage, getErrorStack } from './pure/helpers-pure';
import { QueryMetadata, SortDirection } from './pure/interface-types';
import { Logger, ProgressReporter } from './logging';
import { CompilationMessage } from './pure/messages';
import { CompilationMessage } from './pure/legacy-messages';
import { sarifParser } from './sarif-parser';
import { dbSchemeToLanguage, walkDirectory } from './helpers';
/**
* The version of the SARIF format that we are using.
*/
const SARIF_FORMAT = 'sarifv2.1.0';
/**
* The string used to specify CSV format.
*/
const CSV_FORMAT = 'csv';
/**
* Flags to pass to all cli commands.
*/
@@ -39,6 +45,16 @@ export interface QuerySetup {
compilationCache?: string;
}
/**
* The expected output of `codeql resolve queries --format bylanguage`.
*/
export interface QueryInfoByLanguage {
// Using `unknown` as a placeholder. For now, the value is only ever an empty object.
byLanguage: Record<string, Record<string, unknown>>;
noDeclaredLanguage: Record<string, unknown>;
multipleDeclaredLanguages: Record<string, unknown>;
}
/**
* The expected output of `codeql resolve database`.
*/
@@ -50,6 +66,7 @@ export interface DbInfo {
sourceArchiveRoot: string;
datasetFolder: string;
logsFolder: string;
languages: string[];
}
/**
@@ -58,6 +75,7 @@ export interface DbInfo {
export interface UpgradesInfo {
scripts: string[];
finalDbscheme: string;
matchesTarget?: boolean;
}
/**
@@ -65,6 +83,25 @@ export interface UpgradesInfo {
*/
export type QlpacksInfo = { [name: string]: string[] };
/**
* The expected output of `codeql resolve languages`.
*/
export type LanguagesInfo = { [name: string]: string[] };
/** Information about an ML model, as resolved by `codeql resolve ml-models`. */
export type MlModelInfo = {
checksum: string;
path: string;
};
/** The expected output of `codeql resolve ml-models`. */
export type MlModelsInfo = { models: MlModelInfo[] };
/**
* The expected output of `codeql resolve qlref`.
*/
export type QlrefInfo = { resolvedPath: string };
// `codeql bqrs interpret` requires both of these to be present or
// both absent.
export interface SourceInfo {
@@ -96,6 +133,8 @@ export interface TestCompleted {
evaluationMs: number;
expected: string;
diff: string[] | undefined;
failureDescription?: string;
failureStage?: string;
}
/**
@@ -118,10 +157,6 @@ interface BqrsDecodeOptions {
*/
export class CodeQLCliServer implements Disposable {
/**
* CLI version where --kind=DIL was introduced
*/
private static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0');
/** The process for the cli server, or undefined if one doesn't exist yet */
process?: child_process.ChildProcessWithoutNullStreams;
@@ -133,15 +168,27 @@ export class CodeQLCliServer implements Disposable {
nullBuffer: Buffer;
/** Version of current cli, lazily computed by the `getVersion()` method */
_version: SemVer | undefined;
private _version: Promise<SemVer> | undefined;
/**
* The languages supported by the current version of the CLI, computed by `getSupportedLanguages()`.
*/
private _supportedLanguages: string[] | undefined;
/** Path to current codeQL executable, or undefined if not running yet. */
codeQlPath: string | undefined;
cliConstraints = new CliVersionConstraint(this);
/**
* When set to true, ignore some modal popups and assume user has clicked "yes".
*/
public quiet = false;
constructor(
private distributionProvider: DistributionProvider,
private cliConfig: CliConfig,
private logger: Logger,
private logger: Logger
) {
this.commandQueue = [];
this.commandInProcess = false;
@@ -149,12 +196,15 @@ export class CodeQLCliServer implements Disposable {
if (this.distributionProvider.onDidChangeDistribution) {
this.distributionProvider.onDidChangeDistribution(() => {
this.restartCliServer();
this._version = undefined;
this._supportedLanguages = undefined;
});
}
if (this.cliConfig.onDidChangeConfiguration) {
this.cliConfig.onDidChangeConfiguration(() => {
this.restartCliServer();
this._version = undefined;
this._supportedLanguages = undefined;
});
}
}
@@ -166,15 +216,15 @@ export class CodeQLCliServer implements Disposable {
killProcessIfRunning(): void {
if (this.process) {
// Tell the Java CLI server process to shut down.
this.logger.log('Sending shutdown request');
void this.logger.log('Sending shutdown request');
try {
this.process.stdin.write(JSON.stringify(['shutdown']), 'utf8');
this.process.stdin.write(this.nullBuffer);
this.logger.log('Sent shutdown request');
void this.logger.log('Sent shutdown request');
} catch (e) {
// We are probably fine here, the process has already closed stdin.
this.logger.log(`Shutdown request failed: process stdin may have already closed. The error was ${e}`);
this.logger.log('Stopping the process anyway.');
void this.logger.log(`Shutdown request failed: process stdin may have already closed. The error was ${e}`);
void this.logger.log('Stopping the process anyway.');
}
// Close the stdin and stdout streams.
// This is important on Windows where the child process may not die cleanly.
@@ -190,7 +240,7 @@ export class CodeQLCliServer implements Disposable {
/**
* Restart the server when the current command terminates
*/
private restartCliServer(): void {
restartCliServer(): void {
const callback = (): void => {
try {
this.killProcessIfRunning();
@@ -225,11 +275,16 @@ export class CodeQLCliServer implements Disposable {
*/
private async launchProcess(): Promise<child_process.ChildProcessWithoutNullStreams> {
const codeQlPath = await this.getCodeQlPath();
const args = [];
if (shouldDebugCliServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y');
}
return await spawnServer(
codeQlPath,
'CodeQL CLI Server',
['execute', 'cli-server'],
[],
args,
this.logger,
_data => { /**/ }
);
@@ -254,9 +309,9 @@ export class CodeQLCliServer implements Disposable {
// Compute the full args array
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
const argsString = args.join(' ');
this.logger.log(`${description} using CodeQL CLI: ${argsString}...`);
void this.logger.log(`${description} using CodeQL CLI: ${argsString}...`);
try {
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
// Start listening to stdout
process.stdout.addListener('data', (newData: Buffer) => {
stdoutBuffers.push(newData);
@@ -281,7 +336,7 @@ export class CodeQLCliServer implements Disposable {
const fullBuffer = Buffer.concat(stdoutBuffers);
// Make sure we remove the terminator;
const data = fullBuffer.toString('utf8', 0, fullBuffer.length - 1);
this.logger.log('CLI command succeeded.');
void this.logger.log('CLI command succeeded.');
return data;
} catch (err) {
// Kill the process if it isn't already dead.
@@ -291,10 +346,10 @@ export class CodeQLCliServer implements Disposable {
stderrBuffers.length == 0
? new Error(`${description} failed: ${err}`)
: new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString('utf8')}`);
newError.stack += (err.stack || '');
newError.stack += getErrorStack(err);
throw newError;
} finally {
this.logger.log(Buffer.concat(stderrBuffers).toString('utf8'));
void this.logger.log(Buffer.concat(stderrBuffers).toString('utf8'));
// Remove the listeners we set up.
process.stdout.removeAllListeners('data');
process.stderr.removeAllListeners('data');
@@ -349,12 +404,12 @@ export class CodeQLCliServer implements Disposable {
try {
if (cancellationToken !== undefined) {
cancellationRegistration = cancellationToken.onCancellationRequested(_e => {
tk(child.pid);
tk(child.pid || 0);
});
}
if (logger !== undefined) {
// The human-readable output goes to stderr.
logStream(child.stderr!, logger);
void logStream(child.stderr!, logger);
}
for await (const event of await splitStreamAtSeparators(child.stdout!, ['\0'])) {
@@ -393,7 +448,7 @@ export class CodeQLCliServer implements Disposable {
try {
yield JSON.parse(event) as EventType;
} catch (err) {
throw new Error(`Parsing output of ${description} failed: ${err.stderr || err}`);
throw new Error(`Parsing output of ${description} failed: ${(err as any).stderr || getErrorMessage(err)}`);
}
}
}
@@ -435,17 +490,20 @@ export class CodeQLCliServer implements Disposable {
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
* @param commandArgs The arguments to pass to the `codeql` command.
* @param description Description of the action being run, to be shown in log and error messages.
* @param addFormat Whether or not to add commandline arguments to specify the format as JSON.
* @param progressReporter Used to output progress messages, e.g. to the status bar.
* @returns The contents of the command's stdout, if the command succeeded.
*/
async runJsonCodeQlCliCommand<OutputType>(command: string[], commandArgs: string[], description: string, progressReporter?: ProgressReporter): Promise<OutputType> {
// Add format argument first, in case commandArgs contains positional parameters.
const args = ['--format', 'json'].concat(commandArgs);
async runJsonCodeQlCliCommand<OutputType>(command: string[], commandArgs: string[], description: string, addFormat = true, progressReporter?: ProgressReporter): Promise<OutputType> {
let args: string[] = [];
if (addFormat) // Add format argument first, in case commandArgs contains positional parameters.
args = args.concat(['--format', 'json']);
args = args.concat(commandArgs);
const result = await this.runCodeQlCliCommand(command, args, description, progressReporter);
try {
return JSON.parse(result) as OutputType;
} catch (err) {
throw new Error(`Parsing output of ${description} failed: ${err.stderr || err}`);
throw new Error(`Parsing output of ${description} failed: ${(err as any).stderr || getErrorMessage(err)}`);
}
}
@@ -457,12 +515,24 @@ export class CodeQLCliServer implements Disposable {
async resolveLibraryPath(workspaces: string[], queryPath: string): Promise<QuerySetup> {
const subcommandArgs = [
'--query', queryPath,
'--additional-packs',
workspaces.join(path.delimiter)
...this.getAdditionalPacksArg(workspaces)
];
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
}
/**
* Resolves the language for a query.
* @param queryUri The URI of the query
*/
async resolveQueryByLanguage(workspaces: string[], queryUri: Uri): Promise<QueryInfoByLanguage> {
const subcommandArgs = [
'--format', 'bylanguage',
queryUri.fsPath,
...this.getAdditionalPacksArg(workspaces)
];
return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));
}
/**
* Finds all available QL tests in a given directory.
* @param testPath Root of directory tree to search for tests.
@@ -479,6 +549,29 @@ export class CodeQLCliServer implements Disposable {
);
}
public async resolveQlref(qlref: string): Promise<QlrefInfo> {
const subcommandArgs = [
qlref
];
return await this.runJsonCodeQlCliCommand<QlrefInfo>(
['resolve', 'qlref'],
subcommandArgs,
'Resolving qlref',
false
);
}
/**
* Issues an internal clear-cache command to the cli server. This
* command is used to clear the qlpack cache of the server.
*
* This cache is generally cleared every 1s. This method is used
* to force an early clearing of the cache.
*/
public async clearCache(): Promise<void> {
await this.runCodeQlCliCommand(['clear-cache'], [], 'Clearing qlpack cache');
}
/**
* Runs QL tests.
* @param testPaths Full paths of the tests to run.
@@ -489,12 +582,12 @@ export class CodeQLCliServer implements Disposable {
testPaths: string[], workspaces: string[], options: TestRunOptions
): AsyncGenerator<TestCompleted, void, unknown> {
const subcommandArgs = [
'--additional-packs', workspaces.join(path.delimiter),
const subcommandArgs = this.cliConfig.additionalTestArguments.concat([
...this.getAdditionalPacksArg(workspaces),
'--threads',
this.cliConfig.numberTestThreads.toString(),
...testPaths
];
]);
for await (const event of await this.runAsyncCodeQlCliCommand<TestCompleted>(['test', 'run'],
subcommandArgs, 'Run CodeQL Tests', options.cancellationToken, options.logger)) {
@@ -510,6 +603,20 @@ export class CodeQLCliServer implements Disposable {
return await this.runJsonCodeQlCliCommand<QueryMetadata>(['resolve', 'metadata'], [queryPath], 'Resolving query metadata');
}
/** Resolves the ML models that should be available when evaluating a query. */
async resolveMlModels(additionalPacks: string[], queryPath: string): Promise<MlModelsInfo> {
const args = await this.cliConstraints.supportsPreciseResolveMlModels()
// use the dirname of the path so that we can handle query libraries
? [...this.getAdditionalPacksArg(additionalPacks), path.dirname(queryPath)]
: this.getAdditionalPacksArg(additionalPacks);
return await this.runJsonCodeQlCliCommand<MlModelsInfo>(
['resolve', 'ml-models'],
args,
'Resolving ML models',
false
);
}
/**
* Gets the RAM setting for the query server.
* @param queryMemoryMb The maximum amount of RAM to use, in MB.
@@ -523,7 +630,7 @@ export class CodeQLCliServer implements Disposable {
if (queryMemoryMb !== undefined) {
args.push('--ram', queryMemoryMb.toString());
}
return await this.runJsonCodeQlCliCommand<string[]>(['resolve', 'ram'], args, 'Resolving RAM settings', progressReporter);
return await this.runJsonCodeQlCliCommand<string[]>(['resolve', 'ram'], args, 'Resolving RAM settings', true, progressReporter);
}
/**
* Gets the headers (and optionally pagination info) of a bqrs.
@@ -539,6 +646,67 @@ export class CodeQLCliServer implements Disposable {
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');
}
async databaseUnbundle(archivePath: string, target: string, name?: string): Promise<string> {
const subcommandArgs = [];
if (target) subcommandArgs.push('--target', target);
if (name) subcommandArgs.push('--name', name);
subcommandArgs.push(archivePath);
return await this.runCodeQlCliCommand(['database', 'unbundle'], subcommandArgs, `Extracting ${archivePath} to directory ${target}`);
}
/**
* Uses a .qhelp file to generate Query Help documentation in a specified format.
* @param pathToQhelp The path to the .qhelp file
* @param format The format in which the query help should be generated {@link https://codeql.github.com/docs/codeql-cli/manual/generate-query-help/#cmdoption-codeql-generate-query-help-format}
* @param outputDirectory The output directory for the generated file
*/
async generateQueryHelp(pathToQhelp: string, outputDirectory?: string): Promise<string> {
const subcommandArgs = ['--format=markdown'];
if (outputDirectory) subcommandArgs.push('--output', outputDirectory);
subcommandArgs.push(pathToQhelp);
return await this.runCodeQlCliCommand(['generate', 'query-help'], subcommandArgs, `Generating qhelp in markdown format at ${outputDirectory}`);
}
/**
* Generate a summary of an evaluation log.
* @param endSummaryPath The path to write only the end of query part of the human-readable summary to.
* @param inputPath The path of an evaluation event log.
* @param outputPath The path to write a human-readable summary of it to.
*/
async generateLogSummary(
inputPath: string,
outputPath: string,
endSummaryPath: string,
): Promise<string> {
const subcommandArgs = [
'--format=text',
`--end-summary=${endSummaryPath}`,
...(await this.cliConstraints.supportsSourceMap() ? ['--sourcemap'] : []),
inputPath,
outputPath
];
return await this.runCodeQlCliCommand(['generate', 'log-summary'], subcommandArgs, 'Generating log summary');
}
/**
* Generate a JSON summary of an evaluation log.
* @param inputPath The path of an evaluation event log.
* @param outputPath The path to write a JSON summary of it to.
*/
async generateJsonLogSummary(
inputPath: string,
outputPath: string,
): Promise<string> {
const subcommandArgs = [
'--format=predicates',
inputPath,
outputPath
];
return await this.runCodeQlCliCommand(['generate', 'log-summary'], subcommandArgs, 'Generating JSON log summary');
}
/**
* Gets the results from a bqrs.
* @param bqrsPath The path to the bqrs.
@@ -562,40 +730,76 @@ export class CodeQLCliServer implements Disposable {
return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(['bqrs', 'decode'], subcommandArgs, 'Reading bqrs data');
}
async interpretBqrs(metadata: { kind: string; id: string }, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
async runInterpretCommand(format: string, additonalArgs: string[], metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {
const args = [
`-t=kind=${metadata.kind}`,
`-t=id=${metadata.id}`,
'--output', interpretedResultsPath,
'--format', SARIF_FORMAT,
// TODO: This flag means that we don't group interpreted results
// by primary location. We may want to revisit whether we call
// interpretation with and without this flag, or do some
// grouping client-side.
'--no-group-results',
];
'--format', format,
// Forward all of the query metadata.
...Object.entries(metadata).map(([key, value]) => `-t=${key}=${value}`)
].concat(additonalArgs);
if (sourceInfo !== undefined) {
args.push(
'--source-archive', sourceInfo.sourceArchive,
'--source-location-prefix', sourceInfo.sourceLocationPrefix
);
}
args.push(
'--threads',
this.cliConfig.numberThreads.toString(),
);
args.push(
'--max-paths',
this.cliConfig.maxPaths.toString(),
);
args.push(resultsPath);
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');
}
let output: string;
try {
output = await fs.readFile(interpretedResultsPath, 'utf8');
} catch (err) {
throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`);
async interpretBqrsSarif(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
const additionalArgs = [
// TODO: This flag means that we don't group interpreted results
// by primary location. We may want to revisit whether we call
// interpretation with and without this flag, or do some
// grouping client-side.
'--no-group-results'
];
await this.runInterpretCommand(SARIF_FORMAT, additionalArgs, metadata, resultsPath, interpretedResultsPath, sourceInfo);
return await sarifParser(interpretedResultsPath);
}
// Warning: this function is untenable for large dot files,
async readDotFiles(dir: string): Promise<string[]> {
const dotFiles: Promise<string>[] = [];
for await (const file of walkDirectory(dir)) {
if (file.endsWith('.dot')) {
dotFiles.push(fs.readFile(file, 'utf8'));
}
}
return Promise.all(dotFiles);
}
async interpretBqrsGraph(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<string[]> {
const additionalArgs = sourceInfo
? ['--dot-location-url-format', 'file://' + sourceInfo.sourceLocationPrefix + '{path}:{start:line}:{start:column}:{end:line}:{end:column}']
: [];
await this.runInterpretCommand('dot', additionalArgs, metadata, resultsPath, interpretedResultsPath, sourceInfo);
try {
return JSON.parse(output) as sarif.Log;
const dot = await this.readDotFiles(interpretedResultsPath);
return dot;
} catch (err) {
throw new Error(`Parsing output of interpretation failed: ${err.stderr || err}`);
throw new Error(`Reading output of interpretation failed: ${getErrorMessage(err)}`);
}
}
async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise<void> {
await this.runInterpretCommand(CSV_FORMAT, [], metadata, resultsPath, csvPath, sourceInfo);
}
async sortBqrs(resultsPath: string, sortedResultsPath: string, resultSet: string, sortKeys: number[], sortDirections: SortDirection[]): Promise<void> {
const sortDirectionStrings = sortDirections.map(direction => {
@@ -635,12 +839,19 @@ export class CodeQLCliServer implements Disposable {
* Gets information necessary for upgrading a database.
* @param dbScheme the path to the dbscheme of the database to be upgraded.
* @param searchPath A list of directories to search for upgrade scripts.
* @param allowDowngradesIfPossible Whether we should try and include downgrades of we can.
* @param targetDbScheme The dbscheme to try to upgrade to.
* @returns A list of database upgrade script directories
*/
resolveUpgrades(dbScheme: string, searchPath: string[]): Promise<UpgradesInfo> {
const args = ['--additional-packs', searchPath.join(path.delimiter), '--dbscheme', dbScheme];
return this.runJsonCodeQlCliCommand<UpgradesInfo>(
async resolveUpgrades(dbScheme: string, searchPath: string[], allowDowngradesIfPossible: boolean, targetDbScheme?: string): Promise<UpgradesInfo> {
const args = [...this.getAdditionalPacksArg(searchPath), '--dbscheme', dbScheme];
if (targetDbScheme) {
args.push('--target-dbscheme', targetDbScheme);
if (allowDowngradesIfPossible && await this.cliConstraints.supportsDowngrades()) {
args.push('--allow-downgrades');
}
}
return await this.runJsonCodeQlCliCommand<UpgradesInfo>(
['resolve', 'upgrades'],
args,
'Resolving database upgrade scripts',
@@ -655,7 +866,7 @@ export class CodeQLCliServer implements Disposable {
* @returns A dictionary mapping qlpack name to the directory it comes from
*/
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
const args = this.getAdditionalPacksArg(additionalPacks);
if (searchPath?.length) {
args.push('--search-path', path.join(...searchPath));
}
@@ -667,6 +878,31 @@ export class CodeQLCliServer implements Disposable {
);
}
/**
* Gets information about the available languages.
* @returns A dictionary mapping language name to the directory it comes from
*/
async resolveLanguages(): Promise<LanguagesInfo> {
return await this.runJsonCodeQlCliCommand<LanguagesInfo>(['resolve', 'languages'], [], 'Resolving languages');
}
/**
* Gets the list of available languages. Refines the result of `resolveLanguages()`, by excluding
* extra things like "xml" and "properties".
*
* @returns An array of languages that are supported by the current version of the CodeQL CLI.
*/
public async getSupportedLanguages(): Promise<string[]> {
if (!this._supportedLanguages) {
// Get the intersection of resolveLanguages with the list of hardcoded languages in dbSchemeToLanguage.
const resolvedLanguages = Object.keys(await this.resolveLanguages());
const hardcodedLanguages = Object.values(dbSchemeToLanguage);
this._supportedLanguages = resolvedLanguages.filter(lang => hardcodedLanguages.includes(lang));
}
return this._supportedLanguages;
}
/**
* Gets information about queries in a query suite.
* @param suite The suite to resolve.
@@ -675,11 +911,15 @@ export class CodeQLCliServer implements Disposable {
* the default CLI search path is used.
* @returns A list of query files found.
*/
resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
async resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
const args = this.getAdditionalPacksArg(additionalPacks);
if (searchPath !== undefined) {
args.push('--search-path', path.join(...searchPath));
}
if (await this.cliConstraints.supportsAllowLibraryPacksInResolveQueries()) {
// All of our usage of `codeql resolve queries` needs to handle library packs.
args.push('--allow-library-packs');
}
args.push(suite);
return this.runJsonCodeQlCliCommand<string[]>(
['resolve', 'queries'],
@@ -688,8 +928,52 @@ export class CodeQLCliServer implements Disposable {
);
}
/**
* Downloads a specified pack.
* @param packs The `<package-scope/name[@version]>` of the packs to download.
*/
async packDownload(packs: string[]) {
return this.runJsonCodeQlCliCommand(['pack', 'download'], packs, 'Downloading packs');
}
async packInstall(dir: string, forceUpdate = false) {
const args = [dir];
if (forceUpdate) {
args.push('--mode', 'update');
}
return this.runJsonCodeQlCliCommand(['pack', 'install'], args, 'Installing pack dependencies');
}
async packBundle(dir: string, workspaceFolders: string[], outputPath: string, precompile = true): Promise<void> {
const args = [
'-o',
outputPath,
dir,
...this.getAdditionalPacksArg(workspaceFolders)
];
if (!precompile && await this.cliConstraints.supportsNoPrecompile()) {
args.push('--no-precompile');
}
return this.runJsonCodeQlCliCommand(['pack', 'bundle'], args, 'Bundling pack');
}
async packPacklist(dir: string, includeQueries: boolean): Promise<string[]> {
const args = includeQueries ? [dir] : ['--no-include-queries', dir];
// since 2.7.1, packlist returns an object with a "paths" property that is a list of packs.
// previous versions return a list of packs.
const results: { paths: string[] } | string[] = await this.runJsonCodeQlCliCommand(['pack', 'packlist'], args, 'Generating the pack list');
// Once we no longer need to support 2.7.0 or earlier, we can remove this and assume all versions return an object.
if ('paths' in results) {
return results.paths;
} else {
return results;
}
}
async generateDil(qloFile: string, outFile: string): Promise<void> {
const extraArgs = (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0
const extraArgs = await this.cliConstraints.supportsDecompileDil()
? ['--kind', 'dil', '-o', outFile, qloFile]
: ['-o', outFile, qloFile];
await this.runCodeQlCliCommand(
@@ -699,11 +983,15 @@ export class CodeQLCliServer implements Disposable {
);
}
private async getVersion() {
public async getVersion() {
if (!this._version) {
this._version = await this.refreshVersion();
this._version = this.refreshVersion();
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
await commands.executeCommand(
'setContext', 'codeql.supportsEvalLog', await this.cliConstraints.supportsPerQueryEvalLog()
);
}
return this._version;
return await this._version;
}
private async refreshVersion() {
@@ -720,6 +1008,12 @@ export class CodeQLCliServer implements Disposable {
throw new Error('No distribution found');
}
}
private getAdditionalPacksArg(paths: string[]): string[] {
return paths.length
? ['--additional-packs', paths.join(path.delimiter)]
: [];
}
}
/**
@@ -755,7 +1049,7 @@ export function spawnServer(
if (progressReporter !== undefined) {
progressReporter.report({ message: `Starting ${name}` });
}
logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`);
void logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`);
const child = child_process.spawn(base, args);
if (!child || !child.pid) {
throw new Error(`Failed to start ${name} using command ${base} ${argsString}.`);
@@ -771,7 +1065,7 @@ export function spawnServer(
if (progressReporter !== undefined) {
progressReporter.report({ message: `Started ${name}` });
}
logger.log(`${name} started on PID: ${child.pid}`);
void logger.log(`${name} started on PID: ${child.pid}`);
return child;
}
@@ -800,13 +1094,13 @@ export async function runCodeQlCliCommand(
if (progressReporter !== undefined) {
progressReporter.report({ message: description });
}
logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`);
void logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`);
const result = await promisify(child_process.execFile)(codeQlPath, args);
logger.log(result.stderr);
logger.log('CLI command succeeded.');
void logger.log(result.stderr);
void logger.log('CLI command succeeded.');
return result.stdout;
} catch (err) {
throw new Error(`${description} failed: ${err.stderr || err}`);
throw new Error(`${description} failed: ${(err as any).stderr || getErrorMessage(err)}`);
}
}
@@ -839,6 +1133,20 @@ class SplitBuffer {
this.buffer += this.separators[0]; // Append a separator to the end to ensure the last line is returned.
}
/**
* A version of startsWith that isn't overriden by a broken version of ms-python.
*
* The definition comes from
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
* which is CC0/public domain
*
* See https://github.com/github/vscode-codeql/issues/802 for more context as to why we need it.
*/
private static startsWith(s: string, searchString: string, position: number): boolean {
const pos = position > 0 ? position | 0 : 0;
return s.substring(pos, pos + searchString.length) === searchString;
}
/**
* Extract the next full line from the buffer, if one is available.
* @returns The text of the next available full line (without the separator), or `undefined` if no
@@ -847,9 +1155,9 @@ class SplitBuffer {
public getNextLine(): string | undefined {
while (this.searchIndex <= (this.buffer.length - this.maxSeparatorLength)) {
for (const separator of this.separators) {
if (this.buffer.startsWith(separator, this.searchIndex)) {
const line = this.buffer.substr(0, this.searchIndex);
this.buffer = this.buffer.substr(this.searchIndex + separator.length);
if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {
const line = this.buffer.slice(0, this.searchIndex);
this.buffer = this.buffer.slice(this.searchIndex + separator.length);
this.searchIndex = 0;
return line;
}
@@ -904,7 +1212,8 @@ const lineEndings = ['\r\n', '\r', '\n'];
*/
async function logStream(stream: Readable, logger: Logger): Promise<void> {
for await (const line of await splitStreamAtSeparators(stream, lineEndings)) {
logger.log(line);
// Await the result of log here in order to ensure the logs are written in the correct order.
await logger.log(line);
}
}
@@ -920,3 +1229,193 @@ export function shouldDebugQueryServer() {
&& process.env.QUERY_SERVER_JAVA_DEBUG !== '0'
&& process.env.QUERY_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}
export function shouldDebugCliServer() {
return 'CLI_SERVER_JAVA_DEBUG' in process.env
&& process.env.CLI_SERVER_JAVA_DEBUG !== '0'
&& process.env.CLI_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}
export class CliVersionConstraint {
/**
* CLI version where --kind=DIL was introduced
*/
public static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0');
/**
* CLI version where languages are exposed during a `codeql resolve database` command.
*/
public static CLI_VERSION_WITH_LANGUAGE = new SemVer('2.4.1');
public static CLI_VERSION_WITH_NONDESTURCTIVE_UPGRADES = new SemVer('2.4.2');
/**
* CLI version where `codeql resolve upgrades` supports
* the `--allow-downgrades` flag
*/
public static CLI_VERSION_WITH_DOWNGRADES = new SemVer('2.4.4');
/**
* CLI version where the `codeql resolve qlref` command is available.
*/
public static CLI_VERSION_WITH_RESOLVE_QLREF = new SemVer('2.5.1');
/**
* CLI version where database registration was introduced
*/
public static CLI_VERSION_WITH_DB_REGISTRATION = new SemVer('2.4.1');
/**
* CLI version where the `--allow-library-packs` option to `codeql resolve queries` was
* introduced.
*/
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
/**
* CLI version where the `database unbundle` subcommand was introduced.
*/
public static CLI_VERSION_WITH_DATABASE_UNBUNDLE = new SemVer('2.6.0');
/**
* CLI version where the `--no-precompile` option for pack creation was introduced.
*/
public static CLI_VERSION_WITH_NO_PRECOMPILE = new SemVer('2.7.1');
/**
* CLI version where remote queries (variant analysis) are supported.
*/
public static CLI_VERSION_REMOTE_QUERIES = new SemVer('2.6.3');
/**
* CLI version where the `resolve ml-models` subcommand was introduced.
*/
public static CLI_VERSION_WITH_RESOLVE_ML_MODELS = new SemVer('2.7.3');
/**
* CLI version where the `resolve ml-models` subcommand was enhanced to work with packaging.
*/
public static CLI_VERSION_WITH_PRECISE_RESOLVE_ML_MODELS = new SemVer('2.10.0');
/**
* CLI version where the `--old-eval-stats` option to the query server was introduced.
*/
public static CLI_VERSION_WITH_OLD_EVAL_STATS = new SemVer('2.7.4');
/**
* CLI version where packaging was introduced.
*/
public static CLI_VERSION_WITH_PACKAGING = new SemVer('2.6.0');
/**
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
* on a per-query server basis.
*/
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer('2.8.2');
/**
* CLI version that supports rotating structured logs to produce one per query.
*
* Note that 2.8.4 supports generating the evaluation logs and summaries,
* but 2.9.0 includes a new option to produce the end-of-query summary logs to
* the query server console. For simplicity we gate all features behind 2.9.0,
* but if a user is tied to the 2.8 release, we can enable evaluator logs
* and summaries for them.
*/
public static CLI_VERSION_WITH_PER_QUERY_EVAL_LOG = new SemVer('2.9.0');
/**
* CLI version that supports the `--sourcemap` option for log generation.
*/
public static CLI_VERSION_WITH_SOURCEMAP = new SemVer('2.10.3');
/**
* CLI version that supports the new query server.
*/
public static CLI_VERSION_WITH_NEW_QUERY_SERVER = new SemVer('2.11.0');
constructor(private readonly cli: CodeQLCliServer) {
/**/
}
private async isVersionAtLeast(v: SemVer) {
return (await this.cli.getVersion()).compare(v) >= 0;
}
public async supportsDecompileDil() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DECOMPILE_KIND_DIL);
}
public async supportsLanguageName() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_LANGUAGE);
}
public async supportsNonDestructiveUpgrades() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_NONDESTURCTIVE_UPGRADES);
}
public async supportsDowngrades() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DOWNGRADES);
}
public async supportsResolveQlref() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF);
}
public async supportsAllowLibraryPacksInResolveQueries() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES);
}
async supportsDatabaseRegistration() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
}
async supportsDatabaseUnbundle() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DATABASE_UNBUNDLE);
}
async supportsNoPrecompile() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_NO_PRECOMPILE);
}
async supportsRemoteQueries() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES);
}
async supportsResolveMlModels() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_ML_MODELS);
}
async supportsPreciseResolveMlModels() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_PRECISE_RESOLVE_ML_MODELS);
}
async supportsOldEvalStats() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_OLD_EVAL_STATS);
}
async supportsPackaging() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_PACKAGING);
}
async supportsStructuredEvalLog() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG);
}
async supportsPerQueryEvalLog() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG);
}
async supportsSourceMap() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_SOURCEMAP);
}
async supportsNewQueryServer() {
// TODO while under development, users _must_ opt-in to the new query server
// by setting the `codeql.canaryQueryServer` setting to `true`.
// Ignore the version check for now.
return allowCanaryQueryServer();
// return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_NEW_QUERY_SERVER);
}
}

View File

@@ -0,0 +1,246 @@
import {
CancellationToken,
ProgressOptions,
window as Window,
commands,
Disposable,
ProgressLocation
} from 'vscode';
import { showAndLogErrorMessage, showAndLogWarningMessage } from './helpers';
import { logger } from './logging';
import { getErrorMessage, getErrorStack } from './pure/helpers-pure';
import { telemetryListener } from './telemetry';
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>;
/**
* A task that handles command invocations from `commandRunner`.
* Arguments passed to the command handler are passed along,
* untouched to this `NoProgressTask` instance.
*
* @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);
});
}
/**
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
*
* In this variant of the command runner, no progress monitor is used.
*
* @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.
*/
export function commandRunner(
commandId: string,
task: NoProgressTask,
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
const startTime = Date.now();
let error: Error | undefined;
try {
return await task(...args);
} catch (e) {
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
error = e instanceof Error ? e : new Error(errorMessage);
const errorStack = getErrorStack(e);
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
void logger.log(errorMessage);
} else {
void showAndLogWarningMessage(errorMessage);
}
} else {
// Include the full stack in the error log only.
const fullMessage = errorStack
? `${errorMessage}\n${errorStack}`
: errorMessage;
void showAndLogErrorMessage(errorMessage, {
fullMessage
});
}
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 = logger
): 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) {
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
error = e instanceof Error ? e : new Error(errorMessage);
const errorStack = getErrorStack(e);
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
void outputLogger.log(errorMessage);
} else {
void showAndLogWarningMessage(errorMessage, { outputLogger });
}
} else {
// Include the full stack in the error log only.
const fullMessage = errorStack
? `${errorMessage}\n${errorStack}`
: errorMessage;
void showAndLogErrorMessage(errorMessage, {
outputLogger,
fullMessage
});
}
return undefined;
} finally {
const executionTime = Date.now() - startTime;
telemetryListener.sendCommandUsage(commandId, executionTime, error);
}
});
}
/**
* 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,15 +1,8 @@
import { DisposableObject } from '../vscode-utils/disposable-object';
import {
WebviewPanel,
ExtensionContext,
window as Window,
ViewColumn,
Uri,
} from 'vscode';
import * as path from 'path';
import { tmpDir } from '../run-queries';
import { CompletedQuery } from '../query-results';
import {
FromCompareViewMessage,
ToCompareViewMessage,
@@ -18,36 +11,38 @@ import {
import { Logger } from '../logging';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager } from '../databases';
import { getHtmlForWebview, jumpToLocation } from '../interface-utils';
import { jumpToLocation } from '../interface-utils';
import { transformBqrsResultSet, RawResultSet, BQRSInfo } from '../pure/bqrs-cli-types';
import resultsDiff from './resultsDiff';
import { CompletedLocalQueryInfo } from '../query-results';
import { getErrorMessage } from '../pure/helpers-pure';
import { HistoryItemLabelProvider } from '../history-item-label-provider';
import { AbstractWebview, WebviewPanelConfig } from '../abstract-webview';
interface ComparePair {
from: CompletedQuery;
to: CompletedQuery;
from: CompletedLocalQueryInfo;
to: CompletedLocalQueryInfo;
}
export class CompareInterfaceManager extends DisposableObject {
export class CompareView extends AbstractWebview<ToCompareViewMessage, FromCompareViewMessage> {
private comparePair: ComparePair | undefined;
private panel: WebviewPanel | undefined;
private panelLoaded = false;
private panelLoadedCallBacks: (() => void)[] = [];
constructor(
private ctx: ExtensionContext,
ctx: ExtensionContext,
private databaseManager: DatabaseManager,
private cliServer: CodeQLCliServer,
private logger: Logger,
private labelProvider: HistoryItemLabelProvider,
private showQueryResultsCallback: (
item: CompletedQuery
item: CompletedLocalQueryInfo
) => Promise<void>
) {
super();
super(ctx);
}
async showResults(
from: CompletedQuery,
to: CompletedQuery,
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
selectedResultSetName?: string
) {
this.comparePair = { from, to };
@@ -70,7 +65,7 @@ export class CompareInterfaceManager extends DisposableObject {
try {
rows = this.compareResults(fromResultSet, toResultSet);
} catch (e) {
message = e.message;
message = getErrorMessage(e);
}
await this.postMessage({
@@ -80,18 +75,14 @@ export class CompareInterfaceManager extends DisposableObject {
// since we split the description into several rows
// only run interpolation if the label is user-defined
// otherwise we will wind up with duplicated rows
name: from.options.label
? from.interpolate(from.getLabel())
: from.queryName,
status: from.statusString,
time: from.time,
name: this.labelProvider.getShortLabel(from),
status: from.completedQuery.statusString,
time: from.startTime,
},
toQuery: {
name: to.options.label
? to.interpolate(to.getLabel())
: to.queryName,
status: to.statusString,
time: to.time,
name: this.labelProvider.getShortLabel(to),
status: to.completedQuery.statusString,
time: to.startTime,
},
},
columns: fromResultSet.schema.columns,
@@ -99,81 +90,33 @@ export class CompareInterfaceManager extends DisposableObject {
currentResultSetName: currentResultSetName,
rows,
message,
datebaseUri: to.database.databaseUri,
databaseUri: to.initialInfo.databaseInfo.databaseUri,
});
}
}
getPanel(): WebviewPanel {
if (this.panel == undefined) {
const { ctx } = this;
const panel = (this.panel = Window.createWebviewPanel(
'compareView',
'Compare CodeQL Query Results',
{ viewColumn: ViewColumn.Active, preserveFocus: true },
{
enableScripts: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: [
Uri.file(tmpDir.name),
Uri.file(path.join(this.ctx.extensionPath, 'out')),
],
}
));
this.panel.onDidDispose(
() => {
this.panel = undefined;
this.comparePair = undefined;
},
null,
ctx.subscriptions
);
const scriptPathOnDisk = Uri.file(
ctx.asAbsolutePath('out/compareView.js')
);
const stylesheetPathOnDisk = Uri.file(
ctx.asAbsolutePath('out/resultsView.css')
);
panel.webview.html = getHtmlForWebview(
panel.webview,
scriptPathOnDisk,
stylesheetPathOnDisk
);
panel.webview.onDidReceiveMessage(
async (e) => this.handleMsgFromView(e),
undefined,
ctx.subscriptions
);
}
return this.panel;
protected getPanelConfig(): WebviewPanelConfig {
return {
viewId: 'compareView',
title: 'Compare CodeQL Query Results',
viewColumn: ViewColumn.Active,
preserveFocus: true,
view: 'compare',
};
}
private waitForPanelLoaded(): Promise<void> {
return new Promise((resolve) => {
if (this.panelLoaded) {
resolve();
} else {
this.panelLoadedCallBacks.push(resolve);
}
});
protected onPanelDispose(): void {
this.comparePair = undefined;
}
private async handleMsgFromView(
msg: FromCompareViewMessage
): Promise<void> {
protected async onMessage(msg: FromCompareViewMessage): Promise<void> {
switch (msg.t) {
case 'compareViewLoaded':
this.panelLoaded = true;
this.panelLoadedCallBacks.forEach((cb) => cb());
this.panelLoadedCallBacks = [];
case 'viewLoaded':
this.onWebViewLoaded();
break;
case 'changeCompare':
this.changeTable(msg.newResultSetName);
await this.changeTable(msg.newResultSetName);
break;
case 'viewSourceFile':
@@ -186,20 +129,16 @@ export class CompareInterfaceManager extends DisposableObject {
}
}
private postMessage(msg: ToCompareViewMessage): Thenable<boolean> {
return this.getPanel().webview.postMessage(msg);
}
private async findCommonResultSetNames(
from: CompletedQuery,
to: CompletedQuery,
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
selectedResultSetName: string | undefined
): Promise<[string[], string, RawResultSet, RawResultSet]> {
const fromSchemas = await this.cliServer.bqrsInfo(
from.query.resultsPaths.resultsPath
from.completedQuery.query.resultsPaths.resultsPath
);
const toSchemas = await this.cliServer.bqrsInfo(
to.query.resultsPaths.resultsPath
to.completedQuery.query.resultsPaths.resultsPath
);
const fromSchemaNames = fromSchemas['result-sets'].map(
(schema) => schema.name
@@ -215,12 +154,12 @@ export class CompareInterfaceManager extends DisposableObject {
const fromResultSet = await this.getResultSet(
fromSchemas,
currentResultSetName,
from.query.resultsPaths.resultsPath
from.completedQuery.query.resultsPaths.resultsPath
);
const toResultSet = await this.getResultSet(
toSchemas,
currentResultSetName,
to.query.resultsPaths.resultsPath
to.completedQuery.query.resultsPaths.resultsPath
);
return [
commonResultSetNames,
@@ -267,11 +206,11 @@ export class CompareInterfaceManager extends DisposableObject {
return resultsDiff(fromResults, toResults);
}
private openQuery(kind: 'from' | 'to') {
private async openQuery(kind: 'from' | 'to') {
const toOpen =
kind === 'from' ? this.comparePair?.from : this.comparePair?.to;
if (toOpen) {
this.showQueryResultsCallback(toOpen);
await this.showQueryResultsCallback(toOpen);
}
}
}

View File

@@ -1,16 +1,20 @@
import { DisposableObject } from './vscode-utils/disposable-object';
import { DisposableObject } from './pure/disposable-object';
import { workspace, Event, EventEmitter, ConfigurationChangeEvent, ConfigurationTarget } from 'vscode';
import { DistributionManager } from './distribution';
import { logger } from './logging';
import { ONE_DAY_IN_MS } from './pure/time';
export const ALL_SETTINGS: Setting[] = [];
/** Helper class to look up a labelled (and possibly nested) setting. */
class Setting {
export class Setting {
name: string;
parent?: Setting;
constructor(name: string, parent?: Setting) {
this.name = name;
this.parent = parent;
ALL_SETTINGS.push(this);
}
get qualifiedName(): string {
@@ -35,24 +39,49 @@ class Setting {
return workspace.getConfiguration(this.parent.qualifiedName).update(this.name, value, target);
}
inspect<T>(): InspectionResult<T> | undefined {
if (this.parent === undefined) {
throw new Error('Cannot update the value of a root setting.');
}
return workspace.getConfiguration(this.parent.qualifiedName).inspect(this.name);
}
}
export interface InspectionResult<T> {
globalValue?: T;
workspaceValue?: T,
workspaceFolderValue?: T,
}
const ROOT_SETTING = new Setting('codeQL');
// Distribution configuration
// Global configuration
const TELEMETRY_SETTING = new Setting('telemetry', ROOT_SETTING);
const AST_VIEWER_SETTING = new Setting('astViewer', ROOT_SETTING);
const GLOBAL_TELEMETRY_SETTING = new Setting('telemetry');
export const LOG_TELEMETRY = new Setting('logTelemetry', TELEMETRY_SETTING);
export const ENABLE_TELEMETRY = new Setting('enableTelemetry', TELEMETRY_SETTING);
export const GLOBAL_ENABLE_TELEMETRY = new Setting('enableTelemetry', GLOBAL_TELEMETRY_SETTING);
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);
const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING);
export const CUSTOM_CODEQL_PATH_SETTING = new Setting('executablePath', DISTRIBUTION_SETTING);
const INCLUDE_PRERELEASE_SETTING = new Setting('includePrerelease', DISTRIBUTION_SETTING);
const PERSONAL_ACCESS_TOKEN_SETTING = new Setting('personalAccessToken', DISTRIBUTION_SETTING);
// Query History configuration
const QUERY_HISTORY_SETTING = new Setting('queryHistory', ROOT_SETTING);
const QUERY_HISTORY_FORMAT_SETTING = new Setting('format', QUERY_HISTORY_SETTING);
const QUERY_HISTORY_TTL = new Setting('ttl', QUERY_HISTORY_SETTING);
/** When these settings change, the distribution should be updated. */
const DISTRIBUTION_CHANGE_SETTINGS = [CUSTOM_CODEQL_PATH_SETTING, INCLUDE_PRERELEASE_SETTING, PERSONAL_ACCESS_TOKEN_SETTING];
export interface DistributionConfig {
customCodeQlPath?: string;
readonly customCodeQlPath?: string;
updateCustomCodeQlPath: (newPath: string | undefined) => Promise<void>;
includePrerelease: boolean;
personalAccessToken?: string;
ownerName?: string;
@@ -61,49 +90,63 @@ export interface DistributionConfig {
}
// Query server configuration
const RUNNING_QUERIES_SETTING = new Setting('runningQueries', ROOT_SETTING);
const NUMBER_OF_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_QUERIES_SETTING);
const SAVE_CACHE_SETTING = new Setting('saveCache', RUNNING_QUERIES_SETTING);
const CACHE_SIZE_SETTING = new Setting('cacheSize', RUNNING_QUERIES_SETTING);
const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING);
const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
const 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);
export const ADDITIONAL_TEST_ARGUMENTS_SETTING = new Setting('additionalTestArguments', RUNNING_TESTS_SETTING);
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
export const PAGE_SIZE = new Setting('pageSize', RESULTS_DISPLAY_SETTING);
const CUSTOM_LOG_DIRECTORY_SETTING = new Setting('customLogDirectory', RUNNING_QUERIES_SETTING);
/** When these settings change, the running query server should be restarted. */
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];
const QUERY_SERVER_RESTARTING_SETTINGS = [
NUMBER_OF_THREADS_SETTING, SAVE_CACHE_SETTING, CACHE_SIZE_SETTING, MEMORY_SETTING,
DEBUG_SETTING, CUSTOM_LOG_DIRECTORY_SETTING,
];
export interface QueryServerConfig {
codeQlPath: string;
debug: boolean;
numThreads: number;
saveCache: boolean;
cacheSize: number;
queryMemoryMb?: number;
timeoutSecs: number;
customLogDirectory?: string;
onDidChangeConfiguration?: Event<void>;
}
/** When these settings change, the query history should be refreshed. */
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING];
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING, QUERY_HISTORY_TTL];
export interface QueryHistoryConfig {
format: string;
ttlInMillis: number;
onDidChangeConfiguration: Event<void>;
}
const CLI_SETTINGS = [NUMBER_OF_TEST_THREADS_SETTING];
const CLI_SETTINGS = [ADDITIONAL_TEST_ARGUMENTS_SETTING, NUMBER_OF_TEST_THREADS_SETTING, NUMBER_OF_THREADS_SETTING, MAX_PATHS];
export interface CliConfig {
additionalTestArguments: string[];
numberTestThreads: number;
numberThreads: number;
maxPaths: number;
onDidChangeConfiguration?: Event<void>;
}
abstract class ConfigListener extends DisposableObject {
export abstract class ConfigListener extends DisposableObject {
protected readonly _onDidChangeConfiguration = this.push(new EventEmitter<void>());
constructor() {
@@ -128,7 +171,7 @@ abstract class ConfigListener extends DisposableObject {
protected abstract handleDidChangeConfiguration(e: ConfigurationChangeEvent): void;
private updateConfiguration(): void {
this._onDidChangeConfiguration.fire();
this._onDidChangeConfiguration.fire(undefined);
}
public get onDidChangeConfiguration(): Event<void> {
@@ -149,6 +192,10 @@ export class DistributionConfigListener extends ConfigListener implements Distri
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() || undefined;
}
public async updateCustomCodeQlPath(newPath: string | undefined) {
await CUSTOM_CODEQL_PATH_SETTING.updateValue(newPath, ConfigurationTarget.Global);
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(DISTRIBUTION_CHANGE_SETTINGS, e);
}
@@ -166,7 +213,7 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
config.push(distributionManager.onDidChangeDistribution(async () => {
const codeQlPath = await distributionManager.getCodeQlPathWithoutVersionCheck();
config._codeQlPath = codeQlPath!;
config._onDidChangeConfiguration.fire();
config._onDidChangeConfiguration.fire(undefined);
}));
}
return config;
@@ -176,10 +223,22 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
return this._codeQlPath;
}
public get customLogDirectory(): string | undefined {
return CUSTOM_LOG_DIRECTORY_SETTING.getValue<string>() || undefined;
}
public get numThreads(): number {
return NUMBER_OF_THREADS_SETTING.getValue<number>();
}
public get saveCache(): boolean {
return SAVE_CACHE_SETTING.getValue<boolean>();
}
public get cacheSize(): number {
return CACHE_SIZE_SETTING.getValue<number | null>() || 0;
}
/** Gets the configured query timeout, in seconds. This looks up the setting at the time of access. */
public get timeoutSecs(): number {
return TIMEOUT_SETTING.getValue<number | null>() || 0;
@@ -191,7 +250,7 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
return undefined;
}
if (memory == 0 || typeof (memory) !== 'number') {
logger.log(`Ignoring value '${memory}' for setting ${MEMORY_SETTING.qualifiedName}`);
void logger.log(`Ignoring value '${memory}' for setting ${MEMORY_SETTING.qualifiedName}`);
return undefined;
}
return memory;
@@ -214,19 +273,47 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
public get format(): string {
return QUERY_HISTORY_FORMAT_SETTING.getValue<string>();
}
/**
* The configuration value is in days, but return the value in milliseconds to make it easier to use.
*/
public get ttlInMillis(): number {
return (QUERY_HISTORY_TTL.getValue<number>() || 30) * ONE_DAY_IN_MS;
}
}
export class CliConfigListener extends ConfigListener implements CliConfig {
public get additionalTestArguments(): string[] {
return ADDITIONAL_TEST_ARGUMENTS_SETTING.getValue();
}
public get numberTestThreads(): number {
return NUMBER_OF_TEST_THREADS_SETTING.getValue();
}
public get numberThreads(): number {
return NUMBER_OF_THREADS_SETTING.getValue<number>();
}
public get maxPaths(): number {
return MAX_PATHS.getValue<number>();
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
}
}
/**
* Whether to enable CodeLens for the 'Quick Evaluation' command.
*/
const QUICK_EVAL_CODELENS_SETTING = new Setting('quickEvalCodelens', RUNNING_QUERIES_SETTING);
export function isQuickEvalCodelensEnabled() {
return QUICK_EVAL_CODELENS_SETTING.getValue<boolean>();
}
// Enable experimental features
/**
@@ -235,3 +322,104 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
* want to enable experimental features, they can add them directly in
* their vscode settings json file.
*/
/**
* Enables canary features of this extension. Recommended for all internal users.
*/
export const CANARY_FEATURES = new Setting('canary', ROOT_SETTING);
export function isCanary() {
return !!CANARY_FEATURES.getValue<boolean>();
}
/**
* Enables the experimental query server
*/
export const CANARY_QUERY_SERVER = new Setting('canaryQueryServer', ROOT_SETTING);
export function allowCanaryQueryServer() {
return !!CANARY_QUERY_SERVER.getValue<boolean>();
}
/**
* Avoids caching in the AST viewer if the user is also a canary user.
*/
export const NO_CACHE_AST_VIEWER = new Setting('disableCache', AST_VIEWER_SETTING);
// Settings for variant analysis
const REMOTE_QUERIES_SETTING = new Setting('variantAnalysis', ROOT_SETTING);
/**
* Lists of GitHub repositories that you want to query remotely via the "Run Variant Analysis" command.
* Note: This command is only available for internal users.
*
* This setting should be a JSON object where each key is a user-specified name (string),
* and the value is an array of GitHub repositories (of the form `<owner>/<repo>`).
*/
const REMOTE_REPO_LISTS = new Setting('repositoryLists', REMOTE_QUERIES_SETTING);
export function getRemoteRepositoryLists(): Record<string, string[]> | undefined {
return REMOTE_REPO_LISTS.getValue<Record<string, string[]>>() || undefined;
}
export async function setRemoteRepositoryLists(lists: Record<string, string[]> | undefined) {
await REMOTE_REPO_LISTS.updateValue(lists, ConfigurationTarget.Global);
}
/**
* Path to a file that contains lists of GitHub repositories that you want to query remotely via
* the "Run Variant Analysis" command.
* Note: This command is only available for internal users.
*
* This setting should be a path to a JSON file that contains a JSON object where each key is a
* user-specified name (string), and the value is an array of GitHub repositories
* (of the form `<owner>/<repo>`).
*/
const REPO_LISTS_PATH = new Setting('repositoryListsPath', REMOTE_QUERIES_SETTING);
export function getRemoteRepositoryListsPath(): string | undefined {
return REPO_LISTS_PATH.getValue<string>() || undefined;
}
/**
* The name of the "controller" repository that you want to use with the "Run Variant Analysis" command.
* Note: This command is only available for internal users.
*
* This setting should be a GitHub repository of the form `<owner>/<repo>`.
*/
const REMOTE_CONTROLLER_REPO = new Setting('controllerRepo', REMOTE_QUERIES_SETTING);
export function getRemoteControllerRepo(): string | undefined {
return REMOTE_CONTROLLER_REPO.getValue<string>() || undefined;
}
export async function setRemoteControllerRepo(repo: string | undefined) {
await REMOTE_CONTROLLER_REPO.updateValue(repo, ConfigurationTarget.Global);
}
/**
* The branch of "github/codeql-variant-analysis-action" to use with the "Run Variant Analysis" command.
* Default value is "main".
* Note: This command is only available for internal users.
*/
const ACTION_BRANCH = new Setting('actionBranch', REMOTE_QUERIES_SETTING);
export function getActionBranch(): string {
return ACTION_BRANCH.getValue<string>() || 'main';
}
export function isIntegrationTestMode() {
return process.env.INTEGRATION_TEST_MODE === 'true';
}
/**
* A flag indicating whether to enable the experimental "live results" feature
* for multi-repo variant analyses.
*/
const LIVE_RESULTS = new Setting('liveResults', REMOTE_QUERIES_SETTING);
export function isVariantAnalysisLiveResultsEnabled(): boolean {
return !!LIVE_RESULTS.getValue<boolean>();
}

View File

@@ -1,9 +1,10 @@
import { QueryWithResults } from '../run-queries';
import { CodeQLCliServer } from '../cli';
import { DecodedBqrsChunk, BqrsId, EntityValue } from '../pure/bqrs-cli-types';
import { DatabaseItem } from '../databases';
import { ChildAstItem, AstItem } from '../astViewer';
import fileRangeFromURI from './fileRangeFromURI';
import { Uri } from 'vscode';
import { QueryWithResults } from '../run-queries-shared';
/**
* A class that wraps a tree of QL results from a query that
@@ -17,7 +18,7 @@ export default class AstBuilder {
queryResults: QueryWithResults,
private cli: CodeQLCliServer,
public db: DatabaseItem,
public fileName: string
public fileName: Uri
) {
this.bqrsPath = queryResults.query.resultsPaths.resultsPath;
}

View File

@@ -2,6 +2,7 @@ export enum KeyType {
DefinitionQuery = 'DefinitionQuery',
ReferenceQuery = 'ReferenceQuery',
PrintAstQuery = 'PrintAstQuery',
PrintCfgQuery = 'PrintCfgQuery',
}
export function tagOfKeyType(keyType: KeyType): string {
@@ -12,6 +13,8 @@ export function tagOfKeyType(keyType: KeyType): string {
return 'ide-contextual-queries/local-references';
case KeyType.PrintAstQuery:
return 'ide-contextual-queries/print-ast';
case KeyType.PrintCfgQuery:
return 'ide-contextual-queries/print-cfg';
}
}
@@ -23,6 +26,8 @@ export function nameOfKeyType(keyType: KeyType): string {
return 'references';
case KeyType.PrintAstQuery:
return 'print AST';
case KeyType.PrintCfgQuery:
return 'print CFG';
}
}
@@ -32,6 +37,7 @@ export function kindOfKeyType(keyType: KeyType): string {
case KeyType.ReferenceQuery:
return 'definitions';
case KeyType.PrintAstQuery:
case KeyType.PrintCfgQuery:
return 'graph';
}
}

View File

@@ -1,22 +1,20 @@
import * as vscode from 'vscode';
import { decodeSourceArchiveUri, encodeArchiveBasePath } from '../archive-filesystem-provider';
import { ColumnKindCode, EntityValue, getResultSetSchema, ResultSetSchema } from '../pure/bqrs-cli-types';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager, DatabaseItem } from '../databases';
import fileRangeFromURI from './fileRangeFromURI';
import * as messages from '../pure/messages';
import { QueryServerClient } from '../queryserver-client';
import { QueryWithResults, compileAndRunQueryAgainstDatabase } from '../run-queries';
import { ProgressCallback } from '../helpers';
import { ProgressCallback } from '../commandRunner';
import { KeyType } from './keyType';
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
import { CancellationToken, LocationLink, Uri } from 'vscode';
import { createInitialQueryInfo, QueryWithResults } from '../run-queries-shared';
import { QueryRunner } from '../queryRunner';
const SELECT_QUERY_NAME = '#select';
export const SELECT_QUERY_NAME = '#select';
export const TEMPLATE_NAME = 'selectedSourceFile';
export interface FullLocationLink extends vscode.LocationLink {
originUri: vscode.Uri;
export interface FullLocationLink extends LocationLink {
originUri: Uri;
}
/**
@@ -29,21 +27,23 @@ export interface FullLocationLink extends vscode.LocationLink {
* @param dbm The database manager
* @param uriString The selected source file and location
* @param keyType The contextual query type to run
* @param queryStorageDir The directory to store the query results
* @param progress A progress callback
* @param token A CancellationToken
* @param filter A function that will filter extraneous results
*/
export async function getLocationsForUriString(
cli: CodeQLCliServer,
qs: QueryServerClient,
qs: QueryRunner,
dbm: DatabaseManager,
uriString: string,
keyType: KeyType,
queryStorageDir: string,
progress: ProgressCallback,
token: vscode.CancellationToken,
token: CancellationToken,
filter: (src: string, dest: string) => boolean
): Promise<FullLocationLink[]> {
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString, true));
const uri = decodeSourceArchiveUri(Uri.parse(uriString, true));
const sourceArchiveUri = encodeArchiveBasePath(uri.sourceArchiveZipPath);
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
@@ -52,25 +52,20 @@ export async function getLocationsForUriString(
}
const qlpack = await qlpackOfDatabase(cli, db);
if (qlpack === undefined) {
throw new Error('Can\'t infer qlpack from database source archive');
}
const templates = createTemplates(uri.pathWithinSourceArchive);
const links: FullLocationLink[] = [];
for (const query of await resolveQueries(cli, qlpack, keyType)) {
const results = await compileAndRunQueryAgainstDatabase(
cli,
qs,
db,
false,
vscode.Uri.file(query),
progress,
token,
templates
const initialInfo = await createInitialQueryInfo(
Uri.file(query),
{
name: db.name,
databaseUri: db.databaseUri.toString(),
},
false
);
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
const results = await qs.compileAndRunQueryAgainstDatabase(db, initialInfo, queryStorageDir, progress, token, templates);
if (results.successful) {
links.push(...await getLinksFromResults(results, cli, db, filter));
}
}
@@ -107,15 +102,9 @@ async function getLinksFromResults(
return localLinks;
}
function createTemplates(path: string): messages.TemplateDefinitions {
function createTemplates(path: string): Record<string, string> {
return {
[TEMPLATE_NAME]: {
values: {
tuples: [[{
stringValue: path
}]]
}
}
[TEMPLATE_NAME]: path
};
}

View File

@@ -11,37 +11,96 @@ import {
} from './keyType';
import { CodeQLCliServer } from '../cli';
import { DatabaseItem } from '../databases';
import { QlPacksForLanguage } from '../helpers';
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string | undefined> {
if (db.contents === undefined)
return undefined;
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<QlPacksForLanguage> {
if (db.contents === undefined) {
throw new Error('Database is invalid and cannot infer QLPack.');
}
const datasetPath = db.contents.datasetUri.fsPath;
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
return qlpack;
const dbscheme = await helpers.getPrimaryDbscheme(datasetPath);
return await helpers.getQlPackForDbscheme(cli, dbscheme);
}
export async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
/**
* Finds the contextual queries with the specified key in a list of CodeQL packs.
*
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param keyType The contextual query key of the query to search for.
* @returns The found queries from the first pack in which any matching queries were found.
*/
async function resolveQueriesFromPacks(cli: CodeQLCliServer, qlpacks: string[], keyType: KeyType): Promise<string[]> {
const suiteFile = (await tmp.file({
postfix: '.qls'
})).path;
const suiteYaml = {
qlpack,
include: {
kind: kindOfKeyType(keyType),
'tags contain': tagOfKeyType(keyType)
}
};
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
const suiteYaml = [];
for (const qlpack of qlpacks) {
suiteYaml.push({
from: qlpack,
queries: '.',
include: {
kind: kindOfKeyType(keyType),
'tags contain': tagOfKeyType(keyType)
}
});
}
await fs.writeFile(suiteFile, yaml.dump(suiteYaml), 'utf8');
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
if (queries.length === 0) {
helpers.showAndLogErrorMessage(
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
for this language.`
);
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} for qlpack ${qlpack}`);
}
return queries;
}
export async function resolveQueries(cli: CodeQLCliServer, qlpacks: QlPacksForLanguage, keyType: KeyType): Promise<string[]> {
const cliCanHandleLibraryPack = await cli.cliConstraints.supportsAllowLibraryPacksInResolveQueries();
const packsToSearch: string[] = [];
let blameCli: boolean;
if (cliCanHandleLibraryPack) {
// The CLI can handle both library packs and query packs, so search both packs in order.
packsToSearch.push(qlpacks.dbschemePack);
if (qlpacks.queryPack !== undefined) {
packsToSearch.push(qlpacks.queryPack);
}
// If we don't find the query, it's because it's not there, not because the CLI was unable to
// search the pack.
blameCli = false;
} else {
// Older CLIs can't handle `codeql resolve queries` with a suite that references a library pack.
if (qlpacks.dbschemePackIsLibraryPack) {
if (qlpacks.queryPack !== undefined) {
// Just search the query pack, because some older library/query releases still had the
// contextual queries in the query pack.
packsToSearch.push(qlpacks.queryPack);
}
// If we don't find it, it's because the CLI was unable to search the library pack that
// actually contains the query. Blame any failure on the CLI, not the packs.
blameCli = true;
} else {
// We have an old CLI, but the dbscheme pack is old enough that it's still a unified pack with
// both libraries and queries. Just search that pack.
packsToSearch.push(qlpacks.dbschemePack);
// Any CLI should be able to search the single query pack, so if we don't find it, it's
// because the language doesn't support it.
blameCli = false;
}
}
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
if (queries.length > 0) {
return queries;
}
// No queries found. Determine the correct error message for the various scenarios.
const errorMessage = blameCli ?
`Your current version of the CodeQL CLI, '${(await cli.getVersion()).version}', \
is unable to use contextual queries from recent versions of the standard CodeQL libraries. \
Please upgrade to the latest version of the CodeQL CLI.`
:
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
for this language.`;
void helpers.showAndLogErrorMessage(errorMessage);
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} in any of the following packs: ${packsToSearch.join(', ')}.`);
}

View File

@@ -1,18 +1,30 @@
import * as vscode from 'vscode';
import {
CancellationToken,
DefinitionProvider,
Location,
LocationLink,
Position,
ProgressLocation,
ReferenceContext,
ReferenceProvider,
TextDocument,
Uri
} from 'vscode';
import { decodeSourceArchiveUri, encodeArchiveBasePath, zipArchiveScheme } from '../archive-filesystem-provider';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager } from '../databases';
import { CachedOperation, ProgressCallback, withProgress } from '../helpers';
import * as messages from '../pure/messages';
import { QueryServerClient } from '../queryserver-client';
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';
import { CachedOperation } from '../helpers';
import { ProgressCallback, withProgress } from '../commandRunner';
import AstBuilder from './astBuilder';
import {
KeyType,
} from './keyType';
import { FullLocationLink, getLocationsForUriString, TEMPLATE_NAME } from './locationFinder';
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
import { isCanary, NO_CACHE_AST_VIEWER } from '../config';
import { createInitialQueryInfo, QueryWithResults } from '../run-queries-shared';
import { QueryRunner } from '../queryRunner';
/**
* Run templated CodeQL queries to find definitions and references in
@@ -21,20 +33,21 @@ import { qlpackOfDatabase, resolveQueries } from './queryResolver';
* or from a selected identifier.
*/
export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider {
private cache: CachedOperation<vscode.LocationLink[]>;
export class TemplateQueryDefinitionProvider implements DefinitionProvider {
private cache: CachedOperation<LocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private qs: QueryRunner,
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<vscode.LocationLink[]>(this.getDefinitions.bind(this));
this.cache = new CachedOperation<LocationLink[]>(this.getDefinitions.bind(this));
}
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.LocationLink[]> {
async provideDefinition(document: TextDocument, position: Position, _token: CancellationToken): Promise<LocationLink[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: vscode.LocationLink[] = [];
const locLinks: LocationLink[] = [];
for (const link of fileLinks) {
if (link.originSelectionRange!.contains(position)) {
locLinks.push(link);
@@ -43,9 +56,9 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
return locLinks;
}
private async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
return withProgress({
location: vscode.ProgressLocation.Notification,
location: ProgressLocation.Notification,
cancellable: true,
title: 'Finding definitions'
}, async (progress, token) => {
@@ -55,6 +68,7 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
this.dbm,
uriString,
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
(src, _dest) => src === uriString
@@ -63,25 +77,26 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
}
}
export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider {
export class TemplateQueryReferenceProvider implements ReferenceProvider {
private cache: CachedOperation<FullLocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private qs: QueryRunner,
private dbm: DatabaseManager,
private queryStorageDir: string,
) {
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
}
async provideReferences(
document: vscode.TextDocument,
position: vscode.Position,
_context: vscode.ReferenceContext,
_token: vscode.CancellationToken
): Promise<vscode.Location[]> {
document: TextDocument,
position: Position,
_context: ReferenceContext,
_token: CancellationToken
): Promise<Location[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: vscode.Location[] = [];
const locLinks: Location[] = [];
for (const link of fileLinks) {
if (link.targetRange!.contains(position)) {
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
@@ -92,7 +107,7 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
return withProgress({
location: vscode.ProgressLocation.Notification,
location: ProgressLocation.Notification,
cancellable: true,
title: 'Finding references'
}, async (progress, token) => {
@@ -102,6 +117,7 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
this.dbm,
uriString,
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
(src, _dest) => src === uriString
@@ -110,41 +126,123 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
}
}
type QueryWithDb = {
query: QueryWithResults,
dbUri: Uri
};
export class TemplatePrintAstProvider {
private cache: CachedOperation<QueryWithResults | undefined>;
private cache: CachedOperation<QueryWithDb>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private qs: QueryRunner,
private dbm: DatabaseManager,
// Note: progress and token are only used if a cached value is not available
private progress: ProgressCallback,
private token: vscode.CancellationToken
private queryStorageDir: string,
) {
this.cache = new CachedOperation<QueryWithResults | undefined>(this.getAst.bind(this));
this.cache = new CachedOperation<QueryWithDb>(this.getAst.bind(this));
}
async provideAst(document?: vscode.TextDocument): Promise<AstBuilder | undefined> {
if (!document) {
return;
}
const queryResults = await this.cache.get(document.uri.toString());
if (!queryResults) {
return;
async provideAst(
progress: ProgressCallback,
token: CancellationToken,
fileUri?: Uri
): Promise<AstBuilder | undefined> {
if (!fileUri) {
throw new Error('Cannot view the AST. Please select a valid source file inside a CodeQL database.');
}
const { query, dbUri } = this.shouldCache()
? await this.cache.get(fileUri.toString(), progress, token)
: await this.getAst(fileUri.toString(), progress, token);
return new AstBuilder(
queryResults, this.cli,
this.dbm.findDatabaseItem(vscode.Uri.parse(queryResults.database.databaseUri!, true))!,
document.fileName
query, this.cli,
this.dbm.findDatabaseItem(dbUri)!,
fileUri,
);
}
private async getAst(uriString: string): Promise<QueryWithResults> {
const uri = vscode.Uri.parse(uriString, true);
private shouldCache() {
return !(isCanary() && NO_CACHE_AST_VIEWER.getValue<boolean>());
}
private async getAst(
uriString: string,
progress: ProgressCallback,
token: CancellationToken
): Promise<QueryWithDb> {
const uri = Uri.parse(uriString, true);
if (uri.scheme !== zipArchiveScheme) {
throw new Error('AST Viewing is only available for databases with zipped source archives.');
throw new Error('Cannot view the AST. Please select a valid source file inside a CodeQL database.');
}
const zippedArchive = decodeSourceArchiveUri(uri);
const sourceArchiveUri = encodeArchiveBasePath(zippedArchive.sourceArchiveZipPath);
const db = this.dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
if (!db) {
throw new Error('Can\'t infer database from the provided source.');
}
const qlpacks = await qlpackOfDatabase(this.cli, db);
const queries = await resolveQueries(this.cli, qlpacks, KeyType.PrintAstQuery);
if (queries.length > 1) {
throw new Error('Found multiple Print AST queries. Can\'t continue');
}
if (queries.length === 0) {
throw new Error('Did not find any Print AST queries. Can\'t continue');
}
const query = queries[0];
const templates: Record<string, string> = {
[TEMPLATE_NAME]:
zippedArchive.pathWithinSourceArchive
};
const initialInfo = await createInitialQueryInfo(
Uri.file(query),
{
name: db.name,
databaseUri: db.databaseUri.toString(),
},
false
);
return {
query: await this.qs.compileAndRunQueryAgainstDatabase(
db,
initialInfo,
this.queryStorageDir,
progress,
token,
templates
),
dbUri: db.databaseUri
};
}
}
export class TemplatePrintCfgProvider {
private cache: CachedOperation<[Uri, Record<string, string>] | undefined>;
constructor(
private cli: CodeQLCliServer,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<[Uri, Record<string, string>] | undefined>(this.getCfgUri.bind(this));
}
async provideCfgUri(document?: TextDocument): Promise<[Uri, Record<string, string>] | undefined> {
if (!document) {
return;
}
return await this.cache.get(document.uri.toString());
}
private async getCfgUri(uriString: string): Promise<[Uri, Record<string, string>]> {
const uri = Uri.parse(uriString, true);
if (uri.scheme !== zipArchiveScheme) {
throw new Error('CFG Viewing is only available for databases with zipped source archives.');
}
const zippedArchive = decodeSourceArchiveUri(uri);
@@ -157,36 +255,22 @@ export class TemplatePrintAstProvider {
const qlpack = await qlpackOfDatabase(this.cli, db);
if (!qlpack) {
throw new Error('Can\'t infer qlpack from database source archive');
throw new Error('Can\'t infer qlpack from database source archive.');
}
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintAstQuery);
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintCfgQuery);
if (queries.length > 1) {
throw new Error('Found multiple Print AST queries. Can\'t continue');
throw new Error(`Found multiple Print CFG queries. Can't continue. Make sure there is exacly one query with the tag ${KeyType.PrintCfgQuery}`);
}
if (queries.length === 0) {
throw new Error('Did not find any Print AST queries. Can\'t continue');
throw new Error(`Did not find any Print CFG queries. Can't continue. Make sure there is exacly one query with the tag ${KeyType.PrintCfgQuery}`);
}
const query = queries[0];
const templates: messages.TemplateDefinitions = {
[TEMPLATE_NAME]: {
values: {
tuples: [[{
stringValue: zippedArchive.pathWithinSourceArchive
}]]
}
}
const queryUri = Uri.file(queries[0]);
const templates: Record<string, string> = {
[TEMPLATE_NAME]: zippedArchive.pathWithinSourceArchive
};
return await compileAndRunQueryAgainstDatabase(
this.cli,
this.qs,
db,
false,
vscode.Uri.file(query),
this.progress,
this.token,
templates
);
return [queryUri, templates];
}
}

View File

@@ -1,33 +1,43 @@
import fetch, { Response } from 'node-fetch';
import * as unzipper from 'unzipper';
import { zip } from 'zip-a-folder';
import * as unzipper from 'unzipper';
import {
Uri,
CancellationToken,
commands,
window,
} from 'vscode';
import { CodeQLCliServer } from './cli';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as Octokit from '@octokit/rest';
import { retry } from '@octokit/plugin-retry';
import { DatabaseManager, DatabaseItem } from './databases';
import {
ProgressCallback,
showAndLogInformationMessage,
} from './helpers';
import {
reportStreamProgress,
ProgressCallback,
} from './commandRunner';
import { logger } from './logging';
import { tmpDir } from './helpers';
import { Credentials } from './authentication';
import { REPO_REGEX, getErrorMessage } from './pure/helpers-pure';
/**
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
*
* @param databasesManager the DatabaseManager
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function promptImportInternetDatabase(
databasesManager: DatabaseManager,
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
_: CancellationToken,
token: CancellationToken,
cli?: CodeQLCliServer
): Promise<DatabaseItem | undefined> {
const databaseUrl = await window.showInputBox({
prompt: 'Enter URL of zipfile of database to download',
@@ -40,53 +50,139 @@ export async function promptImportInternetDatabase(
const item = await databaseArchiveFetcher(
databaseUrl,
databasesManager,
{},
databaseManager,
storagePath,
progress
undefined,
progress,
token,
cli
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database downloaded and imported successfully.');
await commands.executeCommand('codeQLDatabases.focus');
void showAndLogInformationMessage('Database downloaded and imported successfully.');
}
return item;
}
/**
* Prompts a user to fetch a database from GitHub.
* User enters a GitHub repository and then the user is asked which language
* to download (if there is more than one)
*
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function promptImportGithubDatabase(
databaseManager: DatabaseManager,
storagePath: string,
credentials: Credentials | undefined,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer
): Promise<DatabaseItem | undefined> {
progress({
message: 'Choose repository',
step: 1,
maxStep: 2
});
const githubRepo = await window.showInputBox({
title: 'Enter a GitHub repository URL or "name with owner" (e.g. https://github.com/github/codeql or github/codeql)',
placeHolder: 'https://github.com/<owner>/<repo> or <owner>/<repo>',
ignoreFocusOut: true,
});
if (!githubRepo) {
return;
}
if (!looksLikeGithubRepo(githubRepo)) {
throw new Error(`Invalid GitHub repository: ${githubRepo}`);
}
const octokit = credentials ? await credentials.getOctokit(true) : new Octokit.Octokit({ retry });
const result = await convertGithubNwoToDatabaseUrl(githubRepo, octokit, progress);
if (!result) {
return;
}
const { databaseUrl, name, owner } = result;
/**
* The 'token' property of the token object returned by `octokit.auth()`.
* The object is undocumented, but looks something like this:
* {
* token: 'xxxx',
* tokenType: 'oauth',
* type: 'token',
* }
* We only need the actual token string.
*/
const octokitToken = (await octokit.auth() as { token: string })?.token;
const item = await databaseArchiveFetcher(
databaseUrl,
{ 'Accept': 'application/zip', 'Authorization': octokitToken ? `Bearer ${octokitToken}` : '' },
databaseManager,
storagePath,
`${owner}/${name}`,
progress,
token,
cli
);
if (item) {
await commands.executeCommand('codeQLDatabases.focus');
void showAndLogInformationMessage('Database downloaded and imported successfully.');
return item;
}
return;
}
/**
* Prompts a user to fetch a database from lgtm.
* User enters a project url and then the user is asked which language
* to download (if there is more than one)
*
* @param databasesManager the DatabaseManager
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function promptImportLgtmDatabase(
databasesManager: DatabaseManager,
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
_: CancellationToken
token: CancellationToken,
cli?: CodeQLCliServer
): Promise<DatabaseItem | undefined> {
progress({
message: 'Choose project',
step: 1,
maxStep: 2
});
const lgtmUrl = await window.showInputBox({
prompt:
'Enter the project URL on LGTM (e.g., https://lgtm.com/projects/g/github/codeql)',
'Enter the project slug or URL on LGTM (e.g., g/github/codeql or https://lgtm.com/projects/g/github/codeql)',
});
if (!lgtmUrl) {
return;
}
if (looksLikeLgtmUrl(lgtmUrl)) {
const databaseUrl = await convertToDatabaseUrl(lgtmUrl);
const databaseUrl = await convertLgtmUrlToDatabaseUrl(lgtmUrl, progress);
if (databaseUrl) {
const item = await databaseArchiveFetcher(
databaseUrl,
databasesManager,
{},
databaseManager,
storagePath,
progress
undefined,
progress,
token,
cli
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database downloaded and imported successfully.');
await commands.executeCommand('codeQLDatabases.focus');
void showAndLogInformationMessage('Database downloaded and imported successfully.');
}
return item;
}
@@ -96,34 +192,49 @@ export async function promptImportLgtmDatabase(
return;
}
export async function retrieveCanonicalRepoName(lgtmUrl: string) {
const givenRepoName = extractProjectSlug(lgtmUrl);
const response = await checkForFailingResponse(await fetch(`https://api.github.com/repos/${givenRepoName}`), 'Failed to locate the repository on github');
const repo = await response.json();
if (!repo || !repo.full_name) {
return;
}
return repo.full_name;
}
/**
* Imports a database from a local archive.
*
* @param databaseUrl the file url of the archive to import
* @param databasesManager the DatabaseManager
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function importArchiveDatabase(
databaseUrl: string,
databasesManager: DatabaseManager,
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
_: CancellationToken,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem | undefined> {
try {
const item = await databaseArchiveFetcher(
databaseUrl,
databasesManager,
{},
databaseManager,
storagePath,
progress
undefined,
progress,
token,
cli
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database unzipped and imported successfully.');
await commands.executeCommand('codeQLDatabases.focus');
void showAndLogInformationMessage('Database unzipped and imported successfully.');
}
return item;
} catch (e) {
if (e.message.includes('unexpected end of file')) {
if (getErrorMessage(e).includes('unexpected end of file')) {
throw new Error('Database is corrupt or too large. Try unzipping outside of VS Code and importing the unzipped folder instead.');
} else {
// delegate
@@ -137,17 +248,24 @@ export async function importArchiveDatabase(
* or in the local filesystem.
*
* @param databaseUrl URL from which to grab the database
* @param databasesManager the DatabaseManager
* @param requestHeaders Headers to send with the request
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
* @param progressCallback optional callback to send progress messages to
* @param nameOverride a name for the database that overrides the default
* @param progress callback to send progress messages to
* @param token cancellation token
*/
async function databaseArchiveFetcher(
databaseUrl: string,
databasesManager: DatabaseManager,
requestHeaders: { [key: string]: string },
databaseManager: DatabaseManager,
storagePath: string,
progressCallback?: ProgressCallback
nameOverride: string | undefined,
progress: ProgressCallback,
token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem> {
progressCallback?.({
progress({
message: 'Getting database',
step: 1,
maxStep: 4,
@@ -159,12 +277,12 @@ async function databaseArchiveFetcher(
const unzipPath = await getStorageFolder(storagePath, databaseUrl);
if (isFile(databaseUrl)) {
await readAndUnzip(databaseUrl, unzipPath);
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
} else {
await fetchAndUnzip(databaseUrl, unzipPath, progressCallback);
await fetchAndUnzip(databaseUrl, requestHeaders, unzipPath, cli, progress);
}
progressCallback?.({
progress({
message: 'Opening database',
step: 3,
maxStep: 4,
@@ -177,15 +295,15 @@ async function databaseArchiveFetcher(
'codeql-database.yml'
);
if (dbPath) {
progressCallback?.({
progress({
message: 'Validating and fixing source location',
step: 4,
maxStep: 4,
});
await ensureZippedSourceLocation(dbPath);
const item = await databasesManager.openDatabase(Uri.file(dbPath));
databasesManager.setCurrentDatabaseItem(item);
const item = await databaseManager.openDatabase(progress, token, Uri.file(dbPath), nameOverride);
await databaseManager.setCurrentDatabaseItem(item);
return item;
} else {
throw new Error('Database not found in archive.');
@@ -232,48 +350,78 @@ function validateHttpsUrl(databaseUrl: string) {
}
}
async function readAndUnzip(databaseUrl: string, unzipPath: string) {
const databaseFile = Uri.parse(databaseUrl).fsPath;
const directory = await unzipper.Open.file(databaseFile);
await directory.extract({ path: unzipPath });
async function readAndUnzip(
zipUrl: string,
unzipPath: string,
cli?: CodeQLCliServer,
progress?: ProgressCallback
) {
// TODO: Providing progress as the file is unzipped is currently blocked
// on https://github.com/ZJONSSON/node-unzipper/issues/222
const zipFile = Uri.parse(zipUrl).fsPath;
progress?.({
maxStep: 10,
step: 9,
message: `Unzipping into ${path.basename(unzipPath)}`
});
if (cli && await cli.cliConstraints.supportsDatabaseUnbundle()) {
// Use the `database unbundle` command if the installed cli version supports it
await cli.databaseUnbundle(zipFile, unzipPath);
} else {
// Must get the zip central directory since streaming the
// zip contents may not have correct local file headers.
// Instead, we can only rely on the central directory.
const directory = await unzipper.Open.file(zipFile);
await directory.extract({ path: unzipPath });
}
}
async function fetchAndUnzip(
databaseUrl: string,
requestHeaders: { [key: string]: string },
unzipPath: string,
progressCallback?: ProgressCallback
cli?: CodeQLCliServer,
progress?: ProgressCallback
) {
const response = await fetch(databaseUrl);
// Although it is possible to download and stream directly to an unzipped directory,
// we need to avoid this for two reasons. The central directory is located at the
// end of the zip file. It is the source of truth of the content locations. Individual
// file headers may be incorrect. Additionally, saving to file first will reduce memory
// pressure compared with unzipping while downloading the archive.
await checkForFailingResponse(response);
const archivePath = path.join(tmpDir.name, `archive-${Date.now()}.zip`);
const unzipStream = unzipper.Extract({
path: unzipPath,
});
progressCallback?.({
progress?.({
maxStep: 3,
message: 'Unzipping database',
step: 2,
});
await new Promise((resolve, reject) => {
const handler = (err: Error) => {
if (err.message.startsWith('invalid signature')) {
reject(new Error('Not a valid archive.'));
} else {
reject(err);
}
};
response.body.on('error', handler);
unzipStream.on('error', handler);
unzipStream.on('close', resolve);
response.body.pipe(unzipStream);
message: 'Downloading database',
step: 1,
});
const response = await checkForFailingResponse(
await fetch(databaseUrl, { headers: requestHeaders }),
'Error downloading database'
);
const archiveFileStream = fs.createWriteStream(archivePath);
const contentLength = response.headers.get('content-length');
const totalNumBytes = contentLength ? parseInt(contentLength, 10) : undefined;
reportStreamProgress(response.body, 'Downloading database', totalNumBytes, progress);
await new Promise((resolve, reject) =>
response.body.pipe(archiveFileStream)
.on('finish', resolve)
.on('error', reject)
);
await readAndUnzip(Uri.file(archivePath).toString(true), unzipPath, cli, progress);
// remove archivePath eagerly since these archives can be large.
await fs.remove(archivePath);
}
async function checkForFailingResponse(response: Response): Promise<void | never> {
async function checkForFailingResponse(response: Response, errorMessage: string): Promise<Response | never> {
if (response.ok) {
return;
return response;
}
// An error downloading the database. Attempt to extract the resaon behind it.
@@ -285,7 +433,7 @@ async function checkForFailingResponse(response: Response): Promise<void | never
} catch (e) {
msg = text;
}
throw new Error(`Error downloading database.\n\nReason: ${msg}`);
throw new Error(`${errorMessage}.\n\nReason: ${msg}`);
}
function isFile(databaseUrl: string) {
@@ -322,15 +470,98 @@ export async function findDirWithFile(
return;
}
/**
* The URL pattern is https://github.com/{owner}/{name}/{subpages}.
*
* This function accepts any URL that matches the pattern above. It also accepts just the
* name with owner (NWO): `<owner>/<repo>`.
*
* @param githubRepo The GitHub repository URL or NWO
*
* @return true if this looks like a valid GitHub repository URL or NWO
*/
export function looksLikeGithubRepo(
githubRepo: string | undefined
): githubRepo is string {
if (!githubRepo) {
return false;
}
if (REPO_REGEX.test(githubRepo) || convertGitHubUrlToNwo(githubRepo)) {
return true;
}
return false;
}
/**
* Converts a GitHub repository URL to the corresponding NWO.
* @param githubUrl The GitHub repository URL
* @return The corresponding NWO, or undefined if the URL is not valid
*/
function convertGitHubUrlToNwo(githubUrl: string): string | undefined {
try {
const uri = Uri.parse(githubUrl, true);
if (uri.scheme !== 'https') {
return;
}
if (uri.authority !== 'github.com' && uri.authority !== 'www.github.com') {
return;
}
const paths = uri.path.split('/').filter((segment: string) => segment);
const nwo = `${paths[0]}/${paths[1]}`;
if (REPO_REGEX.test(nwo)) {
return nwo;
}
return;
} catch (e) {
// Ignore the error here, since we catch failures at a higher level.
// In particular: returning undefined leads to an error in 'promptImportGithubDatabase'.
return;
}
}
export async function convertGithubNwoToDatabaseUrl(
githubRepo: string,
octokit: Octokit.Octokit,
progress: ProgressCallback): Promise<{
databaseUrl: string,
owner: string,
name: string
} | undefined> {
try {
const nwo = convertGitHubUrlToNwo(githubRepo) || githubRepo;
const [owner, repo] = nwo.split('/');
const response = await octokit.request('GET /repos/:owner/:repo/code-scanning/codeql/databases', { owner, repo });
const languages = response.data.map((db: any) => db.language);
const language = await promptForLanguage(languages, progress);
if (!language) {
return;
}
return {
databaseUrl: `https://api.github.com/repos/${owner}/${repo}/code-scanning/codeql/databases/${language}`,
owner,
name: repo
};
} catch (e) {
void logger.log(`Error: ${getErrorMessage(e)}`);
throw new Error(`Unable to get database for '${githubRepo}'`);
}
}
/**
* The URL pattern is https://lgtm.com/projects/{provider}/{org}/{name}/{irrelevant-subpages}.
* There are several possibilities for the provider: in addition to GitHub.com(g),
* There are several possibilities for the provider: in addition to GitHub.com (g),
* LGTM currently hosts projects from Bitbucket (b), GitLab (gl) and plain git (git).
*
* After the {provider}/{org}/{name} path components, there may be the components
* related to sub pages.
* This function accepts any url that matches the pattern above. It also accepts the
* raw project slug, e.g., `g/myorg/myproject`
*
* This function accepts any url that matches the patter above
* After the `{provider}/{org}/{name}` path components, there may be the components
* related to sub pages.
*
* @param lgtmUrl The URL to the lgtm project
*
@@ -342,6 +573,10 @@ export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string
return false;
}
if (convertRawLgtmSlug(lgtmUrl)) {
return true;
}
try {
const uri = Uri.parse(lgtmUrl, true);
if (uri.scheme !== 'https') {
@@ -352,29 +587,60 @@ export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string
return false;
}
const paths = uri.path.split('/').filter((segment) => segment);
const paths = uri.path.split('/').filter((segment: string) => segment);
return paths.length >= 4 && paths[0] === 'projects';
} catch (e) {
return false;
}
}
function convertRawLgtmSlug(maybeSlug: string): string | undefined {
if (!maybeSlug) {
return;
}
const segments = maybeSlug.split('/');
const providers = ['g', 'gl', 'b', 'git'];
if (segments.length === 3 && providers.includes(segments[0])) {
return `https://lgtm.com/projects/${maybeSlug}`;
}
return;
}
function extractProjectSlug(lgtmUrl: string): string | undefined {
// Only matches the '/g/' provider (github)
const re = new RegExp('https://lgtm.com/projects/g/(.*[^/])');
const match = lgtmUrl.match(re);
if (!match) {
return;
}
return match[1];
}
// exported for testing
export async function convertToDatabaseUrl(lgtmUrl: string) {
export async function convertLgtmUrlToDatabaseUrl(
lgtmUrl: string,
progress: ProgressCallback) {
try {
const uri = Uri.parse(lgtmUrl, true);
const paths = ['api', 'v1.0'].concat(
uri.path.split('/').filter((segment) => segment)
).slice(0, 6);
const projectUrl = `https://lgtm.com/${paths.join('/')}`;
const projectResponse = await fetch(projectUrl);
const projectJson = await projectResponse.json();
lgtmUrl = convertRawLgtmSlug(lgtmUrl) || lgtmUrl;
let projectJson = await downloadLgtmProjectMetadata(lgtmUrl);
if (projectJson.code === 404) {
throw new Error();
// fallback check for github repositories with same name but different case
// will fail for other providers
let canonicalName = await retrieveCanonicalRepoName(lgtmUrl);
if (!canonicalName) {
throw new Error(`Project was not found at ${lgtmUrl}.`);
}
canonicalName = convertRawLgtmSlug(`g/${canonicalName}`);
projectJson = await downloadLgtmProjectMetadata(canonicalName);
if (projectJson.code === 404) {
throw new Error('Failed to download project from LGTM.');
}
}
const language = await promptForLanguage(projectJson);
const languages = projectJson?.languages?.map((lang: { language: string }) => lang.language) || [];
const language = await promptForLanguage(languages, progress);
if (!language) {
return;
}
@@ -386,25 +652,43 @@ export async function convertToDatabaseUrl(lgtmUrl: string) {
language,
].join('/')}`;
} catch (e) {
logger.log(`Error: ${e.message}`);
void logger.log(`Error: ${getErrorMessage(e)}`);
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
}
}
async function downloadLgtmProjectMetadata(lgtmUrl: string): Promise<any> {
const uri = Uri.parse(lgtmUrl, true);
const paths = ['api', 'v1.0'].concat(
uri.path.split('/').filter((segment: string) => segment)
).slice(0, 6);
const projectUrl = `https://lgtm.com/${paths.join('/')}`;
const projectResponse = await fetch(projectUrl);
return projectResponse.json();
}
async function promptForLanguage(
projectJson: any
languages: string[],
progress: ProgressCallback
): Promise<string | undefined> {
if (!projectJson?.languages?.length) {
return;
progress({
message: 'Choose language',
step: 2,
maxStep: 2
});
if (!languages.length) {
throw new Error('No databases found');
}
if (projectJson.languages.length === 1) {
return projectJson.languages[0].language;
if (languages.length === 1) {
return languages[0];
}
return await window.showQuickPick(
projectJson.languages.map((lang: { language: string }) => lang.language), {
placeHolder: 'Select the database language to download:'
}
languages,
{
placeHolder: 'Select the database language to download:',
ignoreFocusOut: true,
}
);
}

View File

@@ -1,5 +1,5 @@
import * as path from 'path';
import { DisposableObject } from './vscode-utils/disposable-object';
import { DisposableObject } from './pure/disposable-object';
import {
Event,
EventEmitter,
@@ -12,33 +12,33 @@ import {
} from 'vscode';
import * as fs from 'fs-extra';
import * as cli from './cli';
import {
DatabaseChangedEvent,
DatabaseItem,
DatabaseManager,
getUpgradesDirectories,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from './databases';
import {
commandRunner,
commandRunnerWithProgress,
getOnDiskWorkspaceFolders,
ProgressCallback,
} from './commandRunner';
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
showAndLogErrorMessage
} from './helpers';
import { logger } from './logging';
import { clearCacheInDatabase } from './run-queries';
import * as qsClient from './queryserver-client';
import { upgradeDatabase } from './upgrades';
import {
importArchiveDatabase,
promptImportGithubDatabase,
promptImportInternetDatabase,
promptImportLgtmDatabase,
} from './databaseFetcher';
import { CancellationToken } from 'vscode';
import { asyncFilter } from './pure/helpers-pure';
import { asyncFilter, getErrorMessage } from './pure/helpers-pure';
import { Credentials } from './authentication';
import { QueryRunner } from './queryRunner';
import { isCanary } from './config';
type ThemableIconPath = { light: string; dark: string } | string;
@@ -81,7 +81,7 @@ class DatabaseTreeDataProvider extends DisposableObject
implements TreeDataProvider<DatabaseItem> {
private _sortOrder = SortOrder.NameAsc;
private readonly _onDidChangeTreeData = new EventEmitter<DatabaseItem | undefined>();
private readonly _onDidChangeTreeData = this.push(new EventEmitter<DatabaseItem | undefined>());
private currentDatabaseItem: DatabaseItem | undefined;
constructor(
@@ -109,7 +109,7 @@ class DatabaseTreeDataProvider extends DisposableObject
}
private handleDidChangeDatabaseItem = (event: DatabaseChangedEvent): void => {
// Note that events from the databse manager are instances of DatabaseChangedEvent
// 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.
@@ -136,6 +136,7 @@ class DatabaseTreeDataProvider extends DisposableObject
this.extensionPath,
SELECTED_DATABASE_ICON
);
item.contextValue = 'currentDatabase';
} else if (element.error !== undefined) {
item.iconPath = joinThemableIconPath(
this.extensionPath,
@@ -143,6 +144,7 @@ class DatabaseTreeDataProvider extends DisposableObject
);
}
item.tooltip = element.databaseUri.fsPath;
item.description = element.language;
return item;
}
@@ -179,7 +181,7 @@ class DatabaseTreeDataProvider extends DisposableObject
public set sortOrder(newSortOrder: SortOrder) {
this._sortOrder = newSortOrder;
this._onDidChangeTreeData.fire();
this._onDidChangeTreeData.fire(undefined);
}
}
@@ -215,11 +217,11 @@ export class DatabaseUI extends DisposableObject {
private treeDataProvider: DatabaseTreeDataProvider;
public constructor(
private cliserver: cli.CodeQLCliServer,
private databaseManager: DatabaseManager,
private readonly queryServer: qsClient.QueryServerClient | undefined,
private readonly queryServer: QueryRunner | undefined,
private readonly storagePath: string,
readonly extensionPath: string
readonly extensionPath: string,
private readonly getCredentials: () => Promise<Credentials>
) {
super();
@@ -235,7 +237,7 @@ export class DatabaseUI extends DisposableObject {
}
init() {
logger.log('Registering database panel commands.');
void logger.log('Registering database panel commands.');
this.push(
commandRunnerWithProgress(
'codeQL.setCurrentDatabase',
@@ -291,6 +293,20 @@ export class DatabaseUI extends DisposableObject {
}
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseGithub',
async (
progress: ProgressCallback,
token: CancellationToken
) => {
const credentials = isCanary() ? await this.getCredentials() : undefined;
await this.handleChooseDatabaseGithub(credentials, progress, token);
},
{
title: 'Adding database from GitHub',
})
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseLgtm',
@@ -318,9 +334,13 @@ export class DatabaseUI extends DisposableObject {
)
);
this.push(
commandRunner(
commandRunnerWithProgress(
'codeQLDatabases.removeDatabase',
this.handleRemoveDatabase
this.handleRemoveDatabase,
{
title: 'Removing database',
cancellable: false
}
)
);
this.push(
@@ -345,6 +365,12 @@ export class DatabaseUI extends DisposableObject {
this.handleOpenFolder
)
);
this.push(
commandRunner(
'codeQLDatabases.addDatabaseSource',
this.handleAddSource
)
);
this.push(
commandRunner(
'codeQLDatabases.removeOrphanedDatabases',
@@ -362,18 +388,27 @@ export class DatabaseUI extends DisposableObject {
handleChooseDatabaseFolder = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> => {
): Promise<void> => {
try {
return await this.chooseAndSetDatabase(true, progress, token);
await this.chooseAndSetDatabase(true, progress, token);
} catch (e) {
showAndLogErrorMessage(e.message);
return undefined;
void showAndLogErrorMessage(getErrorMessage(e));
}
};
handleRemoveOrphanedDatabases = async (): Promise<void> => {
logger.log('Removing orphaned databases from workspace storage.');
let dbDirs =
void logger.log('Removing orphaned databases from workspace storage.');
let dbDirs = undefined;
if (
!(await fs.pathExists(this.storagePath)) ||
!(await fs.stat(this.storagePath)).isDirectory()
) {
void logger.log('Missing or invalid storage directory. Not trying to remove orphaned databases.');
return;
}
dbDirs =
// read directory
(await fs.readdir(this.storagePath, { withFileTypes: true }))
// remove non-directories
@@ -390,7 +425,7 @@ export class DatabaseUI extends DisposableObject {
dbDirs = await asyncFilter(dbDirs, isLikelyDatabaseRoot);
if (!dbDirs.length) {
logger.log('No orphaned databases found.');
void logger.log('No orphaned databases found.');
return;
}
@@ -399,8 +434,8 @@ export class DatabaseUI extends DisposableObject {
await Promise.all(
dbDirs.map(async dbDir => {
try {
logger.log(`Deleting orphaned database '${dbDir}'.`);
await fs.rmdir(dbDir, { recursive: true } as any); // typings doesn't recognize the options argument
void logger.log(`Deleting orphaned database '${dbDir}'.`);
await fs.remove(dbDir);
} catch (e) {
failures.push(`${path.basename(dbDir)}`);
}
@@ -409,10 +444,9 @@ export class DatabaseUI extends DisposableObject {
if (failures.length) {
const dirname = path.dirname(failures[0]);
showAndLogErrorMessage(
`Failed to delete unused databases:\n ${
failures.join('\n ')
}\n. To delete unused databases, please remove them manually from the storage folder ${dirname}.`
void showAndLogErrorMessage(
`Failed to delete unused databases (${failures.join(', ')
}).\nTo delete unused databases, please remove them manually from the storage folder ${dirname}.`
);
}
};
@@ -421,26 +455,39 @@ export class DatabaseUI extends DisposableObject {
handleChooseDatabaseArchive = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> => {
): Promise<void> => {
try {
return await this.chooseAndSetDatabase(false, progress, token);
await this.chooseAndSetDatabase(false, progress, token);
} catch (e) {
showAndLogErrorMessage(e.message);
return undefined;
void showAndLogErrorMessage(getErrorMessage(e));
}
};
handleChooseDatabaseInternet = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<
DatabaseItem | undefined
> => {
): Promise<DatabaseItem | undefined> => {
return await promptImportInternetDatabase(
this.databaseManager,
this.storagePath,
progress,
token
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
);
};
@@ -452,7 +499,8 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager,
this.storagePath,
progress,
token
token,
this.queryServer?.cliServer
);
};
@@ -523,25 +571,9 @@ export class DatabaseUI extends DisposableObject {
}
// Search for upgrade scripts in any workspace folders available
const searchPath: string[] = getOnDiskWorkspaceFolders();
const upgradeInfo = await this.cliserver.resolveUpgrades(
databaseItem.contents.dbSchemeUri.fsPath,
searchPath
);
const { scripts, finalDbscheme } = upgradeInfo;
if (finalDbscheme === undefined) {
throw new Error('Could not determine target dbscheme to upgrade to.');
}
const targetDbSchemeUri = Uri.file(finalDbscheme);
await upgradeDatabase(
this.queryServer,
await this.queryServer.upgradeDatabaseExplicit(
databaseItem,
targetDbSchemeUri,
getUpgradesDirectories(scripts),
progress,
token
);
@@ -555,8 +587,7 @@ export class DatabaseUI extends DisposableObject {
this.queryServer !== undefined &&
this.databaseManager.currentDatabaseItem !== undefined
) {
await clearCacheInDatabase(
this.queryServer,
await this.queryServer.clearCacheInDatabase(
this.databaseManager.currentDatabaseItem,
progress,
token
@@ -577,31 +608,32 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager,
this.storagePath,
progress,
token
token,
this.queryServer?.cliServer
);
} else {
await this.setCurrentDatabase(uri);
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 ${path.basename(uri.fsPath)}. Reason: ${
e.message
}`
`Could not set database to ${path.basename(uri.fsPath)}. Reason: ${getErrorMessage(e)}`
);
}
};
private handleRemoveDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined
): Promise<void> => {
if (multiSelect?.length) {
multiSelect.forEach((dbItem) =>
this.databaseManager.removeDatabaseItem(dbItem)
);
await Promise.all(multiSelect.map((dbItem) =>
this.databaseManager.removeDatabaseItem(progress, token, dbItem)
));
} else {
this.databaseManager.removeDatabaseItem(databaseItem);
await this.databaseManager.removeDatabaseItem(progress, token, databaseItem);
}
};
@@ -617,7 +649,7 @@ export class DatabaseUI extends DisposableObject {
});
if (newName) {
this.databaseManager.renameDatabaseItem(databaseItem, newName);
await this.databaseManager.renameDatabaseItem(databaseItem, newName);
}
};
@@ -634,6 +666,24 @@ export class DatabaseUI extends DisposableObject {
}
};
/**
* 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 (
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined
): Promise<void> => {
if (multiSelect?.length) {
for (const dbItem of multiSelect) {
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
}
} else {
await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem);
}
};
/**
* Return the current database directory. If we don't already have a
* current database, ask the user for one, and return that, or
@@ -651,11 +701,13 @@ export class DatabaseUI extends DisposableObject {
}
private async setCurrentDatabase(
progress: ProgressCallback,
token: CancellationToken,
uri: Uri
): Promise<DatabaseItem | undefined> {
let databaseItem = this.databaseManager.findDatabaseItem(uri);
if (databaseItem === undefined) {
databaseItem = await this.databaseManager.openDatabase(uri);
databaseItem = await this.databaseManager.openDatabase(progress, token, uri);
}
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
@@ -672,7 +724,6 @@ export class DatabaseUI extends DisposableObject {
token: CancellationToken,
): Promise<DatabaseItem | undefined> {
const uri = await chooseDatabaseDir(byFolder);
if (!uri) {
return undefined;
}
@@ -680,7 +731,7 @@ export class DatabaseUI extends DisposableObject {
if (byFolder) {
const fixedUri = await this.fixDbUri(uri);
// we are selecting a database folder
return await this.setCurrentDatabase(fixedUri);
return await this.setCurrentDatabase(progress, token, fixedUri);
} else {
// we are selecting a database archive. Must unzip into a workspace-controlled area
// before importing.
@@ -689,7 +740,8 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager,
this.storagePath,
progress,
token
token,
this.queryServer?.cliServer
);
}
}
@@ -698,10 +750,10 @@ export class DatabaseUI extends DisposableObject {
* Perform some heuristics to ensure a proper database location is chosen.
*
* 1. If the selected URI to add is a file, choose the containing directory
* 2. If the selected URI is a directory matching db-*, choose the containing directory
* 2. If the selected URI appears to be a db language folder, choose the containing directory
* 3. choose the current directory
*
* @param uri a URI that is a datbase folder or inside it
* @param uri a URI that is a database folder or inside it
*
* @return the actual database folder found by using the heuristics above.
*/
@@ -711,7 +763,7 @@ export class DatabaseUI extends DisposableObject {
dbPath = path.dirname(dbPath);
}
if (isLikelyDbLanguageFolder(dbPath)) {
if (await isLikelyDbLanguageFolder(dbPath)) {
dbPath = path.dirname(dbPath);
}
return Uri.file(dbPath);

View File

@@ -4,11 +4,21 @@ import * as path from 'path';
import * as vscode from 'vscode';
import * as cli from './cli';
import { ExtensionContext } from 'vscode';
import { showAndLogErrorMessage, showAndLogWarningMessage, showAndLogInformationMessage } from './helpers';
import {
showAndLogErrorMessage,
showAndLogWarningMessage,
showAndLogInformationMessage,
isLikelyDatabaseRoot
} from './helpers';
import {
ProgressCallback,
withProgress
} from './commandRunner';
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
import { DisposableObject } from './vscode-utils/disposable-object';
import { QueryServerConfig } from './config';
import { DisposableObject } from './pure/disposable-object';
import { Logger, logger } from './logging';
import { getErrorMessage } from './pure/helpers-pure';
import { QueryRunner } from './queryRunner';
/**
* databases.ts
@@ -36,11 +46,13 @@ export interface DatabaseOptions {
displayName?: string;
ignoreSourceArchive?: boolean;
dateAdded?: number | undefined;
language?: string;
}
interface FullDatabaseOptions extends DatabaseOptions {
export interface FullDatabaseOptions extends DatabaseOptions {
ignoreSourceArchive: boolean;
dateAdded: number | undefined;
language: string | undefined;
}
interface PersistedDatabaseItem {
@@ -103,30 +115,31 @@ async function findDataset(parentDirectory: string): Promise<vscode.Uri> {
const dbAbsolutePath = path.join(parentDirectory, dbRelativePaths[0]);
if (dbRelativePaths.length > 1) {
showAndLogWarningMessage(`Found multiple dataset directories in database, using '${dbAbsolutePath}'.`);
void showAndLogWarningMessage(`Found multiple dataset directories in database, using '${dbAbsolutePath}'.`);
}
return vscode.Uri.file(dbAbsolutePath);
}
async function findSourceArchive(
// exported for testing
export async function findSourceArchive(
databasePath: string, silent = false
): Promise<vscode.Uri | undefined> {
const relativePaths = ['src', 'output/src_archive'];
for (const relativePath of relativePaths) {
const basePath = path.join(databasePath, relativePath);
const zipPath = basePath + '.zip';
if (await fs.pathExists(basePath)) {
return vscode.Uri.file(basePath);
} else if (await fs.pathExists(zipPath)) {
// Prefer using a zip archive over a directory.
if (await fs.pathExists(zipPath)) {
return encodeArchiveBasePath(zipPath);
} else if (await fs.pathExists(basePath)) {
return vscode.Uri.file(basePath);
}
}
if (!silent) {
showAndLogInformationMessage(
void showAndLogInformationMessage(
`Could not find source archive for database '${databasePath}'. Assuming paths are absolute.`
);
}
@@ -134,7 +147,7 @@ async function findSourceArchive(
}
async function resolveDatabase(
databasePath: string
databasePath: string,
): Promise<DatabaseContents> {
const name = path.basename(databasePath);
@@ -149,7 +162,6 @@ async function resolveDatabase(
datasetUri,
sourceArchiveUri
};
}
/** Gets the relative paths of all `.dbscheme` files in the given directory. */
@@ -157,7 +169,9 @@ async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
return await glob('*.dbscheme', { cwd: dbDirectory });
}
async function resolveDatabaseContents(uri: vscode.Uri): Promise<DatabaseContents> {
async function resolveDatabaseContents(
uri: vscode.Uri,
): Promise<DatabaseContents> {
if (uri.scheme !== 'file') {
throw new Error(`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`);
}
@@ -193,6 +207,9 @@ export interface DatabaseItem {
readonly databaseUri: vscode.Uri;
/** The name of the database to be displayed in the UI */
name: string;
/** The primary language of the database or empty string if unknown */
readonly language: string;
/** The URI of the database's source archive, or `undefined` if no source archive is to be used. */
readonly sourceArchive: vscode.Uri | undefined;
/**
@@ -243,12 +260,27 @@ export interface DatabaseItem {
* Returns the root uri of the virtual filesystem for this database's source archive,
* as displayed in the filesystem explorer.
*/
getSourceArchiveExplorerUri(): vscode.Uri | undefined;
getSourceArchiveExplorerUri(): vscode.Uri;
/**
* Holds if `uri` belongs to this database's source archive.
*/
belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean;
/**
* Whether the database may be affected by test execution for the given path.
*/
isAffectedByTest(testPath: string): Promise<boolean>;
/**
* Gets the state of this database, to be persisted in the workspace state.
*/
getPersistedState(): PersistedDatabaseItem;
/**
* Verifies that this database item has a zipped source folder. Returns an error message if it does not.
*/
verifyZippedSources(): string | undefined;
}
export enum DatabaseEventKind {
@@ -326,14 +358,12 @@ export class DatabaseItemImpl implements DatabaseItem {
try {
this._contents = await resolveDatabaseContents(this.databaseUri);
this._error = undefined;
}
catch (e) {
} catch (e) {
this._contents = undefined;
this._error = e;
this._error = e instanceof Error ? e : new Error(String(e));
throw e;
}
}
finally {
} finally {
this.onChanged({
kind: DatabaseEventKind.Refresh,
item: this
@@ -427,16 +457,33 @@ export class DatabaseItemImpl implements DatabaseItem {
return dbInfo.datasetFolder;
}
public get language() {
return this.options.language || '';
}
/**
* Returns the root uri of the virtual filesystem for this database's source archive.
*/
public getSourceArchiveExplorerUri(): vscode.Uri | undefined {
public getSourceArchiveExplorerUri(): vscode.Uri {
const sourceArchive = this.sourceArchive;
if (sourceArchive === undefined || !sourceArchive.fsPath.endsWith('.zip'))
return undefined;
if (sourceArchive === undefined || !sourceArchive.fsPath.endsWith('.zip')) {
throw new Error(this.verifyZippedSources());
}
return encodeArchiveBasePath(sourceArchive.fsPath);
}
public verifyZippedSources(): string | undefined {
const sourceArchive = this.sourceArchive;
if (sourceArchive === undefined) {
return `${this.name} has no source archive.`;
}
if (!sourceArchive.fsPath.endsWith('.zip')) {
return `${this.name} has a source folder that is unzipped.`;
}
return;
}
/**
* Holds if `uri` belongs to this database's source archive.
*/
@@ -446,6 +493,27 @@ export class DatabaseItemImpl implements DatabaseItem {
return uri.scheme === zipArchiveScheme &&
decodeSourceArchiveUri(uri).sourceArchiveZipPath === this.sourceArchive.fsPath;
}
public async isAffectedByTest(testPath: string): Promise<boolean> {
const databasePath = this.databaseUri.fsPath;
if (!databasePath.endsWith('.testproj')) {
return false;
}
try {
const stats = await fs.stat(testPath);
if (stats.isDirectory()) {
return !path.relative(testPath, databasePath).startsWith('..');
} else {
// database for /one/two/three/test.ql is at /one/two/three/three.testproj
const testdir = path.dirname(testPath);
const testdirbase = path.basename(testdir);
return databasePath == path.join(testdir, testdirbase + '.testproj');
}
} catch {
// No information available for test path - assume database is unaffected.
return false;
}
}
}
/**
@@ -456,7 +524,7 @@ export class DatabaseItemImpl implements DatabaseItem {
function eventFired<T>(event: vscode.Event<T>, timeoutMs = 1000): Promise<T | undefined> {
return new Promise((res, _rej) => {
const timeout = setTimeout(() => {
logger.log(`Waiting for event ${event} timed out after ${timeoutMs}ms`);
void logger.log(`Waiting for event ${event} timed out after ${timeoutMs}ms`);
res(undefined);
dispose();
}, timeoutMs);
@@ -479,43 +547,63 @@ export class DatabaseManager extends DisposableObject {
private readonly _onDidChangeCurrentDatabaseItem = this.push(new vscode.EventEmitter<DatabaseChangedEvent>());
readonly onDidChangeCurrentDatabaseItem = this._onDidChangeCurrentDatabaseItem.event;
private readonly _databaseItems: DatabaseItemImpl[] = [];
private readonly _databaseItems: DatabaseItem[] = [];
private _currentDatabaseItem: DatabaseItem | undefined = undefined;
constructor(
private ctx: ExtensionContext,
public config: QueryServerConfig,
private readonly ctx: ExtensionContext,
private readonly qs: QueryRunner,
private readonly cli: cli.CodeQLCliServer,
public logger: Logger
) {
super();
this.loadPersistedState(); // Let this run async.
qs.onStart(this.reregisterDatabases.bind(this));
}
public async openDatabase(
uri: vscode.Uri, options?: DatabaseOptions
progress: ProgressCallback,
token: vscode.CancellationToken,
uri: vscode.Uri,
displayName?: string
): Promise<DatabaseItem> {
const contents = await resolveDatabaseContents(uri);
const realOptions = options || {};
// Ignore the source archive for QLTest databases by default.
const isQLTestDatabase = path.extname(uri.fsPath) === '.testproj';
const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive: (realOptions.ignoreSourceArchive !== undefined) ?
realOptions.ignoreSourceArchive : isQLTestDatabase,
displayName: realOptions.displayName,
dateAdded: realOptions.dateAdded || Date.now()
ignoreSourceArchive: isQLTestDatabase,
// If a displayName is not passed in, the basename of folder containing the database is used.
displayName,
dateAdded: Date.now(),
language: await this.getPrimaryLanguage(uri.fsPath)
};
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (event) => {
this._onDidChangeDatabaseItem.fire(event);
});
await this.addDatabaseItem(databaseItem);
await this.addDatabaseItem(progress, token, databaseItem);
await this.addDatabaseSourceArchiveFolder(databaseItem);
return databaseItem;
}
private async addDatabaseSourceArchiveFolder(item: DatabaseItem) {
private async reregisterDatabases(
progress: ProgressCallback,
token: vscode.CancellationToken
) {
let completed = 0;
await Promise.all(this._databaseItems.map(async (databaseItem) => {
await this.registerDatabase(progress, token, databaseItem);
completed++;
progress({
maxStep: this._databaseItems.length,
step: completed,
message: 'Re-registering databases'
});
}));
}
public async addDatabaseSourceArchiveFolder(item: DatabaseItem) {
// The folder may already be in workspace state from a previous
// session. If not, add it.
const index = this.getDatabaseWorkspaceFolderIndex(item);
@@ -531,36 +619,41 @@ export class DatabaseManager extends DisposableObject {
// This is undesirable, as we might be adding and removing many
// workspace folders as the user adds and removes databases.
const end = (vscode.workspace.workspaceFolders || []).length;
const msg = item.verifyZippedSources();
if (msg) {
void logger.log(`Could not add source folder because ${msg}`);
return;
}
const uri = item.getSourceArchiveExplorerUri();
if (uri === undefined) {
logger.log(`Couldn't obtain file explorer uri for ${item.name}`);
}
else {
logger.log(`Adding workspace folder for ${item.name} source archive at index ${end}`);
if ((vscode.workspace.workspaceFolders || []).length < 2) {
// Adding this workspace folder makes the workspace
// multi-root, which may surprise the user. Let them know
// we're doing this.
vscode.window.showInformationMessage(`Adding workspace folder for source archive of database ${item.name}.`);
}
vscode.workspace.updateWorkspaceFolders(end, 0, {
name: `[${item.name} source archive]`,
uri,
});
// vscode api documentation says we must to wait for this event
// between multiple `updateWorkspaceFolders` calls.
await eventFired(vscode.workspace.onDidChangeWorkspaceFolders);
void logger.log(`Adding workspace folder for ${item.name} source archive at index ${end}`);
if ((vscode.workspace.workspaceFolders || []).length < 2) {
// Adding this workspace folder makes the workspace
// multi-root, which may surprise the user. Let them know
// we're doing this.
void vscode.window.showInformationMessage(`Adding workspace folder for source archive of database ${item.name}.`);
}
vscode.workspace.updateWorkspaceFolders(end, 0, {
name: `[${item.name} source archive]`,
uri,
});
// vscode api documentation says we must to wait for this event
// between multiple `updateWorkspaceFolders` calls.
await eventFired(vscode.workspace.onDidChangeWorkspaceFolders);
}
}
private async createDatabaseItemFromPersistedState(
progress: ProgressCallback,
token: vscode.CancellationToken,
state: PersistedDatabaseItem
): Promise<DatabaseItem> {
let displayName: string | undefined = undefined;
let ignoreSourceArchive = false;
let dateAdded = undefined;
let language = undefined;
if (state.options) {
if (typeof state.options.displayName === 'string') {
displayName = state.options.displayName;
@@ -571,43 +664,73 @@ export class DatabaseManager extends DisposableObject {
if (typeof state.options.dateAdded === 'number') {
dateAdded = state.options.dateAdded;
}
language = state.options.language;
}
const dbBaseUri = vscode.Uri.parse(state.uri, true);
if (language === undefined) {
// we haven't been successful yet at getting the language. try again
language = await this.getPrimaryLanguage(dbBaseUri.fsPath);
}
const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive,
displayName,
dateAdded
dateAdded,
language
};
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
const item = new DatabaseItemImpl(dbBaseUri, undefined, fullOptions,
(event) => {
this._onDidChangeDatabaseItem.fire(event);
});
await this.addDatabaseItem(item);
await this.addDatabaseItem(progress, token, item);
return item;
}
private async loadPersistedState(): Promise<void> {
const currentDatabaseUri = this.ctx.workspaceState.get<string>(CURRENT_DB);
const databases = this.ctx.workspaceState.get<PersistedDatabaseItem[]>(DB_LIST, []);
try {
for (const database of databases) {
const databaseItem = await this.createDatabaseItemFromPersistedState(database);
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, []);
let step = 0;
progress({
maxStep: databases.length,
message: 'Loading persisted databases',
step
});
try {
await databaseItem.refresh();
if (currentDatabaseUri === database.uri) {
this.setCurrentDatabaseItem(databaseItem, true);
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
});
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}.`);
}
}
} catch (e) {
// database list had an unexpected type - nothing to be done?
void showAndLogErrorMessage(`Database list loading failed: ${getErrorMessage(e)}`);
}
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.
}
}
} catch (e) {
// database list had an unexpected type - nothing to be done?
showAndLogErrorMessage(`Database list loading failed: ${e.message}`);
}
void this.logger.log('Finished loading persisted databases.');
});
}
public get databaseItems(): readonly DatabaseItem[] {
@@ -618,8 +741,10 @@ export class DatabaseManager extends DisposableObject {
return this._currentDatabaseItem;
}
public async setCurrentDatabaseItem(item: DatabaseItem | undefined,
skipRefresh = false): Promise<void> {
public async setCurrentDatabaseItem(
item: DatabaseItem | undefined,
skipRefresh = false
): Promise<void> {
if (!skipRefresh && (item !== undefined)) {
await item.refresh(); // Will throw on invalid database.
@@ -627,6 +752,9 @@ export class DatabaseManager extends DisposableObject {
if (this._currentDatabaseItem !== item) {
this._currentDatabaseItem = item;
this.updatePersistedCurrentDatabaseItem();
await vscode.commands.executeCommand('setContext', 'codeQL.currentDatabaseItem', item?.name);
this._onDidChangeCurrentDatabaseItem.fire({
item,
kind: DatabaseEventKind.Change
@@ -653,9 +781,20 @@ export class DatabaseManager extends DisposableObject {
return this._databaseItems.find(item => item.sourceArchive && item.sourceArchive.toString(true) === uriString);
}
private async addDatabaseItem(item: DatabaseItemImpl) {
private async addDatabaseItem(
progress: ProgressCallback,
token: vscode.CancellationToken,
item: DatabaseItem
) {
this._databaseItems.push(item);
this.updatePersistedDatabaseList();
await this.updatePersistedDatabaseList();
// Add this database item to the allow-list
// Database items reconstituted from persisted state
// will not have their contents yet.
if (item.contents?.datasetUri) {
await this.registerDatabase(progress, token, item);
}
// note that we use undefined as the item in order to reset the entire tree
this._onDidChangeDatabaseItem.fire({
item: undefined,
@@ -665,7 +804,7 @@ export class DatabaseManager extends DisposableObject {
public async renameDatabaseItem(item: DatabaseItem, newName: string) {
item.name = newName;
this.updatePersistedDatabaseList();
await this.updatePersistedDatabaseList();
this._onDidChangeDatabaseItem.fire({
// pass undefined so that the entire tree is rebuilt in order to re-sort
item: undefined,
@@ -673,28 +812,38 @@ export class DatabaseManager extends DisposableObject {
});
}
public removeDatabaseItem(item: DatabaseItem) {
if (this._currentDatabaseItem == item)
public async removeDatabaseItem(
progress: ProgressCallback,
token: vscode.CancellationToken,
item: DatabaseItem
) {
if (this._currentDatabaseItem == item) {
this._currentDatabaseItem = undefined;
}
const index = this.databaseItems.findIndex(searchItem => searchItem === item);
if (index >= 0) {
this._databaseItems.splice(index, 1);
}
this.updatePersistedDatabaseList();
await this.updatePersistedDatabaseList();
// Delete folder from workspace, if it is still there
const folderIndex = (vscode.workspace.workspaceFolders || []).findIndex(folder => item.belongsToSourceArchiveExplorerUri(folder.uri));
if (index >= 0) {
logger.log(`Removing workspace folder at index ${folderIndex}`);
const folderIndex = (vscode.workspace.workspaceFolders || []).findIndex(
folder => item.belongsToSourceArchiveExplorerUri(folder.uri)
);
if (folderIndex >= 0) {
void logger.log(`Removing workspace folder at index ${folderIndex}`);
vscode.workspace.updateWorkspaceFolders(folderIndex, 1);
}
// Remove this database item from the allow-list
await this.deregisterDatabase(progress, token, item);
// Delete folder from file system only if it is controlled by the extension
if (this.isExtensionControlledLocation(item.databaseUri)) {
logger.log('Deleting database from filesystem.');
fs.remove(item.databaseUri.path).then(
() => logger.log(`Deleted '${item.databaseUri.path}'`),
e => logger.log(`Failed to delete '${item.databaseUri.path}'. Reason: ${e.message}`));
void logger.log('Deleting database from filesystem.');
fs.remove(item.databaseUri.fsPath).then(
() => void logger.log(`Deleted '${item.databaseUri.fsPath}'`),
e => void logger.log(`Failed to delete '${item.databaseUri.fsPath}'. Reason: ${getErrorMessage(e)}`));
}
// note that we use undefined as the item in order to reset the entire tree
@@ -704,18 +853,50 @@ export class DatabaseManager extends DisposableObject {
});
}
private async deregisterDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
await this.qs.deregisterDatabase(progress, token, dbItem);
}
private async registerDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
await this.qs.registerDatabase(progress, token, dbItem);
}
private updatePersistedCurrentDatabaseItem(): void {
this.ctx.workspaceState.update(CURRENT_DB, this._currentDatabaseItem ?
void this.ctx.workspaceState.update(CURRENT_DB, this._currentDatabaseItem ?
this._currentDatabaseItem.databaseUri.toString(true) : undefined);
}
private updatePersistedDatabaseList(): void {
this.ctx.workspaceState.update(DB_LIST, this._databaseItems.map(item => item.getPersistedState()));
private async updatePersistedDatabaseList(): Promise<void> {
await this.ctx.workspaceState.update(DB_LIST, this._databaseItems.map(item => item.getPersistedState()));
}
private isExtensionControlledLocation(uri: vscode.Uri) {
const storagePath = this.ctx.storagePath || this.ctx.globalStoragePath;
return uri.path.startsWith(storagePath);
// the uri.fsPath function on windows returns a lowercase drive letter,
// but storagePath will have an uppercase drive letter. Be sure to compare
// URIs to URIs only
if (storagePath) {
return uri.fsPath.startsWith(vscode.Uri.file(storagePath).fsPath);
}
return false;
}
private async getPrimaryLanguage(dbPath: string) {
if (!(await this.cli.cliConstraints.supportsLanguageName())) {
// return undefined so that we recalculate on restart until the cli is at a version that
// supports this feature. This recalculation is cheap since we avoid calling into the cli
// unless we know it can return the langauges property.
return undefined;
}
const dbInfo = await this.cli.resolveDatabase(dbPath);
return dbInfo.languages?.[0] || '';
}
}
@@ -728,23 +909,3 @@ export function getUpgradesDirectories(scripts: string[]): vscode.Uri[] {
const uniqueParentDirs = new Set(parentDirs);
return Array.from(uniqueParentDirs).map(filePath => vscode.Uri.file(filePath));
}
// TODO: Get the list of supported languages from a list that will be auto-updated.
export async function isLikelyDatabaseRoot(fsPath: string) {
const [a, b, c] = (await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
fs.pathExists(path.join(fsPath, '.dbinfo')),
fs.pathExists(path.join(fsPath, 'codeql-database.yml')),
// they *must* have a db-language folder
(await fs.readdir(fsPath)).some(isLikelyDbLanguageFolder)
]));
return (a || b) && c;
}
export function isLikelyDbLanguageFolder(dbPath: string) {
return !!path.basename(dbPath).startsWith('db-');
}

View File

@@ -1,5 +1,5 @@
import { DisposableObject } from './vscode-utils/disposable-object';
import { showAndLogErrorMessage } from './helpers';
import { DisposableObject } from './pure/disposable-object';
import { logger } from './logging';
/**
* Base class for "discovery" operations, which scan the file system to find specific kinds of
@@ -59,23 +59,23 @@ export abstract class Discovery<T> extends DisposableObject {
this.discoveryInProgress = false;
this.update(results);
}
});
})
discoveryPromise.catch(err => {
showAndLogErrorMessage(`${this.name} failed. Reason: ${err.message}`);
});
.catch(err => {
void logger.log(`${this.name} failed. Reason: ${err.message}`);
})
discoveryPromise.finally(() => {
if (this.retry) {
// Another refresh request came in while we were still running a previous discovery
// operation. Since the discovery results we just computed are now stale, we'll launch
// another discovery operation instead of updating.
// Note that by doing this inside of `finally`, we will relaunch discovery even if the
// initial discovery operation failed.
this.retry = false;
this.launchDiscovery();
}
});
.finally(() => {
if (this.retry) {
// Another refresh request came in while we were still running a previous discovery
// operation. Since the discovery results we just computed are now stale, we'll launch
// another discovery operation instead of updating.
// Note that by doing this inside of `finally`, we will relaunch discovery even if the
// initial discovery operation failed.
this.retry = false;
this.launchDiscovery();
}
});
}
/**

View File

@@ -7,10 +7,15 @@ import * as unzipper from 'unzipper';
import * as url from 'url';
import { ExtensionContext, Event } from 'vscode';
import { DistributionConfig } from './config';
import { InvocationRateLimiter, InvocationRateLimiterResultKind, showAndLogErrorMessage } from './helpers';
import {
InvocationRateLimiter,
InvocationRateLimiterResultKind,
showAndLogErrorMessage,
showAndLogWarningMessage
} from './helpers';
import { logger } from './logging';
import * as helpers from './helpers';
import { getCodeQlCliVersion } from './cli-version';
import { ProgressCallback, reportStreamProgress } from './commandRunner';
/**
* distribution.ts
@@ -49,16 +54,36 @@ export interface DistributionProvider {
}
export class DistributionManager implements DistributionProvider {
constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionRange: semver.Range) {
this._config = config;
this._extensionSpecificDistributionManager = new ExtensionSpecificDistributionManager(extensionContext, config, versionRange);
/**
* Get the name of the codeql cli installation we prefer to install, based on our current platform.
*/
public static getRequiredAssetName(): string {
switch (os.platform()) {
case 'linux':
return 'codeql-linux64.zip';
case 'darwin':
return 'codeql-osx64.zip';
case 'win32':
return 'codeql-win64.zip';
default:
return 'codeql.zip';
}
}
constructor(
public readonly config: DistributionConfig,
private readonly versionRange: semver.Range,
extensionContext: ExtensionContext
) {
this._onDidChangeDistribution = config.onDidChangeConfiguration;
this._updateCheckRateLimiter = new InvocationRateLimiter(
this.extensionSpecificDistributionManager =
new ExtensionSpecificDistributionManager(config, versionRange, extensionContext);
this.updateCheckRateLimiter = new InvocationRateLimiter(
extensionContext,
'extensionSpecificDistributionUpdateCheck',
() => this._extensionSpecificDistributionManager.checkForUpdatesToDistribution()
() => this.extensionSpecificDistributionManager.checkForUpdatesToDistribution()
);
this._versionRange = versionRange;
}
/**
@@ -95,9 +120,9 @@ export class DistributionManager implements DistributionProvider {
* - If the user is using an extension-managed CLI, then prereleases are only accepted when the
* includePrerelease config option is set.
*/
const includePrerelease = distribution.kind !== DistributionKind.ExtensionManaged || this._config.includePrerelease;
const includePrerelease = distribution.kind !== DistributionKind.ExtensionManaged || this.config.includePrerelease;
if (!semver.satisfies(version, this._versionRange, { includePrerelease })) {
if (!semver.satisfies(version, this.versionRange, { includePrerelease })) {
return {
distribution,
kind: FindDistributionResultKind.IncompatibleDistribution,
@@ -126,9 +151,9 @@ export class DistributionManager implements DistributionProvider {
*/
async getDistributionWithoutVersionCheck(): Promise<Distribution | undefined> {
// Check config setting, then extension specific distribution, then PATH.
if (this._config.customCodeQlPath) {
if (!await fs.pathExists(this._config.customCodeQlPath)) {
showAndLogErrorMessage(`The CodeQL executable path is specified as "${this._config.customCodeQlPath}" ` +
if (this.config.customCodeQlPath) {
if (!await fs.pathExists(this.config.customCodeQlPath)) {
void showAndLogErrorMessage(`The CodeQL executable path is specified as "${this.config.customCodeQlPath}" ` +
'by a configuration setting, but a CodeQL executable could not be found at that path. Please check ' +
'that a CodeQL executable exists at the specified path or remove the setting.');
return undefined;
@@ -137,18 +162,18 @@ export class DistributionManager implements DistributionProvider {
// emit a warning if using a deprecated launcher and a non-deprecated launcher exists
if (
deprecatedCodeQlLauncherName() &&
this._config.customCodeQlPath.endsWith(deprecatedCodeQlLauncherName()!) &&
this.config.customCodeQlPath.endsWith(deprecatedCodeQlLauncherName()!) &&
await this.hasNewLauncherName()
) {
warnDeprecatedLauncher();
}
return {
codeQlPath: this._config.customCodeQlPath,
codeQlPath: this.config.customCodeQlPath,
kind: DistributionKind.CustomPathConfig
};
}
const extensionSpecificCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
const extensionSpecificCodeQlPath = await this.extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
if (extensionSpecificCodeQlPath !== undefined) {
return {
codeQlPath: extensionSpecificCodeQlPath,
@@ -166,7 +191,7 @@ export class DistributionManager implements DistributionProvider {
};
}
}
logger.log('INFO: Could not find CodeQL on path.');
void logger.log('INFO: Could not find CodeQL on path.');
}
return undefined;
@@ -181,12 +206,12 @@ export class DistributionManager implements DistributionProvider {
public async checkForUpdatesToExtensionManagedDistribution(
minSecondsSinceLastUpdateCheck: number): Promise<DistributionUpdateCheckResult> {
const distribution = await this.getDistributionWithoutVersionCheck();
const extensionManagedCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
const extensionManagedCodeQlPath = await this.extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
if (distribution?.codeQlPath !== extensionManagedCodeQlPath) {
// A distribution is present but it isn't managed by the extension.
return createInvalidLocationResult();
}
const updateCheckResult = await this._updateCheckRateLimiter.invokeFunctionIfIntervalElapsed(minSecondsSinceLastUpdateCheck);
const updateCheckResult = await this.updateCheckRateLimiter.invokeFunctionIfIntervalElapsed(minSecondsSinceLastUpdateCheck);
switch (updateCheckResult.kind) {
case InvocationRateLimiterResultKind.Invoked:
return updateCheckResult.result;
@@ -200,9 +225,11 @@ export class DistributionManager implements DistributionProvider {
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public installExtensionManagedDistributionRelease(release: Release,
progressCallback?: helpers.ProgressCallback): Promise<void> {
return this._extensionSpecificDistributionManager.installDistributionRelease(release, progressCallback);
public installExtensionManagedDistributionRelease(
release: Release,
progressCallback?: ProgressCallback
): Promise<void> {
return this.extensionSpecificDistributionManager.installDistributionRelease(release, progressCallback);
}
public get onDidChangeDistribution(): Event<void> | undefined {
@@ -215,27 +242,27 @@ export class DistributionManager implements DistributionProvider {
* installation. False otherwise.
*/
private async hasNewLauncherName(): Promise<boolean> {
if (!this._config.customCodeQlPath) {
if (!this.config.customCodeQlPath) {
// not managed externally
return false;
}
const dir = path.dirname(this._config.customCodeQlPath);
const dir = path.dirname(this.config.customCodeQlPath);
const newLaunderPath = path.join(dir, codeQlLauncherName());
return await fs.pathExists(newLaunderPath);
}
private readonly _config: DistributionConfig;
private readonly _extensionSpecificDistributionManager: ExtensionSpecificDistributionManager;
private readonly _updateCheckRateLimiter: InvocationRateLimiter<DistributionUpdateCheckResult>;
private readonly extensionSpecificDistributionManager: ExtensionSpecificDistributionManager;
private readonly updateCheckRateLimiter: InvocationRateLimiter<DistributionUpdateCheckResult>;
private readonly _onDidChangeDistribution: Event<void> | undefined;
private readonly _versionRange: semver.Range;
}
class ExtensionSpecificDistributionManager {
constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionRange: semver.Range) {
this._extensionContext = extensionContext;
this._config = config;
this._versionRange = versionRange;
constructor(
private readonly config: DistributionConfig,
private readonly versionRange: semver.Range,
private readonly extensionContext: ExtensionContext
) {
/**/
}
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
@@ -249,7 +276,7 @@ class ExtensionSpecificDistributionManager {
try {
await this.removeDistribution();
} catch (e) {
logger.log('WARNING: Tried to remove corrupted CodeQL CLI at ' +
void logger.log('WARNING: Tried to remove corrupted CodeQL CLI at ' +
`${this.getDistributionStoragePath()} but encountered an error: ${e}.`);
}
}
@@ -283,29 +310,29 @@ class ExtensionSpecificDistributionManager {
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async installDistributionRelease(release: Release,
progressCallback?: helpers.ProgressCallback): Promise<void> {
progressCallback?: ProgressCallback): Promise<void> {
await this.downloadDistribution(release, progressCallback);
// Store the installed release within the global extension state.
this.storeInstalledRelease(release);
await this.storeInstalledRelease(release);
}
private async downloadDistribution(release: Release,
progressCallback?: helpers.ProgressCallback): Promise<void> {
progressCallback?: ProgressCallback): Promise<void> {
try {
await this.removeDistribution();
} catch (e) {
logger.log(`Tried to clean up old version of CLI at ${this.getDistributionStoragePath()} ` +
void logger.log(`Tried to clean up old version of CLI at ${this.getDistributionStoragePath()} ` +
`but encountered an error: ${e}.`);
}
// Filter assets to the unique one that we require.
const requiredAssetName = this.getRequiredAssetName();
const requiredAssetName = DistributionManager.getRequiredAssetName();
const assets = release.assets.filter(asset => asset.name === requiredAssetName);
if (assets.length === 0) {
throw new Error(`Invariant violation: chose a release to install that didn't have ${requiredAssetName}`);
}
if (assets.length > 1) {
logger.log('WARNING: chose a release with more than one asset to install, found ' +
void logger.log('WARNING: chose a release with more than one asset to install, found ' +
assets.map(asset => asset.name).join(', '));
}
@@ -317,27 +344,8 @@ class ExtensionSpecificDistributionManager {
const archiveFile = fs.createWriteStream(archivePath);
const contentLength = assetStream.headers.get('content-length');
let numBytesDownloaded = 0;
if (progressCallback && contentLength !== null) {
const totalNumBytes = parseInt(contentLength, 10);
const bytesToDisplayMB = (numBytes: number): string => `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = (): void => {
progressCallback({
step: numBytesDownloaded,
maxStep: totalNumBytes,
message: `Downloading CodeQL CLI… [${bytesToDisplayMB(numBytesDownloaded)} of ${bytesToDisplayMB(totalNumBytes)}]`,
});
};
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();
assetStream.body.on('data', data => {
numBytesDownloaded += data.length;
updateProgress();
});
}
const totalNumBytes = contentLength ? parseInt(contentLength, 10) : undefined;
reportStreamProgress(assetStream.body, `Downloading CodeQL CLI ${release.name}`, totalNumBytes, progressCallback);
await new Promise((resolve, reject) =>
assetStream.body.pipe(archiveFile)
@@ -347,7 +355,7 @@ class ExtensionSpecificDistributionManager {
await this.bumpDistributionFolderIndex();
logger.log(`Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`);
void logger.log(`Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`);
await extractZipArchive(archivePath, this.getDistributionStoragePath());
} finally {
await fs.remove(tmpDirectory);
@@ -360,37 +368,27 @@ class ExtensionSpecificDistributionManager {
* This should not be called for a distribution that is currently in use, as remove may fail.
*/
private async removeDistribution(): Promise<void> {
this.storeInstalledRelease(undefined);
await this.storeInstalledRelease(undefined);
if (await fs.pathExists(this.getDistributionStoragePath())) {
await fs.remove(this.getDistributionStoragePath());
}
}
/**
* Get the name of the codeql cli installation we prefer to install, based on our current platform.
*/
private getRequiredAssetName(): string {
if (os.platform() === 'linux') return 'codeql-linux64.zip';
if (os.platform() === 'darwin') return 'codeql-osx64.zip';
if (os.platform() === 'win32') return 'codeql-win64.zip';
return 'codeql.zip';
}
private async getLatestRelease(): Promise<Release> {
const requiredAssetName = this.getRequiredAssetName();
logger.log(`Searching for latest release including ${requiredAssetName}.`);
const requiredAssetName = DistributionManager.getRequiredAssetName();
void logger.log(`Searching for latest release including ${requiredAssetName}.`);
return this.createReleasesApiConsumer().getLatestRelease(
this._versionRange,
this._config.includePrerelease,
this.versionRange,
this.config.includePrerelease,
release => {
const matchingAssets = release.assets.filter(asset => asset.name === requiredAssetName);
if (matchingAssets.length === 0) {
// For example, this could be a release with no platform-specific assets.
logger.log(`INFO: Ignoring a release with no assets named ${requiredAssetName}`);
void logger.log(`INFO: Ignoring a release with no assets named ${requiredAssetName}`);
return false;
}
if (matchingAssets.length > 1) {
logger.log(`WARNING: Ignoring a release with more than one asset named ${requiredAssetName}`);
void logger.log(`WARNING: Ignoring a release with more than one asset named ${requiredAssetName}`);
return false;
}
return true;
@@ -399,23 +397,23 @@ class ExtensionSpecificDistributionManager {
}
private createReleasesApiConsumer(): ReleasesApiConsumer {
const ownerName = this._config.ownerName ? this._config.ownerName : DEFAULT_DISTRIBUTION_OWNER_NAME;
const repositoryName = this._config.repositoryName ? this._config.repositoryName : DEFAULT_DISTRIBUTION_REPOSITORY_NAME;
return new ReleasesApiConsumer(ownerName, repositoryName, this._config.personalAccessToken);
const ownerName = this.config.ownerName ? this.config.ownerName : DEFAULT_DISTRIBUTION_OWNER_NAME;
const repositoryName = this.config.repositoryName ? this.config.repositoryName : DEFAULT_DISTRIBUTION_REPOSITORY_NAME;
return new ReleasesApiConsumer(ownerName, repositoryName, this.config.personalAccessToken);
}
private async bumpDistributionFolderIndex(): Promise<void> {
const index = this._extensionContext.globalState.get(
const index = this.extensionContext.globalState.get(
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0);
await this._extensionContext.globalState.update(
await this.extensionContext.globalState.update(
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, index + 1);
}
private getDistributionStoragePath(): string {
// Use an empty string for the initial distribution for backwards compatibility.
const distributionFolderIndex = this._extensionContext.globalState.get(
const distributionFolderIndex = this.extensionContext.globalState.get(
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0) || '';
return path.join(this._extensionContext.globalStoragePath,
return path.join(this.extensionContext.globalStoragePath,
ExtensionSpecificDistributionManager._currentDistributionFolderBaseName + distributionFolderIndex);
}
@@ -425,17 +423,13 @@ class ExtensionSpecificDistributionManager {
}
private getInstalledRelease(): Release | undefined {
return this._extensionContext.globalState.get(ExtensionSpecificDistributionManager._installedReleaseStateKey);
return this.extensionContext.globalState.get(ExtensionSpecificDistributionManager._installedReleaseStateKey);
}
private async storeInstalledRelease(release: Release | undefined): Promise<void> {
await this._extensionContext.globalState.update(ExtensionSpecificDistributionManager._installedReleaseStateKey, release);
await this.extensionContext.globalState.update(ExtensionSpecificDistributionManager._installedReleaseStateKey, release);
}
private readonly _config: DistributionConfig;
private readonly _extensionContext: ExtensionContext;
private readonly _versionRange: semver.Range;
private static readonly _currentDistributionFolderBaseName = 'distribution';
private static readonly _currentDistributionFolderIndexStateKey = 'distributionFolderIndex';
private static readonly _installedReleaseStateKey = 'distributionRelease';
@@ -576,7 +570,7 @@ export async function extractZipArchive(archivePath: string, outPath: string): P
}));
}
function codeQlLauncherName(): string {
export function codeQlLauncherName(): string {
return (os.platform() === 'win32') ? 'codeql.exe' : 'codeql';
}
@@ -713,14 +707,14 @@ export async function getExecutableFromDirectory(directory: string, warnWhenNotF
return alternateExpectedLauncherPath;
}
if (warnWhenNotFound) {
logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` +
void logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` +
'Will try PATH.');
}
return undefined;
}
function warnDeprecatedLauncher() {
helpers.showAndLogWarningMessage(
void showAndLogWarningMessage(
`The "${deprecatedCodeQlLauncherName()!}" launcher has been deprecated and will be removed in a future version. ` +
`Please use "${codeQlLauncherName()}" instead. It is recommended to update to the latest CodeQL binaries.`
);

View File

@@ -0,0 +1,67 @@
import { ChildEvalLogTreeItem, EvalLogTreeItem } from './eval-log-viewer';
import { EvalLogData as EvalLogData } from './pure/log-summary-parser';
/** Builds the tree data for the evaluator log viewer for a single query run. */
export default class EvalLogTreeBuilder {
private queryName: string;
private evalLogDataItems: EvalLogData[];
constructor(queryName: string, evaluatorLogDataItems: EvalLogData[]) {
this.queryName = queryName;
this.evalLogDataItems = evaluatorLogDataItems;
}
async getRoots(): Promise<EvalLogTreeItem[]> {
return await this.parseRoots();
}
private async parseRoots(): Promise<EvalLogTreeItem[]> {
const roots: EvalLogTreeItem[] = [];
// Once the viewer can show logs for multiple queries, there will be more than 1 item at the root
// level. For now, there will always be one root (the one query being shown).
const queryItem: EvalLogTreeItem = {
label: this.queryName,
children: [] // Will assign predicate items as children shortly.
};
// Display descriptive message when no data exists
if (this.evalLogDataItems.length === 0) {
const noResultsItem: ChildEvalLogTreeItem = {
label: 'No predicates evaluated in this query run.',
parent: queryItem,
children: [],
};
queryItem.children.push(noResultsItem);
}
// For each predicate, create a TreeItem object with appropriate parents/children
this.evalLogDataItems.forEach(logDataItem => {
const predicateLabel = `${logDataItem.predicateName} (${logDataItem.resultSize} tuples, ${logDataItem.millis} ms)`;
const predicateItem: ChildEvalLogTreeItem = {
label: predicateLabel,
parent: queryItem,
children: [] // Will assign pipeline items as children shortly.
};
for (const [pipelineName, steps] of Object.entries(logDataItem.ra)) {
const pipelineLabel = `Pipeline: ${pipelineName}`;
const pipelineItem: ChildEvalLogTreeItem = {
label: pipelineLabel,
parent: predicateItem,
children: [] // Will assign step items as children shortly.
};
predicateItem.children.push(pipelineItem);
pipelineItem.children = steps.map((step: string) => ({
label: step,
parent: pipelineItem,
children: []
}));
}
queryItem.children.push(predicateItem);
});
roots.push(queryItem);
return roots;
}
}

View File

@@ -0,0 +1,92 @@
import { window, TreeDataProvider, TreeView, TreeItem, ProviderResult, Event, EventEmitter, TreeItemCollapsibleState } from 'vscode';
import { commandRunner } from './commandRunner';
import { DisposableObject } from './pure/disposable-object';
import { showAndLogErrorMessage } from './helpers';
export interface EvalLogTreeItem {
label?: string;
children: ChildEvalLogTreeItem[];
}
export interface ChildEvalLogTreeItem extends EvalLogTreeItem {
parent: ChildEvalLogTreeItem | EvalLogTreeItem;
}
/** Provides data from parsed CodeQL evaluator logs to be rendered in a tree view. */
class EvalLogDataProvider extends DisposableObject implements TreeDataProvider<EvalLogTreeItem> {
public roots: EvalLogTreeItem[] = [];
private _onDidChangeTreeData: EventEmitter<EvalLogTreeItem | undefined | null | void> = new EventEmitter<EvalLogTreeItem | undefined | null | void>();
readonly onDidChangeTreeData: Event<EvalLogTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire();
}
getTreeItem(element: EvalLogTreeItem): TreeItem | Thenable<TreeItem> {
const state = element.children.length
? TreeItemCollapsibleState.Collapsed
: TreeItemCollapsibleState.None;
const treeItem = new TreeItem(element.label || '', state);
treeItem.tooltip = `${treeItem.label} || ''}`;
return treeItem;
}
getChildren(element?: EvalLogTreeItem): ProviderResult<EvalLogTreeItem[]> {
// If no item is passed, return the root.
if (!element) {
return this.roots || [];
}
// Otherwise it is called with an existing item, to load its children.
return element.children;
}
getParent(element: ChildEvalLogTreeItem): ProviderResult<EvalLogTreeItem> {
return element.parent;
}
}
/** Manages a tree viewer of structured evaluator logs. */
export class EvalLogViewer extends DisposableObject {
private treeView: TreeView<EvalLogTreeItem>;
private treeDataProvider: EvalLogDataProvider;
constructor() {
super();
this.treeDataProvider = new EvalLogDataProvider();
this.treeView = window.createTreeView('codeQLEvalLogViewer', {
treeDataProvider: this.treeDataProvider,
showCollapseAll: true
});
this.push(this.treeView);
this.push(this.treeDataProvider);
this.push(
commandRunner('codeQLEvalLogViewer.clear', async () => {
this.clear();
})
);
}
private clear(): void {
this.treeDataProvider.roots = [];
this.treeDataProvider.refresh();
this.treeView.message = undefined;
}
// Called when the Show Evaluator Log (UI) command is run on a new query.
updateRoots(roots: EvalLogTreeItem[]): void {
this.treeDataProvider.roots = roots;
this.treeDataProvider.refresh();
this.treeView.message = 'Viewer for query run:'; // Currently only one query supported at a time.
// Handle error on reveal. This could happen if
// the tree view is disposed during the reveal.
this.treeView.reveal(roots[0], { focus: false })?.then(
() => { /**/ },
err => showAndLogErrorMessage(err)
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,177 +2,29 @@ import * as fs from 'fs-extra';
import * as glob from 'glob-promise';
import * as yaml from 'js-yaml';
import * as path from 'path';
import * as tmp from 'tmp-promise';
import {
CancellationToken,
ExtensionContext,
ProgressOptions,
Uri,
window as Window,
workspace,
commands,
Disposable,
ProgressLocation
env
} from 'vscode';
import { CodeQLCliServer } from './cli';
import { CodeQLCliServer, QlpacksInfo } from './cli';
import { UserCancellationException } from './commandRunner';
import { logger } from './logging';
import { QueryMetadata } from './pure/interface-types';
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);
// Shared temporary folder for the extension.
export const tmpDir = tmp.dirSync({ prefix: 'queries_', keep: false, unsafeCleanup: true });
export const upgradesTmpDir = path.join(tmpDir.name, 'upgrades');
fs.ensureDirSync(upgradesTmpDir);
export const tmpDirDisposal = {
dispose: () => {
tmpDir.removeCallback();
}
}
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>;
/**
* A task that handles command invocations from `commandRunner`.
* Arguments passed to the command handler are passed along,
* untouched to this `NoProgressTask` instance.
*
* @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);
});
}
/**
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
*
* In this variant of the command runner, no progress monitor is used.
*
* @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.
*/
export function commandRunner(
commandId: string,
task: NoProgressTask,
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
try {
await task(...args);
} catch (e) {
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
logger.log(e.message);
} else {
showAndLogWarningMessage(e.message);
}
} else {
showAndLogErrorMessage(e.message || e);
}
}
});
}
/**
* 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>
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
const progressOptionsWithDefaults = {
location: ProgressLocation.Notification,
...progressOptions
};
try {
await withProgress(progressOptionsWithDefaults, task, ...args);
} catch (e) {
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
logger.log(e.message);
} else {
showAndLogWarningMessage(e.message);
}
} else {
showAndLogErrorMessage(e.message || e);
}
}
});
}
};
/**
* Show an error message and log it to the console
@@ -180,15 +32,24 @@ export function commandRunnerWithProgress<R>(
* @param message The message to show.
* @param options.outputLogger The output logger that will receive the message
* @param options.items A set of items that will be rendered as actions in the message.
* @param options.fullMessage An alternate message that is added to the log, but not displayed
* in the popup. This is useful for adding extra detail to the logs
* that would be too noisy for the popup.
*
* @return A promise that resolves to the selected item or undefined when being dismissed.
*/
export async function showAndLogErrorMessage(message: string, {
outputLogger = logger,
items = [] as string[]
items = [] as string[],
fullMessage = undefined as (string | undefined)
} = {}): Promise<string | undefined> {
return internalShowAndLog(message, items, outputLogger, Window.showErrorMessage);
return internalShowAndLog(dropLinesExceptInitial(message), items, outputLogger, Window.showErrorMessage, fullMessage);
}
function dropLinesExceptInitial(message: string, n = 2) {
return message.toString().split(/\r?\n/).slice(0, n).join('\n');
}
/**
* Show a warning message and log it to the console
*
@@ -215,17 +76,23 @@ export async function showAndLogWarningMessage(message: string, {
*/
export async function showAndLogInformationMessage(message: string, {
outputLogger = logger,
items = [] as string[]
items = [] as string[],
fullMessage = ''
} = {}): Promise<string | undefined> {
return internalShowAndLog(message, items, outputLogger, Window.showInformationMessage);
return internalShowAndLog(message, items, outputLogger, Window.showInformationMessage, fullMessage);
}
type ShowMessageFn = (message: string, ...items: string[]) => Thenable<string | undefined>;
async function internalShowAndLog(message: string, items: string[], outputLogger = logger,
fn: ShowMessageFn): Promise<string | undefined> {
async function internalShowAndLog(
message: string,
items: string[],
outputLogger = logger,
fn: ShowMessageFn,
fullMessage?: string
): Promise<string | undefined> {
const label = 'Show Log';
outputLogger.log(message);
void outputLogger.log(fullMessage || message);
const result = await fn(message, label, ...items);
if (result === label) {
outputLogger.show();
@@ -235,17 +102,61 @@ async function internalShowAndLog(message: string, items: string[], outputLogger
/**
* Opens a modal dialog for the user to make a yes/no choice.
* @param message The message to show.
*
* @return `true` if the user clicks 'Yes', `false` if the user clicks 'No' or cancels the dialog.
* @param message The message to show.
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
* be closed even if the user does not make a choice.
*
* @return
* `true` if the user clicks 'Yes',
* `false` if the user clicks 'No' or cancels the dialog,
* `undefined` if the dialog is closed without the user making a choice.
*/
export async function showBinaryChoiceDialog(message: string): Promise<boolean> {
export async function showBinaryChoiceDialog(message: string, modal = true): Promise<boolean | undefined> {
const yesItem = { title: 'Yes', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true };
const chosenItem = await Window.showInformationMessage(message, { modal: true }, yesItem, noItem);
const chosenItem = await Window.showInformationMessage(message, { modal }, yesItem, noItem);
if (!chosenItem) {
return undefined;
}
return chosenItem?.title === yesItem.title;
}
/**
* Opens a modal dialog for the user to make a yes/no choice.
*
* @param message The message to show.
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
* be closed even if the user does not make a choice.
*
* @return
* `true` if the user clicks 'Yes',
* `false` if the user clicks 'No' or cancels the dialog,
* `undefined` if the dialog is closed without the user making a choice.
*/
export async function showBinaryChoiceWithUrlDialog(message: string, url: string): Promise<boolean | undefined> {
const urlItem = { title: 'More Information', isCloseAffordance: false };
const yesItem = { title: 'Yes', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true };
let chosenItem;
// Keep the dialog open as long as the user is clicking the 'more information' option.
// To prevent an infinite loop, if the user clicks 'more information' 5 times, close the dialog and return cancelled
let count = 0;
do {
chosenItem = await Window.showInformationMessage(message, { modal: true }, urlItem, yesItem, noItem);
if (chosenItem === urlItem) {
await env.openExternal(Uri.parse(url, true));
}
count++;
} while (chosenItem === urlItem && count < 5);
if (!chosenItem || chosenItem.title === urlItem.title) {
return undefined;
}
return chosenItem.title === yesItem.title;
}
/**
* Show an information message with a customisable action.
* @param message The message to show.
@@ -358,40 +269,78 @@ function createRateLimitedResult(): RateLimitedResult {
};
}
export type DatasetFolderInfo = {
dbscheme: string;
qlpack: string;
export interface QlPacksForLanguage {
/** The name of the pack containing the dbscheme. */
dbschemePack: string;
/** `true` if `dbschemePack` is a library pack. */
dbschemePackIsLibraryPack: boolean;
/**
* The name of the corresponding standard query pack.
* Only defined if `dbschemePack` is a library pack.
*/
queryPack?: string;
}
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: { packDir: string | undefined; packName: string }[] =
Object.entries(qlpacks).map(([packName, dirs]) => {
if (dirs.length < 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`);
}
return {
packName,
packDir: dirs[0]
};
});
interface QlPackWithPath {
packName: string;
packDir: string | undefined;
}
async function findDbschemePack(packs: QlPackWithPath[], dbschemePath: string): Promise<{ name: string; isLibraryPack: boolean; }> {
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme: string };
const qlpack = yaml.load(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme?: string; library?: boolean; };
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
return packName;
return {
name: packName,
isLibraryPack: qlpack.library === true
};
}
}
}
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
}
export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFolder: string): Promise<DatasetFolderInfo> {
function findStandardQueryPack(qlpacks: QlpacksInfo, dbschemePackName: string): string | undefined {
const matches = dbschemePackName.match(/^codeql\/(?<language>[a-z]+)-all$/);
if (matches) {
const queryPackName = `codeql/${matches.groups!.language}-queries`;
if (qlpacks[queryPackName] !== undefined) {
return queryPackName;
}
}
// Either the dbscheme pack didn't look like one where the queries might be in the query pack, or
// no query pack was found in the search path. Either is OK.
return undefined;
}
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<QlPacksForLanguage> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: QlPackWithPath[] =
Object.entries(qlpacks).map(([packName, dirs]) => {
if (dirs.length < 1) {
void logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
void logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`);
}
return {
packName,
packDir: dirs[0]
};
});
const dbschemePack = await findDbschemePack(packs, dbschemePath);
const queryPack = dbschemePack.isLibraryPack ? findStandardQueryPack(qlpacks, dbschemePack.name) : undefined;
return {
dbschemePack: dbschemePack.name,
dbschemePackIsLibraryPack: dbschemePack.isLibraryPack,
queryPack
};
}
export async function getPrimaryDbscheme(datasetFolder: string): Promise<string> {
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'));
if (dbschemes.length < 1) {
@@ -400,31 +349,30 @@ export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFo
dbschemes.sort();
const dbscheme = dbschemes[0];
if (dbschemes.length > 1) {
Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`);
}
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
return { dbscheme, qlpack };
if (dbschemes.length > 1) {
void Window.showErrorMessage(`Found multiple dbschemes in ${datasetFolder} during quick query; arbitrarily choosing the first, ${dbscheme}, to decide what library to use.`);
}
return dbscheme;
}
/**
* A cached mapping from strings to value of type U.
*/
export class CachedOperation<U> {
private readonly operation: (t: string) => Promise<U>;
private readonly operation: (t: string, ...args: any[]) => Promise<U>;
private readonly cached: Map<string, U>;
private readonly lru: string[];
private readonly inProgressCallbacks: Map<string, [(u: U) => void, (reason?: any) => void][]>;
constructor(operation: (t: string) => Promise<U>, private cacheSize = 100) {
constructor(operation: (t: string, ...args: any[]) => Promise<U>, private cacheSize = 100) {
this.operation = operation;
this.lru = [];
this.inProgressCallbacks = new Map<string, [(u: U) => void, (reason?: any) => void][]>();
this.cached = new Map<string, U>();
}
async get(t: string): Promise<U> {
async get(t: string, ...args: any[]): Promise<U> {
// Try and retrieve from the cache
const fromCache = this.cached.get(t);
if (fromCache !== undefined) {
@@ -445,7 +393,7 @@ export class CachedOperation<U> {
const callbacks: [(u: U) => void, (reason?: any) => void][] = [];
this.inProgressCallbacks.set(t, callbacks);
try {
const result = await this.operation(t);
const result = await this.operation(t, ...args);
callbacks.forEach(f => f[0](result));
this.inProgressCallbacks.delete(t);
if (this.lru.length > this.cacheSize) {
@@ -464,3 +412,183 @@ export class CachedOperation<U> {
}
}
}
/**
* The following functions al heuristically determine metadata about databases.
*/
/**
* Note that this heuristic is only being used for backwards compatibility with
* CLI versions before the langauge name was introduced to dbInfo. Features
* that do not require backwards compatibility should call
* `cli.CodeQLCliServer.resolveDatabase` and use the first entry in the
* `languages` property.
*
* @see cli.CliVersionConstraint.supportsLanguageName
* @see cli.CodeQLCliServer.resolveDatabase
*/
export const dbSchemeToLanguage = {
'semmlecode.javascript.dbscheme': 'javascript',
'semmlecode.cpp.dbscheme': 'cpp',
'semmlecode.dbscheme': 'java',
'semmlecode.python.dbscheme': 'python',
'semmlecode.csharp.dbscheme': 'csharp',
'go.dbscheme': 'go',
'ruby.dbscheme': 'ruby'
};
export const languageToDbScheme = Object.entries(dbSchemeToLanguage).reduce((acc, [k, v]) => {
acc[v] = k;
return acc;
}, {} as { [k: string]: string });
/**
* Returns the initial contents for an empty query, based on the language of the selected
* databse.
*
* First try to use the given language name. If that doesn't exist, try to infer it based on
* dbscheme. Otherwise return no import statement.
*
* @param language the database language or empty string if unknown
* @param dbscheme path to the dbscheme file
*
* @returns an import and empty select statement appropriate for the selected language
*/
export function getInitialQueryContents(language: string, dbscheme: string) {
if (!language) {
const dbschemeBase = path.basename(dbscheme) as keyof typeof dbSchemeToLanguage;
language = dbSchemeToLanguage[dbschemeBase];
}
return language
? `import ${language}\n\nselect ""`
: 'select ""';
}
/**
* Heuristically determines if the directory passed in corresponds
* to a database root. A database root is a directory that contains
* a codeql-database.yml or (historically) a .dbinfo file. It also
* contains a folder starting with `db-`.
*/
export async function isLikelyDatabaseRoot(maybeRoot: string) {
const [a, b, c] = (await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
fs.pathExists(path.join(maybeRoot, '.dbinfo')),
fs.pathExists(path.join(maybeRoot, 'codeql-database.yml')),
// they *must* have a db-{language} folder
glob('db-*/', { cwd: maybeRoot })
]));
return ((a || b) && c.length > 0);
}
/**
* A language folder is any folder starting with `db-` that is itself not a database root.
*/
export async function isLikelyDbLanguageFolder(dbPath: string) {
return path.basename(dbPath).startsWith('db-') && !(await isLikelyDatabaseRoot(dbPath));
}
/**
* Finds the language that a query targets.
* If it can't be autodetected, prompt the user to specify the language manually.
*/
export async function findLanguage(
cliServer: CodeQLCliServer,
queryUri: Uri | undefined
): Promise<string | undefined> {
const uri = queryUri || Window.activeTextEditor?.document.uri;
if (uri !== undefined) {
try {
const queryInfo = await cliServer.resolveQueryByLanguage(getOnDiskWorkspaceFolders(), uri);
const language = (Object.keys(queryInfo.byLanguage))[0];
void logger.log(`Detected query language: ${language}`);
return language;
} catch (e) {
void logger.log('Could not autodetect query language. Select language manually.');
}
}
// will be undefined if user cancels the quick pick.
return await askForLanguage(cliServer, false);
}
export async function askForLanguage(cliServer: CodeQLCliServer, throwOnEmpty = true): Promise<string | undefined> {
const language = await Window.showQuickPick(
await cliServer.getSupportedLanguages(),
{ placeHolder: 'Select target language for your query', ignoreFocusOut: true }
);
if (!language) {
// This only happens if the user cancels the quick pick.
if (throwOnEmpty) {
throw new UserCancellationException('Cancelled.');
} else {
void showAndLogErrorMessage('Language not found. Language must be specified manually.');
}
}
return language;
}
/**
* Gets metadata for a query, if it exists.
* @param cliServer The CLI server.
* @param queryPath The path to the query.
* @returns A promise that resolves to the query metadata, if available.
*/
export async function tryGetQueryMetadata(cliServer: CodeQLCliServer, queryPath: string): Promise<QueryMetadata | undefined> {
try {
return await cliServer.resolveMetadata(queryPath);
} catch (e) {
// Ignore errors and provide no metadata.
void logger.log(`Couldn't resolve metadata for ${queryPath}: ${e}`);
return;
}
}
/**
* Creates a file in the query directory that indicates when this query was created.
* This is important for keeping track of when queries should be removed.
*
* @param queryPath The directory that will containt all files relevant to a query result.
* It does not need to exist.
*/
export async function createTimestampFile(storagePath: string) {
const timestampPath = path.join(storagePath, 'timestamp');
await fs.ensureDir(storagePath);
await fs.writeFile(timestampPath, Date.now().toString(), 'utf8');
}
/**
* Recursively walk a directory and return the full path to all files found.
* Symbolic links are ignored.
*
* @param dir the directory to walk
*
* @return An iterator of the full path to all files recursively found in the directory.
*/
export async function* walkDirectory(dir: string): AsyncIterableIterator<string> {
const seenFiles = new Set<string>();
for await (const d of await fs.opendir(dir)) {
const entry = path.join(dir, d.name);
seenFiles.add(entry);
if (d.isDirectory()) {
yield* walkDirectory(entry);
} else if (d.isFile()) {
yield entry;
}
}
}
/**
* Pluralizes a word.
* Example: Returns "N repository" if N is one, "N repositories" otherwise.
*/
export function pluralize(numItems: number | undefined, singular: string, plural: string): string {
return numItems ? `${numItems} ${numItems === 1 ? singular : plural}` : '';
}

View File

@@ -0,0 +1,118 @@
import { env } from 'vscode';
import * as path from 'path';
import { QueryHistoryConfig } from './config';
import { LocalQueryInfo } from './query-results';
import { getRawQueryName, QueryHistoryInfo } from './query-history-info';
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
import { pluralize } from './helpers';
import { VariantAnalysisHistoryItem } from './remote-queries/variant-analysis-history-item';
import { assertNever } from './pure/helpers-pure';
interface InterpolateReplacements {
t: string; // Start time
q: string; // Query name
d: string; // Database/Controller repo name
r: string; // Result count/Empty
s: string; // Status
f: string; // Query file name
'%': '%'; // Percent sign
}
export class HistoryItemLabelProvider {
constructor(private config: QueryHistoryConfig) {
/**/
}
getLabel(item: QueryHistoryInfo) {
let replacements: InterpolateReplacements;
switch (item.t) {
case 'local':
replacements = this.getLocalInterpolateReplacements(item);
break;
case 'remote':
replacements = this.getRemoteInterpolateReplacements(item);
break;
case 'variant-analysis':
replacements = this.getVariantAnalysisInterpolateReplacements(item);
break;
default:
assertNever(item);
}
const rawLabel = item.userSpecifiedLabel ?? (this.config.format || '%q');
return this.interpolate(rawLabel, replacements);
}
/**
* If there is a user-specified label for this query, interpolate and use that.
* Otherwise, use the raw name of this query.
*
* @returns the name of the query, unless there is a custom label for this query.
*/
getShortLabel(item: QueryHistoryInfo): string {
return item.userSpecifiedLabel
? this.getLabel(item)
: getRawQueryName(item);
}
private interpolate(rawLabel: string, replacements: InterpolateReplacements): string {
const label = rawLabel.replace(/%(.)/g, (match, key: keyof InterpolateReplacements) => {
const replacement = replacements[key];
return replacement !== undefined ? replacement : match;
});
return label.replace(/\s+/g, ' ');
}
private getLocalInterpolateReplacements(item: LocalQueryInfo): InterpolateReplacements {
const { resultCount = 0, statusString = 'in progress' } = item.completedQuery || {};
return {
t: item.startTime,
q: item.getQueryName(),
d: item.initialInfo.databaseInfo.name,
r: `(${resultCount} results)`,
s: statusString,
f: item.getQueryFileName(),
'%': '%',
};
}
// Return the number of repositories queried if available. Otherwise, use the controller repository name.
private buildRepoLabel(item: RemoteQueryHistoryItem): string {
const repositoryCount = item.remoteQuery.repositoryCount;
if (repositoryCount) {
return pluralize(repositoryCount, 'repository', 'repositories');
}
return `${item.remoteQuery.controllerRepository.owner}/${item.remoteQuery.controllerRepository.name}`;
}
private getRemoteInterpolateReplacements(item: RemoteQueryHistoryItem): InterpolateReplacements {
const resultCount = item.resultCount ? `(${pluralize(item.resultCount, 'result', 'results')})` : '';
return {
t: new Date(item.remoteQuery.executionStartTime).toLocaleString(env.language),
q: `${item.remoteQuery.queryName} (${item.remoteQuery.language})`,
d: this.buildRepoLabel(item),
r: resultCount,
s: item.status,
f: path.basename(item.remoteQuery.queryFilePath),
'%': '%'
};
}
private getVariantAnalysisInterpolateReplacements(item: VariantAnalysisHistoryItem): InterpolateReplacements {
const resultCount = item.resultCount ? `(${pluralize(item.resultCount, 'result', 'results')})` : '';
return {
t: new Date(item.variantAnalysis.executionStartTime).toLocaleString(env.language),
q: `${item.variantAnalysis.query.name} (${item.variantAnalysis.query.language})`,
d: 'TODO',
r: resultCount,
s: item.status,
f: path.basename(item.variantAnalysis.query.filePath),
'%': '%',
};
}
}

View File

@@ -1,8 +1,10 @@
import * as crypto from 'crypto';
import * as os from 'os';
import {
Uri,
Location,
Range,
ExtensionContext,
WebviewPanel,
Webview,
workspace,
@@ -70,7 +72,7 @@ function resolveFivePartLocation(
Math.max(0, loc.startLine - 1),
Math.max(0, loc.startColumn - 1),
Math.max(0, loc.endLine - 1),
Math.max(0, loc.endColumn)
Math.max(1, loc.endColumn)
);
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
@@ -110,20 +112,54 @@ export function tryResolveLocation(
}
}
export type WebviewView = 'results' | 'compare' | 'remote-queries' | 'variant-analysis';
export interface WebviewMessage {
t: string;
}
/**
* Returns HTML to populate the given webview.
* Uses a content security policy that only loads the given script.
*/
export function getHtmlForWebview(
ctx: ExtensionContext,
webview: Webview,
scriptUriOnDisk: Uri,
stylesheetUriOnDisk: Uri
view: WebviewView,
{
allowInlineStyles,
}: {
allowInlineStyles?: boolean;
} = {
allowInlineStyles: false,
}
): string {
const scriptUriOnDisk = Uri.file(
ctx.asAbsolutePath('out/webview.js')
);
const stylesheetUrisOnDisk = [
Uri.file(ctx.asAbsolutePath('out/webview.css'))
];
// Convert the on-disk URIs into webview URIs.
const scriptWebviewUri = webview.asWebviewUri(scriptUriOnDisk);
const stylesheetWebviewUri = webview.asWebviewUri(stylesheetUriOnDisk);
const stylesheetWebviewUris = stylesheetUrisOnDisk.map(stylesheetUriOnDisk =>
webview.asWebviewUri(stylesheetUriOnDisk));
// Use a nonce in the content security policy to uniquely identify the above resources.
const nonce = getNonce();
const stylesheetsHtmlLines = allowInlineStyles
? stylesheetWebviewUris.map(uri => createStylesLinkWithoutNonce(uri))
: stylesheetWebviewUris.map(uri => createStylesLinkWithNonce(nonce, uri));
const styleSrc = allowInlineStyles
? `${webview.cspSource} vscode-file: 'unsafe-inline'`
: `'nonce-${nonce}'`;
const fontSrc = webview.cspSource;
/*
* Content security policy:
* default-src: allow nothing by default.
@@ -136,11 +172,11 @@ export function getHtmlForWebview(
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src ${webview.cspSource};">
<link nonce="${nonce}" rel="stylesheet" href="${stylesheetWebviewUri}">
content="default-src 'none'; script-src 'nonce-${nonce}'; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${webview.cspSource};">
${stylesheetsHtmlLines.join(` ${os.EOL}`)}
</head>
<body>
<div id=root>
<div id=root data-view="${view}">
</div>
<script nonce="${nonce}" src="${scriptWebviewUri}">
</script>
@@ -224,15 +260,23 @@ export async function jumpToLocation(
} catch (e) {
if (e instanceof Error) {
if (e.message.match(/File not found/)) {
Window.showErrorMessage(
void Window.showErrorMessage(
'Original file of this result is not in the database\'s source archive.'
);
} else {
logger.log(`Unable to handleMsgFromView: ${e.message}`);
void logger.log(`Unable to handleMsgFromView: ${e.message}`);
}
} else {
logger.log(`Unable to handleMsgFromView: ${e}`);
void logger.log(`Unable to handleMsgFromView: ${e}`);
}
}
}
}
function createStylesLinkWithNonce(nonce: string, uri: Uri): string {
return `<link nonce="${nonce}" rel="stylesheet" href="${uri}">`;
}
function createStylesLinkWithoutNonce(uri: Uri): string {
return `<link rel="stylesheet" href="${uri}">`;
}

View File

@@ -1,6 +1,4 @@
import * as path from 'path';
import * as Sarif from 'sarif';
import { DisposableObject } from './vscode-utils/disposable-object';
import * as vscode from 'vscode';
import {
Diagnostic,
@@ -15,7 +13,7 @@ import * as cli from './cli';
import { CodeQLCliServer } from './cli';
import { DatabaseEventKind, DatabaseItem, DatabaseManager } from './databases';
import { showAndLogErrorMessage } from './helpers';
import { assertNever } from './pure/helpers-pure';
import { assertNever, getErrorMessage, getErrorStack } from './pure/helpers-pure';
import {
FromResultsViewMsg,
Interpretation,
@@ -27,26 +25,28 @@ import {
InterpretedResultsSortState,
SortDirection,
ALERTS_TABLE_NAME,
GRAPH_TABLE_NAME,
RawResultsSortState,
} from './pure/interface-types';
import { Logger } from './logging';
import { commandRunner } from './helpers';
import * as messages from './pure/messages';
import { CompletedQuery, interpretResults } from './query-results';
import { QueryInfo, tmpDir } from './run-queries';
import { commandRunner } from './commandRunner';
import { CompletedQueryInfo, interpretResultsSarif, interpretGraphResults } from './query-results';
import { QueryEvaluationInfo } from './run-queries-shared';
import { parseSarifLocation, parseSarifPlainTextMessage } from './pure/sarif-utils';
import {
WebviewReveal,
fileUriToWebviewUri,
tryResolveLocation,
getHtmlForWebview,
shownLocationDecoration,
shownLocationLineDecoration,
jumpToLocation,
} from './interface-utils';
import { getDefaultResultSetName, ParsedResultSets } from './pure/interface-types';
import { RawResultSet, transformBqrsResultSet, ResultSetSchema } from './pure/bqrs-cli-types';
import { AbstractWebview, WebviewPanelConfig } from './abstract-webview';
import { PAGE_SIZE } from './config';
import { CompletedLocalQueryInfo } from './query-results';
import { HistoryItemLabelProvider } from './history-item-label-provider';
/**
* interface.ts
@@ -87,20 +87,41 @@ function sortInterpretedResults(
}
}
function numPagesOfResultSet(resultSet: RawResultSet): number {
return Math.ceil(resultSet.schema.rows / PAGE_SIZE.getValue<number>());
function interpretedPageSize(interpretation: Interpretation | undefined): number {
if (interpretation?.data.t == 'GraphInterpretationData') {
// Graph views always have one result per page.
return 1;
}
return PAGE_SIZE.getValue<number>();
}
function numPagesOfResultSet(resultSet: RawResultSet, interpretation?: Interpretation): number {
const pageSize = interpretedPageSize(interpretation);
const n = interpretation?.data.t == 'GraphInterpretationData'
? interpretation.data.dot.length
: resultSet.schema.rows;
return Math.ceil(n / pageSize);
}
function numInterpretedPages(interpretation: Interpretation | undefined): number {
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / PAGE_SIZE.getValue<number>());
if (!interpretation) {
return 0;
}
const pageSize = interpretedPageSize(interpretation);
const n = interpretation.data.t == 'GraphInterpretationData'
? interpretation.data.dot.length
: interpretation.data.runs[0].results?.length || 0;
return Math.ceil(n / pageSize);
}
export class InterfaceManager extends DisposableObject {
private _displayedQuery?: CompletedQuery;
export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResultsViewMsg> {
private _displayedQuery?: CompletedLocalQueryInfo;
private _interpretation?: Interpretation;
private _panel: vscode.WebviewPanel | undefined;
private _panelLoaded = false;
private _panelLoadedCallBacks: (() => void)[] = [];
private readonly _diagnosticCollection = languages.createDiagnosticCollection(
'codeql-query-results'
@@ -110,16 +131,17 @@ export class InterfaceManager extends DisposableObject {
public ctx: vscode.ExtensionContext,
private databaseManager: DatabaseManager,
public cliServer: CodeQLCliServer,
public logger: Logger
public logger: Logger,
private labelProvider: HistoryItemLabelProvider
) {
super();
super(ctx);
this.push(this._diagnosticCollection);
this.push(
vscode.window.onDidChangeTextEditorSelection(
this.handleSelectionChange.bind(this)
)
);
logger.log('Registering path-step navigation commands.');
void logger.log('Registering path-step navigation commands.');
this.push(
commandRunner(
'codeQLQueryResults.nextPathStep',
@@ -137,77 +159,132 @@ export class InterfaceManager extends DisposableObject {
this.databaseManager.onDidChangeDatabaseItem(({ kind }) => {
if (kind === DatabaseEventKind.Remove) {
this._diagnosticCollection.clear();
this.postMessage({
t: 'untoggleShowProblems'
});
if (this.isShowingPanel) {
void this.postMessage({
t: 'untoggleShowProblems'
});
}
}
})
);
}
async navigatePathStep(direction: number): Promise<void> {
this.postMessage({ t: 'navigatePath', direction });
await this.postMessage({ t: 'navigatePath', direction });
}
// Returns the webview panel, creating it if it doesn't already
// exist.
getPanel(): vscode.WebviewPanel {
if (this._panel == undefined) {
const { ctx } = this;
const panel = (this._panel = Window.createWebviewPanel(
'resultsView', // internal name
'CodeQL Query Results', // user-visible name
{ viewColumn: vscode.ViewColumn.Beside, preserveFocus: true },
{
enableScripts: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: [
vscode.Uri.file(tmpDir.name),
vscode.Uri.file(path.join(this.ctx.extensionPath, 'out'))
]
protected getPanelConfig(): WebviewPanelConfig {
return {
viewId: 'resultsView',
title: 'CodeQL Query Results',
viewColumn: this.chooseColumnForWebview(),
preserveFocus: true,
view: 'results',
};
}
protected onPanelDispose(): void {
this._displayedQuery = undefined;
}
protected async onMessage(msg: FromResultsViewMsg): Promise<void> {
try {
switch (msg.t) {
case 'viewLoaded':
this.onWebViewLoaded();
break;
case 'viewSourceFile': {
await jumpToLocation(msg, this.databaseManager, this.logger);
break;
}
));
this._panel.onDidDispose(
() => {
this._panel = undefined;
this._displayedQuery = undefined;
},
null,
ctx.subscriptions
);
const scriptPathOnDisk = vscode.Uri.file(
ctx.asAbsolutePath('out/resultsView.js')
);
const stylesheetPathOnDisk = vscode.Uri.file(
ctx.asAbsolutePath('out/resultsView.css')
);
panel.webview.html = getHtmlForWebview(
panel.webview,
scriptPathOnDisk,
stylesheetPathOnDisk
);
panel.webview.onDidReceiveMessage(
async (e) => this.handleMsgFromView(e),
undefined,
ctx.subscriptions
);
case 'toggleDiagnostics': {
if (msg.visible) {
const databaseItem = this.databaseManager.findDatabaseItem(
Uri.parse(msg.databaseUri)
);
if (databaseItem !== undefined) {
await this.showResultsAsDiagnostics(
msg.origResultsPaths,
msg.metadata,
databaseItem
);
}
} else {
// TODO: Only clear diagnostics on the same database.
this._diagnosticCollection.clear();
}
break;
}
case 'changeSort':
await this.changeRawSortState(msg.resultSetName, msg.sortState);
break;
case 'changeInterpretedSort':
await this.changeInterpretedSortState(msg.sortState);
break;
case 'changePage':
if (msg.selectedTable === ALERTS_TABLE_NAME || msg.selectedTable === GRAPH_TABLE_NAME) {
await this.showPageOfInterpretedResults(msg.pageNumber);
}
else {
await this.showPageOfRawResults(
msg.selectedTable,
msg.pageNumber,
// When we are in an unsorted state, we guarantee that
// sortedResultsInfo doesn't have an entry for the current
// result set. Use this to determine whether or not we use
// the sorted bqrs file.
!!this._displayedQuery?.completedQuery.sortedResultsInfo[msg.selectedTable]
);
}
break;
case 'openFile':
await this.openFile(msg.filePath);
break;
default:
assertNever(msg);
}
} catch (e) {
void showAndLogErrorMessage(getErrorMessage(e), {
fullMessage: getErrorStack(e)
});
}
return this._panel;
}
/**
* Choose where to open the webview.
*
* If there is a single view column, then open beside it.
* If there are multiple view columns, then open beside the active column,
* unless the active editor is the last column. In this case, open in the first column.
*
* The goal is to avoid opening new columns when there already are two columns open.
*/
private chooseColumnForWebview(): vscode.ViewColumn {
// This is not a great way to determine the number of view columns, but I
// can't find a vscode API that does it any better.
// Here, iterate through all the visible editors and determine the max view column.
// This won't work if the largest view column is empty.
const colCount = Window.visibleTextEditors.reduce((maxVal, editor) =>
Math.max(maxVal, Number.parseInt(editor.viewColumn?.toFixed() || '0', 10)), 0);
if (colCount <= 1) {
return vscode.ViewColumn.Beside;
}
const activeViewColumnNum = Number.parseInt(Window.activeTextEditor?.viewColumn?.toFixed() || '0', 10);
return activeViewColumnNum === colCount ? vscode.ViewColumn.One : vscode.ViewColumn.Beside;
}
private async changeInterpretedSortState(
sortState: InterpretedResultsSortState | undefined
): Promise<void> {
if (this._displayedQuery === undefined) {
showAndLogErrorMessage(
void showAndLogErrorMessage(
'Failed to sort results since evaluation info was unknown.'
);
return;
}
// Notify the webview that it should expect new results.
await this.postMessage({ t: 'resultsUpdating' });
this._displayedQuery.updateInterpretedSortState(sortState);
await this._displayedQuery.completedQuery.updateInterpretedSortState(sortState);
await this.showResults(this._displayedQuery, WebviewReveal.NotForced, true);
}
@@ -216,14 +293,14 @@ export class InterfaceManager extends DisposableObject {
sortState: RawResultsSortState | undefined
): Promise<void> {
if (this._displayedQuery === undefined) {
showAndLogErrorMessage(
void showAndLogErrorMessage(
'Failed to sort results since evaluation info was unknown.'
);
return;
}
// Notify the webview that it should expect new results.
await this.postMessage({ t: 'resultsUpdating' });
await this._displayedQuery.updateSortState(
await this._displayedQuery.completedQuery.updateSortState(
this.cliServer,
resultSetName,
sortState
@@ -235,79 +312,9 @@ export class InterfaceManager extends DisposableObject {
await this.showPageOfRawResults(resultSetName, 0, true);
}
private async handleMsgFromView(msg: FromResultsViewMsg): Promise<void> {
switch (msg.t) {
case 'viewSourceFile': {
await jumpToLocation(msg, this.databaseManager, this.logger);
break;
}
case 'toggleDiagnostics': {
if (msg.visible) {
const databaseItem = this.databaseManager.findDatabaseItem(
Uri.parse(msg.databaseUri)
);
if (databaseItem !== undefined) {
await this.showResultsAsDiagnostics(
msg.origResultsPaths,
msg.metadata,
databaseItem
);
}
} else {
// TODO: Only clear diagnostics on the same database.
this._diagnosticCollection.clear();
}
break;
}
case 'resultViewLoaded':
this._panelLoaded = true;
this._panelLoadedCallBacks.forEach((cb) => cb());
this._panelLoadedCallBacks = [];
break;
case 'changeSort':
await this.changeRawSortState(msg.resultSetName, msg.sortState);
break;
case 'changeInterpretedSort':
await this.changeInterpretedSortState(msg.sortState);
break;
case 'changePage':
if (msg.selectedTable === ALERTS_TABLE_NAME) {
await this.showPageOfInterpretedResults(msg.pageNumber);
}
else {
await this.showPageOfRawResults(
msg.selectedTable,
msg.pageNumber,
// When we are in an unsorted state, we guarantee that
// sortedResultsInfo doesn't have an entry for the current
// result set. Use this to determine whether or not we use
// the sorted bqrs file.
this._displayedQuery?.sortedResultsInfo.has(msg.selectedTable) || false
);
}
break;
default:
assertNever(msg);
}
}
postMessage(msg: IntoResultsViewMsg): Thenable<boolean> {
return this.getPanel().webview.postMessage(msg);
}
private waitForPanelLoaded(): Promise<void> {
return new Promise((resolve) => {
if (this._panelLoaded) {
resolve();
} else {
this._panelLoadedCallBacks.push(resolve);
}
});
}
/**
* Show query results in webview panel.
* @param results Evaluation info for the executed query.
* @param fullQuery Evaluation info for the executed query.
* @param shouldKeepOldResultsWhileRendering Should keep old results while rendering.
* @param forceReveal Force the webview panel to be visible and
* Appropriate when the user has just performed an explicit
@@ -315,58 +322,59 @@ export class InterfaceManager extends DisposableObject {
* history entry.
*/
public async showResults(
results: CompletedQuery,
fullQuery: CompletedLocalQueryInfo,
forceReveal: WebviewReveal,
shouldKeepOldResultsWhileRendering = false
): Promise<void> {
if (results.result.resultType !== messages.QueryResultType.SUCCESS) {
if (!fullQuery.completedQuery.successful) {
return;
}
this._interpretation = undefined;
const interpretationPage = await this.interpretResultsInfo(
results.query,
results.interpretedResultsSortState
fullQuery.completedQuery.query,
fullQuery.completedQuery.interpretedResultsSortState
);
const sortedResultsMap: SortedResultsMap = {};
results.sortedResultsInfo.forEach(
(v, k) =>
Object.entries(fullQuery.completedQuery.sortedResultsInfo).forEach(
([k, v]) =>
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v))
);
this._displayedQuery = results;
this._displayedQuery = fullQuery;
const panel = this.getPanel();
await this.waitForPanelLoaded();
if (forceReveal === WebviewReveal.Forced) {
panel.reveal(undefined, true);
} else if (!panel.visible) {
// The results panel exists, (`.getPanel()` guarantees it) but
// is not visible; it's in a not-currently-viewed tab. Show a
// more asynchronous message to not so abruptly interrupt
// user's workflow by immediately revealing the panel.
const showButton = 'View Results';
const queryName = results.queryName;
const resultPromise = vscode.window.showInformationMessage(
`Finished running query ${
queryName.length > 0 ? ` "${queryName}"` : ''
}.`,
showButton
);
// Address this click asynchronously so we still update the
// query history immediately.
resultPromise.then((result) => {
if (result === showButton) {
panel.reveal();
}
});
if (!panel.visible) {
if (forceReveal === WebviewReveal.Forced) {
panel.reveal(undefined, true);
} else {
// The results panel exists, (`.getPanel()` guarantees it) but
// is not visible; it's in a not-currently-viewed tab. Show a
// more asynchronous message to not so abruptly interrupt
// user's workflow by immediately revealing the panel.
const showButton = 'View Results';
const queryName = this.labelProvider.getShortLabel(fullQuery);
const resultPromise = vscode.window.showInformationMessage(
`Finished running query ${queryName.length > 0 ? ` "${queryName}"` : ''
}.`,
showButton
);
// Address this click asynchronously so we still update the
// query history immediately.
void resultPromise.then((result) => {
if (result === showButton) {
panel.reveal();
}
});
}
}
// Note that the resultSetSchemas will return offsets for the default (unsorted) page,
// which may not be correct. However, in this case, it doesn't matter since we only
// need the first offset, which will be the same no matter which sorting we use.
const resultSetSchemas = await this.getResultSetSchemas(results);
const resultSetSchemas = await this.getResultSetSchemas(fullQuery.completedQuery);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
const selectedTable = getDefaultResultSetName(resultSetNames);
@@ -376,7 +384,7 @@ export class InterfaceManager extends DisposableObject {
// Use sorted results path if it exists. This may happen if we are
// reloading the results view after it has been sorted in the past.
const resultsPath = results.getResultsPath(selectedTable);
const resultsPath = fullQuery.completedQuery.getResultsPath(selectedTable);
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
resultsPath,
@@ -391,10 +399,11 @@ export class InterfaceManager extends DisposableObject {
}
);
const resultSet = transformBqrsResultSet(schema, chunk);
fullQuery.completedQuery.setResultCount(interpretationPage?.numTotalResults || resultSet.schema.rows);
const parsedResultSets: ParsedResultSets = {
pageNumber: 0,
pageSize,
numPages: numPagesOfResultSet(resultSet),
numPages: numPagesOfResultSet(resultSet, this._interpretation),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { ...resultSet, t: 'RawResultSet' },
selectedTable: undefined,
@@ -404,15 +413,17 @@ export class InterfaceManager extends DisposableObject {
await this.postMessage({
t: 'setState',
interpretation: interpretationPage,
origResultsPaths: results.query.resultsPaths,
origResultsPaths: fullQuery.completedQuery.query.resultsPaths,
resultsPath: this.convertPathToWebviewUri(
results.query.resultsPaths.resultsPath
fullQuery.completedQuery.query.resultsPaths.resultsPath
),
parsedResultSets,
sortedResultsMap,
database: results.database,
database: fullQuery.initialInfo.databaseInfo,
shouldKeepOldResultsWhileRendering,
metadata: results.query.metadata,
metadata: fullQuery.completedQuery.query.metadata,
queryName: this.labelProvider.getLabel(fullQuery),
queryPath: fullQuery.initialInfo.queryPath
});
}
@@ -428,27 +439,29 @@ export class InterfaceManager extends DisposableObject {
if (this._interpretation === undefined) {
throw new Error('Trying to show interpreted results but interpretation was undefined');
}
if (this._interpretation.sarif.runs[0].results === undefined) {
if (this._interpretation.data.t === 'SarifInterpretationData' && this._interpretation.data.runs[0].results === undefined) {
throw new Error('Trying to show interpreted results but results were undefined');
}
const resultSetSchemas = await this.getResultSetSchemas(this._displayedQuery);
const resultSetSchemas = await this.getResultSetSchemas(this._displayedQuery.completedQuery);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
await this.postMessage({
t: 'showInterpretedPage',
interpretation: this.getPageOfInterpretedResults(pageNumber),
database: this._displayedQuery.database,
metadata: this._displayedQuery.query.metadata,
database: this._displayedQuery.initialInfo.databaseInfo,
metadata: this._displayedQuery.completedQuery.query.metadata,
pageNumber,
resultSetNames,
pageSize: PAGE_SIZE.getValue(),
pageSize: interpretedPageSize(this._interpretation),
numPages: numInterpretedPages(this._interpretation),
queryName: this.labelProvider.getLabel(this._displayedQuery),
queryPath: this._displayedQuery.initialInfo.queryPath
});
}
private async getResultSetSchemas(results: CompletedQuery, selectedTable = ''): Promise<ResultSetSchema[]> {
const resultsPath = results.getResultsPath(selectedTable);
private async getResultSetSchemas(completedQuery: CompletedQueryInfo, selectedTable = ''): Promise<ResultSetSchema[]> {
const resultsPath = completedQuery.getResultsPath(selectedTable);
const schemas = await this.cliServer.bqrsInfo(
resultsPath,
PAGE_SIZE.getValue()
@@ -456,6 +469,11 @@ export class InterfaceManager extends DisposableObject {
return schemas['result-sets'];
}
public async openFile(filePath: string) {
const textDocument = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
}
/**
* Show a page of raw results from the chosen table.
*/
@@ -470,13 +488,18 @@ export class InterfaceManager extends DisposableObject {
}
const sortedResultsMap: SortedResultsMap = {};
results.sortedResultsInfo.forEach(
(v, k) =>
Object.entries(results.completedQuery.sortedResultsInfo).forEach(
([k, v]) =>
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v))
);
const resultSetSchemas = await this.getResultSetSchemas(results, sorted ? selectedTable : '');
const resultSetNames = resultSetSchemas.map(schema => schema.name);
const resultSetSchemas = await this.getResultSetSchemas(results.completedQuery, sorted ? selectedTable : '');
// If there is a specific sorted table selected, a different bqrs file is loaded that doesn't have all the result set names.
// Make sure that we load all result set names here.
// See https://github.com/github/vscode-codeql/issues/1005
const allResultSetSchemas = sorted ? await this.getResultSetSchemas(results.completedQuery, '') : resultSetSchemas;
const resultSetNames = allResultSetSchemas.map(schema => schema.name);
const schema = resultSetSchemas.find(
(resultSet) => resultSet.name == selectedTable
@@ -486,7 +509,7 @@ export class InterfaceManager extends DisposableObject {
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
results.getResultsPath(selectedTable, sorted),
results.completedQuery.getResultsPath(selectedTable, sorted),
schema.name,
{
offset: schema.pagination?.offsets[pageNumber],
@@ -508,15 +531,17 @@ export class InterfaceManager extends DisposableObject {
await this.postMessage({
t: 'setState',
interpretation: this._interpretation,
origResultsPaths: results.query.resultsPaths,
origResultsPaths: results.completedQuery.query.resultsPaths,
resultsPath: this.convertPathToWebviewUri(
results.query.resultsPaths.resultsPath
results.completedQuery.query.resultsPaths.resultsPath
),
parsedResultSets,
sortedResultsMap,
database: results.database,
database: results.initialInfo.databaseInfo,
shouldKeepOldResultsWhileRendering: false,
metadata: results.query.metadata,
metadata: results.completedQuery.query.metadata,
queryName: this.labelProvider.getLabel(results),
queryPath: results.initialInfo.queryPath
});
}
@@ -526,30 +551,50 @@ export class InterfaceManager extends DisposableObject {
sourceInfo: cli.SourceInfo | undefined,
sourceLocationPrefix: string,
sortState: InterpretedResultsSortState | undefined
): Promise<Interpretation> {
const sarif = await interpretResults(
this.cliServer,
metadata,
resultsPaths,
sourceInfo
);
sarif.runs.forEach(run => {
if (run.results !== undefined)
sortInterpretedResults(run.results, sortState);
});
): Promise<Interpretation | undefined> {
if (!resultsPaths) {
void this.logger.log('No results path. Cannot display interpreted results.');
return undefined;
}
let data;
let numTotalResults;
if (metadata?.kind === GRAPH_TABLE_NAME) {
data = await interpretGraphResults(
this.cliServer,
metadata,
resultsPaths,
sourceInfo
);
numTotalResults = data.dot.length;
} else {
const sarif = await interpretResultsSarif(
this.cliServer,
metadata,
resultsPaths,
sourceInfo
);
const numTotalResults = (() => {
if (sarif.runs.length === 0) return 0;
if (sarif.runs[0].results === undefined) return 0;
return sarif.runs[0].results.length;
})();
sarif.runs.forEach(run => {
if (run.results) {
sortInterpretedResults(run.results, sortState);
}
});
sarif.sortState = sortState;
data = sarif;
numTotalResults = (() => {
return sarif.runs?.[0]?.results
? sarif.runs[0].results.length
: 0;
})();
}
const interpretation: Interpretation = {
sarif,
data,
sourceLocationPrefix,
numTruncatedResults: 0,
numTotalResults,
sortState,
numTotalResults
};
this._interpretation = interpretation;
return interpretation;
@@ -558,7 +603,6 @@ export class InterfaceManager extends DisposableObject {
private getPageOfInterpretedResults(
pageNumber: number
): Interpretation {
function getPageOfRun(run: Sarif.Run): Sarif.Run {
return {
...run, results: run.results?.slice(
@@ -568,32 +612,44 @@ export class InterfaceManager extends DisposableObject {
};
}
if (this._interpretation === undefined) {
const interp = this._interpretation;
if (interp === undefined) {
throw new Error('Tried to get interpreted results before interpretation finished');
}
if (this._interpretation.sarif.runs.length !== 1) {
this.logger.log(`Warning: SARIF file had ${this._interpretation.sarif.runs.length} runs, expected 1`);
if (interp.data.t !== 'SarifInterpretationData')
return interp;
if (interp.data.runs.length !== 1) {
void this.logger.log(`Warning: SARIF file had ${interp.data.runs.length} runs, expected 1`);
}
const interp = this._interpretation;
return {
...interp,
sarif: { ...interp.sarif, runs: [getPageOfRun(interp.sarif.runs[0])] },
data: {
...interp.data,
runs: [getPageOfRun(interp.data.runs[0])]
}
};
}
private async interpretResultsInfo(
query: QueryInfo,
query: QueryEvaluationInfo,
sortState: InterpretedResultsSortState | undefined
): Promise<Interpretation | undefined> {
if (
(await query.canHaveInterpretedResults()) &&
query.canHaveInterpretedResults() &&
query.quickEvalPosition === undefined // never do results interpretation if quickEval
) {
try {
const sourceLocationPrefix = await query.dbItem.getSourceLocationPrefix(
const dbItem = this.databaseManager.findDatabaseItem(Uri.file(query.dbItemPath));
if (!dbItem) {
throw new Error(`Could not find database item for ${query.dbItemPath}`);
}
const sourceLocationPrefix = await dbItem.getSourceLocationPrefix(
this.cliServer
);
const sourceArchiveUri = query.dbItem.sourceArchive;
const sourceArchiveUri = dbItem.sourceArchive;
const sourceInfo =
sourceArchiveUri === undefined
? undefined
@@ -611,8 +667,8 @@ export class InterfaceManager extends DisposableObject {
} catch (e) {
// If interpretation fails, accept the error and continue
// trying to render uninterpreted results anyway.
this.logger.log(
`Exception during results interpretation: ${e.message}. Will show raw results instead.`
void showAndLogErrorMessage(
`Showing raw results instead of interpreted ones due to an error. ${getErrorMessage(e)}`
);
}
}
@@ -644,12 +700,15 @@ export class InterfaceManager extends DisposableObject {
undefined
);
if (!interpretation) {
return;
}
try {
await this.showProblemResultsAsDiagnostics(interpretation, database);
} catch (e) {
const msg = e instanceof Error ? e.message : e.toString();
this.logger.log(
`Exception while computing problem results as diagnostics: ${msg}`
void this.logger.log(
`Exception while computing problem results as diagnostics: ${getErrorMessage(e)}`
);
this._diagnosticCollection.clear();
}
@@ -659,10 +718,13 @@ export class InterfaceManager extends DisposableObject {
interpretation: Interpretation,
databaseItem: DatabaseItem
): Promise<void> {
const { sarif, sourceLocationPrefix } = interpretation;
const { data, sourceLocationPrefix } = interpretation;
if (!sarif.runs || !sarif.runs[0].results) {
this.logger.log(
if (data.t !== 'SarifInterpretationData')
return;
if (!data.runs || !data.runs[0].results) {
void this.logger.log(
'Didn\'t find a run in the sarif results. Error processing sarif?'
);
return;
@@ -670,14 +732,14 @@ export class InterfaceManager extends DisposableObject {
const diagnostics: [Uri, ReadonlyArray<Diagnostic>][] = [];
for (const result of sarif.runs[0].results) {
for (const result of data.runs[0].results) {
const message = result.message.text;
if (message === undefined) {
this.logger.log('Sarif had result without plaintext message');
void this.logger.log('Sarif had result without plaintext message');
continue;
}
if (!result.locations) {
this.logger.log('Sarif had result without location');
void this.logger.log('Sarif had result without location');
continue;
}
@@ -690,7 +752,7 @@ export class InterfaceManager extends DisposableObject {
}
const resultLocation = tryResolveLocation(sarifLoc, databaseItem);
if (!resultLocation) {
this.logger.log('Sarif location was not resolvable ' + sarifLoc);
void this.logger.log('Sarif location was not resolvable ' + sarifLoc);
continue;
}
const parsedMessage = parseSarifPlainTextMessage(message);

View File

@@ -0,0 +1,30 @@
import { Logger } from './logging';
import * as cp from 'child_process';
import { Disposable } from 'vscode';
import { MessageConnection } from 'vscode-jsonrpc';
/** A running query server process and its associated message connection. */
export class ServerProcess implements Disposable {
child: cp.ChildProcess;
connection: MessageConnection;
logger: Logger;
constructor(child: cp.ChildProcess, connection: MessageConnection, private name: string, logger: Logger) {
this.child = child;
this.connection = connection;
this.logger = logger;
}
dispose(): void {
void this.logger.log(`Stopping ${this.name}...`);
this.connection.dispose();
this.child.stdin!.end();
this.child.stderr!.destroy();
// TODO kill the process if it doesn't terminate after a certain time limit.
// On Windows, we usually have to terminate the process before closing its stdout.
this.child.stdout!.destroy();
void this.logger.log(`Stopped ${this.name}.`);
}
}

View File

@@ -0,0 +1,65 @@
import { CancellationToken } from 'vscode';
import { ProgressCallback } from '../commandRunner';
import { DatabaseItem } from '../databases';
import { Dataset, deregisterDatabases, registerDatabases } from '../pure/legacy-messages';
import { InitialQueryInfo, LocalQueryInfo } from '../query-results';
import { QueryRunner } from '../queryRunner';
import { QueryWithResults } from '../run-queries-shared';
import { QueryServerClient } from './queryserver-client';
import { clearCacheInDatabase, compileAndRunQueryAgainstDatabase } from './run-queries';
import { upgradeDatabaseExplicit } from './upgrades';
export class LegacyQueryRunner extends QueryRunner {
constructor(public readonly qs: QueryServerClient) {
super();
}
get cliServer() {
return this.qs.cliServer;
}
async restartQueryServer(progress: ProgressCallback, token: CancellationToken): Promise<void> {
await this.qs.restartQueryServer(progress, token);
}
onStart(callBack: (progress: ProgressCallback, token: CancellationToken) => Promise<void>) {
this.qs.onDidStartQueryServer(callBack);
}
async clearCacheInDatabase(dbItem: DatabaseItem, progress: ProgressCallback, token: CancellationToken): Promise<void> {
await clearCacheInDatabase(this.qs, dbItem, progress, token);
}
async compileAndRunQueryAgainstDatabase(dbItem: DatabaseItem, initialInfo: InitialQueryInfo, queryStorageDir: string, progress: ProgressCallback, token: CancellationToken, templates?: Record<string, string>, queryInfo?: LocalQueryInfo): Promise<QueryWithResults> {
return await compileAndRunQueryAgainstDatabase(this.qs.cliServer, this.qs, dbItem, initialInfo, queryStorageDir, progress, token, templates, queryInfo);
}
async deregisterDatabase(progress: ProgressCallback, token: CancellationToken, dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents && (await this.qs.cliServer.cliConstraints.supportsDatabaseRegistration())) {
const databases: Dataset[] = [{
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default'
}];
await this.qs.sendRequest(deregisterDatabases, { databases }, token, progress);
}
}
async registerDatabase(progress: ProgressCallback, token: CancellationToken, dbItem: DatabaseItem): Promise<void> {
if (dbItem.contents && (await this.qs.cliServer.cliConstraints.supportsDatabaseRegistration())) {
const databases: Dataset[] = [{
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default'
}];
await this.qs.sendRequest(registerDatabases, { databases }, token, progress);
}
}
async upgradeDatabaseExplicit(dbItem: DatabaseItem, progress: ProgressCallback, token: CancellationToken): Promise<void> {
await upgradeDatabaseExplicit(this.qs, dbItem, progress, token);
}
async clearPackCache(): Promise<void> {
/**
* Nothing needs to be done
*/
}
}

View File

@@ -1,45 +1,25 @@
import * as cp from 'child_process';
import * as path from 'path';
import { DisposableObject } from './vscode-utils/disposable-object';
import { Disposable } from 'vscode';
import { CancellationToken, createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc';
import * as cli from './cli';
import { QueryServerConfig } from './config';
import { Logger, ProgressReporter } from './logging';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './pure/messages';
import * as messages from './pure/messages';
import * as fs from 'fs-extra';
import { DisposableObject } from '../pure/disposable-object';
import { CancellationToken, commands } from 'vscode';
import { createMessageConnection, RequestType } from 'vscode-jsonrpc';
import * as cli from '../cli';
import { QueryServerConfig } from '../config';
import { Logger, ProgressReporter } from '../logging';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from '../pure/legacy-messages';
import * as messages from '../pure/legacy-messages';
import { ProgressCallback, ProgressTask } from '../commandRunner';
import { findQueryLogFile } from '../run-queries-shared';
import { ServerProcess } from '../json-rpc-server';
type WithProgressReporting = (task: (progress: ProgressReporter, token: CancellationToken) => Thenable<void>) => Thenable<void>;
type ServerOpts = {
logger: Logger;
contextStoragePath: string;
}
/** A running query server process and its associated message connection. */
class ServerProcess implements Disposable {
child: cp.ChildProcess;
connection: MessageConnection;
logger: Logger;
constructor(child: cp.ChildProcess, connection: MessageConnection, logger: Logger) {
this.child = child;
this.connection = connection;
this.logger = logger;
}
dispose(): void {
this.logger.log('Stopping query server...');
this.connection.dispose();
this.child.stdin!.end();
this.child.stderr!.destroy();
// TODO kill the process if it doesn't terminate after a certain time limit.
// On Windows, we usually have to terminate the process before closing its stdout.
this.child.stdout!.destroy();
this.logger.log('Stopped query server.');
}
}
type WithProgressReporting = (task: (progress: ProgressReporter, token: CancellationToken) => Thenable<void>) => Thenable<void>;
/**
* Client that manages a query server process.
* The server process is started upon initialization and tracked during its lifetime.
@@ -47,22 +27,36 @@ type WithProgressReporting = (task: (progress: ProgressReporter, token: Cancella
* to restart it (which disposes the existing process and starts a new one).
*/
export class QueryServerClient extends DisposableObject {
serverProcess?: ServerProcess;
evaluationResultCallbacks: { [key: number]: (res: EvaluationResult) => void };
progressCallbacks: { [key: number]: ((res: ProgressMessage) => void) | undefined };
nextCallback: number;
nextProgress: number;
withProgressReporting: WithProgressReporting;
public activeQueryName: string | undefined;
constructor(readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, withProgressReporting: WithProgressReporting) {
private readonly queryServerStartListeners = [] as ProgressTask<void>[];
// Can't use standard vscode EventEmitter here since they do not cause the calling
// function to fail if one of the event handlers fail. This is something that
// we need here.
readonly onDidStartQueryServer = (e: ProgressTask<void>) => {
this.queryServerStartListeners.push(e);
}
public activeQueryLogFile: string | undefined;
constructor(
readonly config: QueryServerConfig,
readonly cliServer: cli.CodeQLCliServer,
readonly opts: ServerOpts,
withProgressReporting: WithProgressReporting
) {
super();
// When the query server configuration changes, restart the query server.
if (config.onDidChangeConfiguration !== undefined) {
this.push(config.onDidChangeConfiguration(async () => {
this.logger.log('Restarting query server due to configuration changes...');
await this.restartQueryServer();
}, this));
this.push(config.onDidChangeConfiguration(() =>
commands.executeCommand('codeQL.restartQueryServer')));
}
this.withProgressReporting = withProgressReporting;
this.nextCallback = 0;
@@ -80,14 +74,24 @@ export class QueryServerClient extends DisposableObject {
if (this.serverProcess !== undefined) {
this.disposeAndStopTracking(this.serverProcess);
} else {
this.logger.log('No server process to be stopped.');
void this.logger.log('No server process to be stopped.');
}
}
/** Restarts the query server by disposing of the current server process and then starting a new one. */
async restartQueryServer(): Promise<void> {
async restartQueryServer(
progress: ProgressCallback,
token: CancellationToken
): Promise<void> {
this.stopQueryServer();
await this.startQueryServer();
// Ensure we await all responses from event handlers so that
// errors can be properly reported to the user.
await Promise.all(this.queryServerStartListeners.map(handler => handler(
progress,
token
)));
}
showLog(): void {
@@ -104,12 +108,43 @@ export class QueryServerClient extends DisposableObject {
private async startQueryServerImpl(progressReporter: ProgressReporter): Promise<void> {
const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);
const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);
if (this.config.saveCache) {
args.push('--save-cache');
}
if (this.config.cacheSize > 0) {
args.push('--max-disk-cache');
args.push(this.config.cacheSize.toString());
}
if (await this.cliServer.cliConstraints.supportsDatabaseRegistration()) {
args.push('--require-db-registration');
}
if (await this.cliServer.cliConstraints.supportsOldEvalStats() && !(await this.cliServer.cliConstraints.supportsPerQueryEvalLog())) {
args.push('--old-eval-stats');
}
if (await this.cliServer.cliConstraints.supportsStructuredEvalLog()) {
const structuredLogFile = `${this.opts.contextStoragePath}/structured-evaluator-log.json`;
await fs.ensureFile(structuredLogFile);
args.push('--evaluator-log');
args.push(structuredLogFile);
// We hard-code the verbosity level to 5 and minify to false.
// This will be the behavior of the per-query structured logging in the CLI after 2.8.3.
args.push('--evaluator-log-level');
args.push('5');
}
if (this.config.debug) {
args.push('--debug', '--tuple-counting');
}
if (cli.shouldDebugQueryServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=y,suspend=n,quiet=y');
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=n,suspend=y,quiet=y');
}
const child = cli.spawnServer(
@@ -120,7 +155,7 @@ export class QueryServerClient extends DisposableObject {
this.logger,
data => this.logger.log(data.toString(), {
trailingNewline: false,
additionalLogLocation: this.activeQueryName
additionalLogLocation: this.activeQueryLogFile
}),
undefined, // no listener for stdout
progressReporter
@@ -129,13 +164,8 @@ export class QueryServerClient extends DisposableObject {
const connection = createMessageConnection(child.stdout, child.stdin);
connection.onRequest(completeQuery, res => {
if (!(res.runId in this.evaluationResultCallbacks)) {
this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);
}
else {
const baseLocation = this.logger.getBaseLocation();
if (baseLocation && this.activeQueryName) {
res.logFileLocation = path.join(baseLocation, this.activeQueryName);
}
void this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);
} else {
this.evaluationResultCallbacks[res.runId](res);
}
return {};
@@ -146,7 +176,7 @@ export class QueryServerClient extends DisposableObject {
callback(res);
}
});
this.serverProcess = new ServerProcess(child, connection, this.opts.logger);
this.serverProcess = new ServerProcess(child, connection, 'Query server', this.logger);
// Ensure the server process is disposed together with this client.
this.track(this.serverProcess);
connection.listen();
@@ -168,7 +198,7 @@ export class QueryServerClient extends DisposableObject {
}
get serverProcessPid(): number {
return this.serverProcess!.child.pid;
return this.serverProcess!.child.pid || 0;
}
async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {
@@ -196,8 +226,7 @@ export class QueryServerClient extends DisposableObject {
*/
private updateActiveQuery(method: string, parameter: any): void {
if (method === messages.compileQuery.method) {
const queryPath = parameter?.queryToCheck?.queryPath || 'unknown';
this.activeQueryName = `query-${path.basename(queryPath)}-${this.nextProgress}.log`;
this.activeQueryLogFile = findQueryLogFile(path.dirname(parameter.resultPath));
}
}
}

View File

@@ -0,0 +1,526 @@
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import * as tmp from 'tmp-promise';
import * as path from 'path';
import {
CancellationToken,
Uri,
} from 'vscode';
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
import * as cli from '../cli';
import { DatabaseItem, } from '../databases';
import {
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogWarningMessage,
tryGetQueryMetadata,
upgradesTmpDir
} from '../helpers';
import { ProgressCallback } from '../commandRunner';
import { QueryMetadata } from '../pure/interface-types';
import { logger } from '../logging';
import * as messages from '../pure/legacy-messages';
import { InitialQueryInfo, LocalQueryInfo } from '../query-results';
import * as qsClient from './queryserver-client';
import { getErrorMessage } from '../pure/helpers-pure';
import { compileDatabaseUpgradeSequence, upgradeDatabaseExplicit } from './upgrades';
import { QueryEvaluationInfo, QueryWithResults } from '../run-queries-shared';
/**
* A collection of evaluation-time information about a query,
* including the query itself, and where we have decided to put
* temporary files associated with it, such as the compiled query
* output and results.
*/
export class QueryInProgress {
public queryEvalInfo: QueryEvaluationInfo;
/**
* Note that in the {@link slurpQueryHistory} method, we create a QueryEvaluationInfo instance
* by explicitly setting the prototype in order to avoid calling this constructor.
*/
constructor(
readonly querySaveDir: string,
readonly dbItemPath: string,
databaseHasMetadataFile: boolean,
readonly queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution
readonly quickEvalPosition?: messages.Position,
readonly metadata?: QueryMetadata,
readonly templates?: Record<string, string>,
) {
this.queryEvalInfo = new QueryEvaluationInfo(querySaveDir, dbItemPath, databaseHasMetadataFile, quickEvalPosition, metadata);
/**/
}
get compiledQueryPath() {
return path.join(this.querySaveDir, 'compiledQuery.qlo');
}
async run(
qs: qsClient.QueryServerClient,
upgradeQlo: string | undefined,
availableMlModels: cli.MlModelInfo[],
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
queryInfo?: LocalQueryInfo,
): Promise<messages.EvaluationResult> {
if (!dbItem.contents || dbItem.error) {
throw new Error('Can\'t run query on invalid database.');
}
let result: messages.EvaluationResult | null = null;
const callbackId = qs.registerCallback(res => {
result = {
...res,
logFileLocation: this.queryEvalInfo.logPath
};
});
const availableMlModelUris: messages.MlModel[] = availableMlModels.map(model => ({ uri: Uri.file(model.path).toString(true) }));
const queryToRun: messages.QueryToRun = {
resultsPath: this.queryEvalInfo.resultsPaths.resultsPath,
qlo: Uri.file(this.compiledQueryPath).toString(),
compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(),
allowUnknownTemplates: true,
templateValues: createSimpleTemplates(this.templates),
availableMlModels: availableMlModelUris,
id: callbackId,
timeoutSecs: qs.config.timeoutSecs,
};
const dataset: messages.Dataset = {
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default'
};
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
await qs.sendRequest(messages.startLog, {
db: dataset,
logPath: this.queryEvalInfo.evalLogPath,
});
}
const params: messages.EvaluateQueriesParams = {
db: dataset,
evaluateId: callbackId,
queries: [queryToRun],
stopOnError: false,
useSequenceHint: false
};
try {
await qs.sendRequest(messages.runQueries, params, token, progress);
if (qs.config.customLogDirectory) {
void showAndLogWarningMessage(
`Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${this.queryEvalInfo.logPath}.`
);
}
} finally {
qs.unRegisterCallback(callbackId);
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
await qs.sendRequest(messages.endLog, {
db: dataset,
logPath: this.queryEvalInfo.evalLogPath,
});
if (await this.queryEvalInfo.hasEvalLog()) {
await this.queryEvalInfo.addQueryLogs(queryInfo, qs.cliServer, qs.logger);
} else {
void showAndLogWarningMessage(`Failed to write structured evaluator log to ${this.queryEvalInfo.evalLogPath}.`);
}
}
}
return result || {
evaluationTime: 0,
message: 'No result from server',
queryId: -1,
runId: callbackId,
resultType: messages.QueryResultType.OTHER_ERROR
};
}
async compile(
qs: qsClient.QueryServerClient,
program: messages.QlProgram,
progress: ProgressCallback,
token: CancellationToken,
): Promise<messages.CompilationMessage[]> {
let compiled: messages.CheckQueryResult | undefined;
try {
const target = this.quickEvalPosition ? {
quickEval: { quickEvalPos: this.quickEvalPosition }
} : { query: {} };
const params: messages.CompileQueryParams = {
compilationOptions: {
computeNoLocationUrls: true,
failOnWarnings: false,
fastCompilation: false,
includeDilInQlo: true,
localChecking: false,
noComputeGetUrl: false,
noComputeToString: false,
computeDefaultStrings: true,
emitDebugInfo: true
},
extraOptions: {
timeoutSecs: qs.config.timeoutSecs
},
queryToCheck: program,
resultPath: this.compiledQueryPath,
target,
};
compiled = await qs.sendRequest(messages.compileQuery, params, token, progress);
} finally {
void qs.logger.log(' - - - COMPILATION DONE - - - ', { additionalLogLocation: this.queryEvalInfo.logPath });
}
return (compiled?.messages || []).filter(msg => msg.severity === messages.Severity.ERROR);
}
}
export async function clearCacheInDatabase(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<messages.ClearCacheResult> {
if (dbItem.contents === undefined) {
throw new Error('Can\'t clear the cache in an invalid database.');
}
const db: messages.Dataset = {
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default',
};
const params: messages.ClearCacheParams = {
dryRun: false,
db,
};
return qs.sendRequest(messages.clearCache, params, token, progress);
}
/**
* Compare the dbscheme implied by the query `query` and that of the current database.
* - If they are compatible, do nothing.
* - If they are incompatible but the database can be upgraded, suggest that upgrade.
* - If they are incompatible and the database cannot be upgraded, throw an error.
*/
async function checkDbschemeCompatibility(
cliServer: cli.CodeQLCliServer,
qs: qsClient.QueryServerClient,
query: QueryInProgress,
qlProgram: messages.QlProgram,
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
const searchPath = getOnDiskWorkspaceFolders();
if (dbItem.contents?.dbSchemeUri !== undefined) {
const { finalDbscheme } = await cliServer.resolveUpgrades(dbItem.contents.dbSchemeUri.fsPath, searchPath, false);
const hash = async function(filename: string): Promise<string> {
return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');
};
// At this point, we have learned about three dbschemes:
// the dbscheme of the actual database we're querying.
const dbschemeOfDb = await hash(dbItem.contents.dbSchemeUri.fsPath);
// the dbscheme of the query we're running, including the library we've resolved it to use.
const dbschemeOfLib = await hash(query.queryDbscheme);
// the database we're able to upgrade to
const upgradableTo = await hash(finalDbscheme);
if (upgradableTo != dbschemeOfLib) {
reportNoUpgradePath(qlProgram, query);
}
if (upgradableTo == dbschemeOfLib &&
dbschemeOfDb != dbschemeOfLib) {
// Try to upgrade the database
await upgradeDatabaseExplicit(
qs,
dbItem,
progress,
token
);
}
}
}
function reportNoUpgradePath(qlProgram: messages.QlProgram, query: QueryInProgress): void {
throw new Error(
`Query ${qlProgram.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`
);
}
/**
* Compile a non-destructive upgrade.
*/
async function compileNonDestructiveUpgrade(
qs: qsClient.QueryServerClient,
upgradeTemp: tmp.DirectoryResult,
query: QueryInProgress,
qlProgram: messages.QlProgram,
dbItem: DatabaseItem,
progress: ProgressCallback,
token: CancellationToken,
): Promise<string> {
if (!dbItem?.contents?.dbSchemeUri) {
throw new Error('Database is invalid, and cannot be upgraded.');
}
// When packaging is used, dependencies may exist outside of the workspace and they are always on the resolved search path.
// When packaging is not used, all dependencies are in the workspace.
const upgradesPath = (await qs.cliServer.cliConstraints.supportsPackaging())
? qlProgram.libraryPath
: getOnDiskWorkspaceFolders();
const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades(
dbItem.contents.dbSchemeUri.fsPath,
upgradesPath,
true,
query.queryDbscheme
);
if (!matchesTarget) {
reportNoUpgradePath(qlProgram, query);
}
const result = await compileDatabaseUpgradeSequence(qs, dbItem, scripts, upgradeTemp, progress, token);
if (result.compiledUpgrade === undefined) {
const error = result.error || '[no error message available]';
throw new Error(error);
}
// We can upgrade to the actual target
qlProgram.dbschemePath = query.queryDbscheme;
// We are new enough that we will always support single file upgrades.
return result.compiledUpgrade;
}
export async function compileAndRunQueryAgainstDatabase(
cliServer: cli.CodeQLCliServer,
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
initialInfo: InitialQueryInfo,
queryStorageDir: string,
progress: ProgressCallback,
token: CancellationToken,
templates?: Record<string, string>,
queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query.
): Promise<QueryWithResults> {
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
throw new Error(`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`);
}
// Get the workspace folder paths.
const diskWorkspaceFolders = getOnDiskWorkspaceFolders();
// Figure out the library path for the query.
const packConfig = await cliServer.resolveLibraryPath(diskWorkspaceFolders, initialInfo.queryPath);
if (!packConfig.dbscheme) {
throw new Error('Could not find a database scheme for this query. Please check that you have a valid qlpack.yml file for this query, which refers to a database scheme either in the `dbscheme` field or through one of its dependencies.');
}
// Check whether the query has an entirely different schema from the
// database. (Queries that merely need the database to be upgraded
// won't trigger this check)
// This test will produce confusing results if we ever change the name of the database schema files.
const querySchemaName = path.basename(packConfig.dbscheme);
const dbSchemaName = path.basename(dbItem.contents.dbSchemeUri.fsPath);
if (querySchemaName != dbSchemaName) {
void logger.log(`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`);
throw new Error(`The query ${path.basename(initialInfo.queryPath)} cannot be run against the selected database (${dbItem.name}): their target languages are different. Please select a different database and try again.`);
}
const qlProgram: messages.QlProgram = {
// The project of the current document determines which library path
// we use. The `libraryPath` field in this server message is relative
// to the workspace root, not to the project root.
libraryPath: packConfig.libraryPath,
// Since we are compiling and running a query against a database,
// we use the database's DB scheme here instead of the DB scheme
// from the current document's project.
dbschemePath: dbItem.contents.dbSchemeUri.fsPath,
queryPath: initialInfo.queryPath
};
// Read the query metadata if possible, to use in the UI.
const metadata = await tryGetQueryMetadata(cliServer, qlProgram.queryPath);
let availableMlModels: cli.MlModelInfo[] = [];
if (!await cliServer.cliConstraints.supportsResolveMlModels()) {
void logger.log('Resolving ML models is unsupported by this version of the CLI. Running the query without any ML models.');
} else {
try {
availableMlModels = (await cliServer.resolveMlModels(diskWorkspaceFolders, initialInfo.queryPath)).models;
if (availableMlModels.length) {
void logger.log(`Found available ML models at the following paths: ${availableMlModels.map(x => `'${x.path}'`).join(', ')}.`);
} else {
void logger.log('Did not find any available ML models.');
}
} catch (e) {
const message = `Couldn't resolve available ML models for ${qlProgram.queryPath}. Running the ` +
`query without any ML models: ${e}.`;
void showAndLogErrorMessage(message);
}
}
const hasMetadataFile = (await dbItem.hasMetadataFile());
const query = new QueryInProgress(
path.join(queryStorageDir, initialInfo.id),
dbItem.databaseUri.fsPath,
hasMetadataFile,
packConfig.dbscheme,
initialInfo.quickEvalPosition,
metadata,
templates
);
await query.queryEvalInfo.createTimestampFile();
let upgradeDir: tmp.DirectoryResult | undefined;
try {
let upgradeQlo;
if (await cliServer.cliConstraints.supportsNonDestructiveUpgrades()) {
upgradeDir = await tmp.dir({ dir: upgradesTmpDir, unsafeCleanup: true });
upgradeQlo = await compileNonDestructiveUpgrade(qs, upgradeDir, query, qlProgram, dbItem, progress, token);
} else {
await checkDbschemeCompatibility(cliServer, qs, query, qlProgram, dbItem, progress, token);
}
let errors;
try {
errors = await query.compile(qs, qlProgram, progress, token);
} catch (e) {
if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
return createSyntheticResult(query, 'Query cancelled');
} else {
throw e;
}
}
if (errors.length === 0) {
const result = await query.run(qs, upgradeQlo, availableMlModels, dbItem, progress, token, queryInfo);
if (result.resultType !== messages.QueryResultType.SUCCESS) {
const message = result.message || 'Failed to run query';
void logger.log(message);
void showAndLogErrorMessage(message);
}
const message = formatLegacyMessage(result);
return {
query: query.queryEvalInfo,
message,
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(
`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
{ additionalLogLocation: query.queryEvalInfo.logPath }
);
const formattedMessages: string[] = [];
for (const error of errors) {
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 });
}
if (initialInfo.isQuickEval && formattedMessages.length <= 2) {
// If there are more than 2 error messages, they will not be displayed well in a popup
// and will be trimmed by the function displaying the error popup. Accordingly, we only
// try to show the errors if there are 2 or less, otherwise we direct the user to the log.
void showAndLogErrorMessage('Quick evaluation compilation failed: ' + formattedMessages.join('\n'));
} else {
void showAndLogErrorMessage((initialInfo.isQuickEval ? 'Quick evaluation' : 'Query') + compilationFailedErrorTail);
}
return createSyntheticResult(query, 'Query had compilation errors');
}
} finally {
try {
await upgradeDir?.cleanup();
} catch (e) {
void qs.logger.log(
`Could not clean up the upgrades dir. Reason: ${getErrorMessage(e)}`,
{ additionalLogLocation: query.queryEvalInfo.logPath }
);
}
}
}
const compilationFailedErrorTail = ' compilation failed. Please make sure there are no errors in the query, the database is up to date,' +
' and the query and database use the same target language. For more details on the error, go to View > Output,' +
' and choose CodeQL Query Server from the dropdown.';
export function formatLegacyMessage(result: messages.EvaluationResult) {
switch (result.resultType) {
case messages.QueryResultType.CANCELLATION:
return `cancelled after ${Math.round(result.evaluationTime / 1000)} seconds`;
case messages.QueryResultType.OOM:
return 'out of memory';
case messages.QueryResultType.SUCCESS:
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
case messages.QueryResultType.TIMEOUT:
return `timed out after ${Math.round(result.evaluationTime / 1000)} seconds`;
case messages.QueryResultType.OTHER_ERROR:
default:
return result.message ? `failed: ${result.message}` : 'failed';
}
}
/**
* Create a synthetic result for a query that failed to compile.
*/
function createSyntheticResult(
query: QueryInProgress,
message: string,
): QueryWithResults {
return {
query: query.queryEvalInfo,
message,
result: {
evaluationTime: 0,
queryId: 0,
resultType: messages.QueryResultType.OTHER_ERROR,
message,
runId: 0,
},
successful: false,
dispose: () => { /**/ },
};
}
function createSimpleTemplates(templates: Record<string, string> | undefined): messages.TemplateDefinitions | undefined {
if (!templates) {
return undefined;
}
const result: messages.TemplateDefinitions = {};
for (const key of Object.keys(templates)) {
result[key] = {
values: {
tuples: [[{ stringValue: templates[key] }]]
}
};
}
return result;
}

View File

@@ -0,0 +1,239 @@
import * as vscode from 'vscode';
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage, tmpDir } from '../helpers';
import { ProgressCallback, UserCancellationException } from '../commandRunner';
import { logger } from '../logging';
import * as messages from '../pure/legacy-messages';
import * as qsClient from './queryserver-client';
import * as tmp from 'tmp-promise';
import * as path from 'path';
import { DatabaseItem } from '../databases';
/**
* Maximum number of lines to include from database upgrade message,
* to work around the fact that we can't guarantee a scrollable text
* box for it when displaying in dialog boxes.
*/
const MAX_UPGRADE_MESSAGE_LINES = 10;
/**
* Compile a database upgrade sequence.
* Callers must check that this is valid with the current queryserver first.
*/
export async function compileDatabaseUpgradeSequence(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
resolvedSequence: string[],
currentUpgradeTmp: tmp.DirectoryResult,
progress: ProgressCallback,
token: vscode.CancellationToken
): Promise<messages.CompileUpgradeSequenceResult> {
if (dbItem.contents === undefined || dbItem.contents.dbSchemeUri === undefined) {
throw new Error('Database is invalid, and cannot be upgraded.');
}
if (!await qs.cliServer.cliConstraints.supportsNonDestructiveUpgrades()) {
throw new Error('The version of codeql is too old to run non-destructive upgrades.');
}
// If possible just compile the upgrade sequence
return await qs.sendRequest(messages.compileUpgradeSequence, {
upgradeTempDir: currentUpgradeTmp.path,
upgradePaths: resolvedSequence
}, token, progress);
}
async function compileDatabaseUpgrade(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
targetDbScheme: string,
resolvedSequence: string[],
currentUpgradeTmp: tmp.DirectoryResult,
progress: ProgressCallback,
token: vscode.CancellationToken
): Promise<messages.CompileUpgradeResult> {
if (!dbItem.contents?.dbSchemeUri) {
throw new Error('Database is invalid, and cannot be upgraded.');
}
// We have the upgrades we want but compileUpgrade
// requires searching for them. So we use the parent directories of the upgrades
// as the upgrade path.
const parentDirs = resolvedSequence.map(dir => path.dirname(dir));
const uniqueParentDirs = new Set(parentDirs);
progress({
step: 1,
maxStep: 3,
message: 'Checking for database upgrades'
});
return qs.sendRequest(messages.compileUpgrade, {
upgrade: {
fromDbscheme: dbItem.contents.dbSchemeUri.fsPath,
toDbscheme: targetDbScheme,
additionalUpgrades: Array.from(uniqueParentDirs)
},
upgradeTempDir: currentUpgradeTmp.path,
singleFileUpgrades: true,
}, token, progress);
}
/**
* Checks whether the user wants to proceed with the upgrade.
* Reports errors to both the user and the console.
*/
async function checkAndConfirmDatabaseUpgrade(
compiled: messages.CompiledUpgrades,
db: DatabaseItem,
quiet: boolean
): Promise<void> {
let descriptionMessage = '';
const descriptions = getUpgradeDescriptions(compiled);
for (const script of descriptions) {
descriptionMessage += `Would perform upgrade: ${script.description}\n`;
descriptionMessage += `\t-> Compatibility: ${script.compatibility}\n`;
}
void logger.log(descriptionMessage);
// If the quiet flag is set, do the upgrade without a popup.
if (quiet) {
return;
}
// Ask the user to confirm the upgrade.
const showLogItem: vscode.MessageItem = { title: 'No, Show Changes', isCloseAffordance: true };
const yesItem = { title: 'Yes', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true };
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
let messageLines = descriptionMessage.split('\n');
if (messageLines.length > MAX_UPGRADE_MESSAGE_LINES) {
messageLines = messageLines.slice(0, MAX_UPGRADE_MESSAGE_LINES);
messageLines.push('The list of upgrades was truncated, click "No, Show Changes" to see the full list.');
dialogOptions.push(showLogItem);
}
const message = `Should the database ${db.databaseUri.fsPath} be upgraded?\n\n${messageLines.join('\n')}`;
const chosenItem = await vscode.window.showInformationMessage(message, { modal: true }, ...dialogOptions);
if (chosenItem === showLogItem) {
logger.outputChannel.show();
}
if (chosenItem !== yesItem) {
throw new UserCancellationException('User cancelled the database upgrade.');
}
}
/**
* Get the descriptions from a compiled upgrade
*/
function getUpgradeDescriptions(compiled: messages.CompiledUpgrades): messages.UpgradeDescription[] {
// We use the presence of compiledUpgradeFile to check
// if it is multifile or not. We need to explicitly check undefined
// as the types claim the empty string is a valid value
if (compiled.compiledUpgradeFile === undefined) {
return compiled.scripts.map(script => script.description);
} else {
return compiled.descriptions;
}
}
/**
* Command handler for 'Upgrade Database'.
* Attempts to upgrade the given database to the given target DB scheme, using the given directory of upgrades.
* First performs a dry-run and prompts the user to confirm the upgrade.
* Reports errors during compilation and evaluation of upgrades to the user.
*/
export async function upgradeDatabaseExplicit(
qs: qsClient.QueryServerClient,
dbItem: DatabaseItem,
progress: ProgressCallback,
token: vscode.CancellationToken,
): Promise<messages.RunUpgradeResult | undefined> {
const searchPath: string[] = getOnDiskWorkspaceFolders();
if (!dbItem?.contents?.dbSchemeUri) {
throw new Error('Database is invalid, and cannot be upgraded.');
}
const upgradeInfo = await qs.cliServer.resolveUpgrades(
dbItem.contents.dbSchemeUri.fsPath,
searchPath,
false
);
const { scripts, finalDbscheme } = upgradeInfo;
if (finalDbscheme === undefined) {
throw new Error('Could not determine target dbscheme to upgrade to.');
}
const currentUpgradeTmp = await tmp.dir({ dir: tmpDir.name, prefix: 'upgrade_', keep: false, unsafeCleanup: true });
try {
let compileUpgradeResult: messages.CompileUpgradeResult;
try {
compileUpgradeResult = await compileDatabaseUpgrade(qs, dbItem, finalDbscheme, scripts, currentUpgradeTmp, progress, token);
}
catch (e) {
void showAndLogErrorMessage(`Compilation of database upgrades failed: ${e}`);
return;
}
finally {
void qs.logger.log('Done compiling database upgrade.');
}
if (!compileUpgradeResult.compiledUpgrades) {
const error = compileUpgradeResult.error || '[no error message available]';
void showAndLogErrorMessage(`Compilation of database upgrades failed: ${error}`);
return;
}
await checkAndConfirmDatabaseUpgrade(compileUpgradeResult.compiledUpgrades, dbItem, qs.cliServer.quiet);
try {
void qs.logger.log('Running the following database upgrade:');
getUpgradeDescriptions(compileUpgradeResult.compiledUpgrades).map(s => s.description).join('\n');
const result = await runDatabaseUpgrade(qs, dbItem, compileUpgradeResult.compiledUpgrades, progress, token);
// TODO Can remove the next lines when https://github.com/github/codeql-team/issues/1241 is fixed
// restart the query server to avoid a bug in the CLI where the upgrade is applied, but the old dbscheme
// is still cached in memory.
await qs.restartQueryServer(progress, token);
return result;
}
catch (e) {
void showAndLogErrorMessage(`Database upgrade failed: ${e}`);
return;
} finally {
void qs.logger.log('Done running database upgrade.');
}
} finally {
await currentUpgradeTmp.cleanup();
}
}
async function runDatabaseUpgrade(
qs: qsClient.QueryServerClient,
db: DatabaseItem,
upgrades: messages.CompiledUpgrades,
progress: ProgressCallback,
token: vscode.CancellationToken,
): Promise<messages.RunUpgradeResult> {
if (db.contents === undefined || db.contents.datasetUri === undefined) {
throw new Error('Can\'t upgrade an invalid database.');
}
const database: messages.Dataset = {
dbDir: db.contents.datasetUri.fsPath,
workingSet: 'default'
};
const params: messages.RunUpgradeParams = {
db: database,
timeoutSecs: qs.config.timeoutSecs,
toRun: upgrades
};
return qs.sendRequest(messages.runUpgrade, params, token, progress);
}

View File

@@ -0,0 +1,460 @@
import * as I from 'immutable';
import { EvaluationLogProblemReporter, EvaluationLogScanner, EvaluationLogScannerProvider } from './log-scanner';
import { InLayer, ComputeRecursive, SummaryEvent, PipelineRun, ComputeSimple } from './log-summary';
const DEFAULT_WARNING_THRESHOLD = 50;
/**
* Like `max`, but returns 0 if no meaningful maximum can be computed.
*/
function safeMax(it?: Iterable<number>) {
const m = Math.max(...(it || []));
return Number.isFinite(m) ? m : 0;
}
/**
* Compute a key for the maps that that is sent to report generation.
* Should only be used on events that are known to define queryCausingWork.
*/
function makeKey(
queryCausingWork: string | undefined,
predicate: string,
suffix = ''
): string {
if (queryCausingWork === undefined) {
throw new Error(
'queryCausingWork was not defined on an event we expected it to be defined for!'
);
}
return `${queryCausingWork}:${predicate}${suffix ? ' ' + suffix : ''}`;
}
const DEPENDENT_PREDICATES_REGEXP = (() => {
const regexps = [
// SCAN id
String.raw`SCAN\s+([0-9a-zA-Z:#_]+)\s`,
// JOIN id WITH id
String.raw`JOIN\s+([0-9a-zA-Z:#_]+)\s+WITH\s+([0-9a-zA-Z:#_]+)\s`,
// AGGREGATE id, id
String.raw`AGGREGATE\s+([0-9a-zA-Z:#_]+)\s*,\s+([0-9a-zA-Z:#_]+)`,
// id AND NOT id
String.raw`([0-9a-zA-Z:#_]+)\s+AND\s+NOT\s+([0-9a-zA-Z:#_]+)`,
// INVOKE HIGHER-ORDER RELATION rel ON <id, ..., id>
String.raw`INVOKE\s+HIGHER-ORDER\s+RELATION\s[^\s]+\sON\s+<([0-9a-zA-Z:#_<>]+)((?:,[0-9a-zA-Z:#_<>]+)*)>`,
// SELECT id
String.raw`SELECT\s+([0-9a-zA-Z:#_]+)`
];
return new RegExp(
`${String.raw`\{[0-9]+\}\s+[0-9a-zA-Z]+\s=\s(?:` + regexps.join('|')})`
);
})();
function getDependentPredicates(operations: string[]): I.List<string> {
return I.List(operations).flatMap(operation => {
const matches = DEPENDENT_PREDICATES_REGEXP.exec(operation.trim());
if (matches !== null) {
return I.List(matches)
.rest() // Skip the first group as it's just the entire string
.filter(x => !!x && !x.match('r[0-9]+|PRIMITIVE')) // Only keep the references to predicates.
.flatMap(x => x.split(',')) // Group 2 in the INVOKE HIGHER_ORDER RELATION case is a comma-separated list of identifiers.
.filter(x => !!x); // Remove empty strings
} else {
return I.List();
}
});
}
function getMainHash(event: InLayer | ComputeRecursive): string {
switch (event.evaluationStrategy) {
case 'IN_LAYER':
return event.mainHash;
case 'COMPUTE_RECURSIVE':
return event.raHash;
}
}
/**
* Sum arrays a and b element-wise. The shorter array is padded with 0s if the arrays are not the same length.
*/
function pointwiseSum(a: Int32Array, b: Int32Array, problemReporter: EvaluationLogProblemReporter): Int32Array {
function reportIfInconsistent(ai: number, bi: number) {
if (ai === -1 && bi !== -1) {
problemReporter.log(
`Operation was not evaluated in the first pipeline, but it was evaluated in the accumulated pipeline (with tuple count ${bi}).`
);
}
if (ai !== -1 && bi === -1) {
problemReporter.log(
`Operation was evaluated in the first pipeline (with tuple count ${ai}), but it was not evaluated in the accumulated pipeline.`
);
}
}
const length = Math.max(a.length, b.length);
const result = new Int32Array(length);
for (let i = 0; i < length; i++) {
const ai = a[i] || 0;
const bi = b[i] || 0;
// -1 is used to represent the absence of a tuple count for a line in the pretty-printed RA (e.g. an empty line), so we ignore those.
if (i < a.length && i < b.length && (ai === -1 || bi === -1)) {
result[i] = -1;
reportIfInconsistent(ai, bi);
} else {
result[i] = ai + bi;
}
}
return result;
}
function pushValue<K, V>(m: Map<K, V[]>, k: K, v: V) {
if (!m.has(k)) {
m.set(k, []);
}
m.get(k)!.push(v);
return m;
}
function computeJoinOrderBadness(
maxTupleCount: number,
maxDependentPredicateSize: number,
resultSize: number
): number {
return maxTupleCount / Math.max(maxDependentPredicateSize, resultSize);
}
/**
* A bucket contains the pointwise sum of the tuple counts, result sizes and dependent predicate sizes
* For each (predicate, order) in an SCC, we will compute a bucket.
*/
interface Bucket {
tupleCounts: Int32Array;
resultSize: number;
dependentPredicateSizes: I.Map<string, number>;
}
class JoinOrderScanner implements EvaluationLogScanner {
// Map a predicate hash to its result size
private readonly predicateSizes = new Map<string, number>();
private readonly layerEvents = new Map<string, (ComputeRecursive | InLayer)[]>();
// Map a key of the form 'query-with-demand : predicate name' to its badness input.
private readonly maxTupleCountMap = new Map<string, number[]>();
private readonly resultSizeMap = new Map<string, number[]>();
private readonly maxDependentPredicateSizeMap = new Map<string, number[]>();
private readonly joinOrderMetricMap = new Map<string, number>();
constructor(
private readonly problemReporter: EvaluationLogProblemReporter,
private readonly warningThreshold: number) {
}
public onEvent(event: SummaryEvent): void {
if (
event.completionType !== undefined &&
event.completionType !== 'SUCCESS'
) {
return; // Skip any evaluation that wasn't successful
}
this.recordPredicateSizes(event);
this.computeBadnessMetric(event);
}
public onDone(): void {
void this;
}
private recordPredicateSizes(event: SummaryEvent): void {
switch (event.evaluationStrategy) {
case 'EXTENSIONAL':
case 'COMPUTED_EXTENSIONAL':
case 'COMPUTE_SIMPLE':
case 'CACHACA':
case 'CACHE_HIT': {
this.predicateSizes.set(event.raHash, event.resultSize);
break;
}
case 'SENTINEL_EMPTY': {
this.predicateSizes.set(event.raHash, 0);
break;
}
case 'COMPUTE_RECURSIVE':
case 'IN_LAYER': {
this.predicateSizes.set(event.raHash, event.resultSize);
// layerEvents are indexed by the mainHash.
const hash = getMainHash(event);
if (!this.layerEvents.has(hash)) {
this.layerEvents.set(hash, []);
}
this.layerEvents.get(hash)!.push(event);
break;
}
}
}
private reportProblemIfNecessary(event: SummaryEvent, iteration: number, metric: number): void {
if (metric >= this.warningThreshold) {
this.problemReporter.reportProblem(event.predicateName, event.raHash, iteration,
`Relation '${event.predicateName}' has an inefficient join order. Its join order metric is ${metric.toFixed(2)}, which is larger than the threshold of ${this.warningThreshold.toFixed(2)}.`);
}
}
private computeBadnessMetric(event: SummaryEvent): void {
if (
event.completionType !== undefined &&
event.completionType !== 'SUCCESS'
) {
return; // Skip any evaluation that wasn't successful
}
switch (event.evaluationStrategy) {
case 'COMPUTE_SIMPLE': {
if (!event.pipelineRuns) {
// skip if the optional pipelineRuns field is not present.
break;
}
// Compute the badness metric for a non-recursive predicate. The metric in this case is defined as:
// badness = (max tuple count in the pipeline) / (largest predicate this pipeline depends on)
const key = makeKey(event.queryCausingWork, event.predicateName);
const resultSize = event.resultSize;
// There is only one entry in `pipelineRuns` if it's a non-recursive predicate.
const { maxTupleCount, maxDependentPredicateSize } =
this.badnessInputsForNonRecursiveDelta(event.pipelineRuns[0], event);
if (maxDependentPredicateSize > 0) {
pushValue(this.maxTupleCountMap, key, maxTupleCount);
pushValue(this.resultSizeMap, key, resultSize);
pushValue(
this.maxDependentPredicateSizeMap,
key,
maxDependentPredicateSize
);
const metric = computeJoinOrderBadness(maxTupleCount, maxDependentPredicateSize, resultSize!);
this.joinOrderMetricMap.set(key, metric);
this.reportProblemIfNecessary(event, 0, metric);
}
break;
}
case 'COMPUTE_RECURSIVE': {
// Compute the badness metric for a recursive predicate for each ordering.
const sccMetricInput = this.badnessInputsForRecursiveDelta(event);
// Loop through each predicate in the SCC
sccMetricInput.forEach((buckets, predicate) => {
// Loop through each ordering of the predicate
buckets.forEach((bucket, raReference) => {
// Format the key as demanding-query:name (ordering)
const key = makeKey(
event.queryCausingWork,
predicate,
`(${raReference})`
);
const maxTupleCount = Math.max(...bucket.tupleCounts);
const resultSize = bucket.resultSize;
const maxDependentPredicateSize = Math.max(
...bucket.dependentPredicateSizes.values()
);
if (maxDependentPredicateSize > 0) {
pushValue(this.maxTupleCountMap, key, maxTupleCount);
pushValue(this.resultSizeMap, key, resultSize);
pushValue(
this.maxDependentPredicateSizeMap,
key,
maxDependentPredicateSize
);
const metric = computeJoinOrderBadness(maxTupleCount, maxDependentPredicateSize, resultSize);
const oldMetric = this.joinOrderMetricMap.get(key);
if ((oldMetric === undefined) || (metric > oldMetric)) {
this.joinOrderMetricMap.set(key, metric);
}
}
});
});
break;
}
}
}
/**
* Iterate through an SCC with main node `event`.
*/
private iterateSCC(
event: ComputeRecursive,
func: (
inLayerEvent: ComputeRecursive | InLayer,
run: PipelineRun,
iteration: number
) => void
): void {
const sccEvents = this.layerEvents.get(event.raHash)!;
const nextPipeline: number[] = new Array(sccEvents.length).fill(0);
const maxIteration = Math.max(
...sccEvents.map(e => e.predicateIterationMillis.length)
);
for (let iteration = 0; iteration < maxIteration; ++iteration) {
// Loop through each predicate in this iteration
for (let predicate = 0; predicate < sccEvents.length; ++predicate) {
const inLayerEvent = sccEvents[predicate];
const iterationTime =
inLayerEvent.predicateIterationMillis.length <= iteration
? -1
: inLayerEvent.predicateIterationMillis[iteration];
if (iterationTime != -1) {
const run: PipelineRun =
inLayerEvent.pipelineRuns[nextPipeline[predicate]++];
func(inLayerEvent, run, iteration);
}
}
}
}
/**
* Compute the maximum tuple count and maximum dependent predicate size for a non-recursive pipeline
*/
private badnessInputsForNonRecursiveDelta(
pipelineRun: PipelineRun,
event: ComputeSimple
): { maxTupleCount: number; maxDependentPredicateSize: number } {
const dependentPredicateSizes = Object.values(event.dependencies).map(hash =>
this.predicateSizes.get(hash) ?? 0 // Should always be present, but zero is a safe default.
);
const maxDependentPredicateSize = safeMax(dependentPredicateSizes);
return {
maxTupleCount: safeMax(pipelineRun.counts),
maxDependentPredicateSize: maxDependentPredicateSize
};
}
private prevDeltaSizes(event: ComputeRecursive, predicate: string, i: number) {
// If an iteration isn't present in the map it means it was skipped because the optimizer
// inferred that it was empty. So its size is 0.
return this.curDeltaSizes(event, predicate, i - 1);
}
private curDeltaSizes(event: ComputeRecursive, predicate: string, i: number) {
// If an iteration isn't present in the map it means it was skipped because the optimizer
// inferred that it was empty. So its size is 0.
return (
this.layerEvents.get(event.raHash)?.find(x => x.predicateName === predicate)?.deltaSizes[i] ?? 0
);
}
/**
* Compute the metric dependent predicate sizes and the result size for a predicate in an SCC.
*/
private badnessInputsForLayer(
event: ComputeRecursive,
inLayerEvent: InLayer | ComputeRecursive,
raReference: string,
iteration: number
) {
const dependentPredicates = getDependentPredicates(
inLayerEvent.ra[raReference]
);
let dependentPredicateSizes: I.Map<string, number>;
// We treat the base case as a non-recursive pipeline. In that case, the dependent predicates are
// the dependencies of the base case and the cur_deltas.
if (raReference === 'base') {
dependentPredicateSizes = I.Map(
dependentPredicates.map((pred): [string, number] => {
// A base case cannot contain a `prev_delta`, but it can contain a `cur_delta`.
let size = 0;
if (pred.endsWith('#cur_delta')) {
size = this.curDeltaSizes(
event,
pred.slice(0, -'#cur_delta'.length),
iteration
);
} else {
const hash = event.dependencies[pred];
size = this.predicateSizes.get(hash)!;
}
return [pred, size];
})
);
} else {
// It's a non-base case in a recursive pipeline. In that case, the dependent predicates are
// only the prev_deltas.
dependentPredicateSizes = I.Map(
dependentPredicates
.flatMap(pred => {
// If it's actually a prev_delta
if (pred.endsWith('#prev_delta')) {
// Return the predicate without the #prev_delta suffix.
return [pred.slice(0, -'#prev_delta'.length)];
} else {
// Not a recursive delta. Skip it.
return [];
}
})
.map((prev): [string, number] => {
const size = this.prevDeltaSizes(event, prev, iteration);
return [prev, size];
})
);
}
const deltaSize = inLayerEvent.deltaSizes[iteration];
return { dependentPredicateSizes, deltaSize };
}
/**
* Compute the metric input for all the events in a SCC that starts with main node `event`
*/
private badnessInputsForRecursiveDelta(event: ComputeRecursive): Map<string, Map<string, Bucket>> {
// nameToOrderToBucket : predicate name -> ordering (i.e., standard, order_500000, etc.) -> bucket
const nameToOrderToBucket = new Map<string, Map<string, Bucket>>();
// Iterate through the SCC and compute the metric inputs
this.iterateSCC(event, (inLayerEvent, run, iteration) => {
const raReference = run.raReference;
const predicateName = inLayerEvent.predicateName;
if (!nameToOrderToBucket.has(predicateName)) {
nameToOrderToBucket.set(predicateName, new Map());
}
const orderTobucket = nameToOrderToBucket.get(predicateName)!;
if (!orderTobucket.has(raReference)) {
orderTobucket.set(raReference, {
tupleCounts: new Int32Array(0),
resultSize: 0,
dependentPredicateSizes: I.Map()
});
}
const { dependentPredicateSizes, deltaSize } = this.badnessInputsForLayer(
event,
inLayerEvent,
raReference,
iteration
);
const bucket = orderTobucket.get(raReference)!;
// Pointwise sum the tuple counts
const newTupleCounts = pointwiseSum(
bucket.tupleCounts,
new Int32Array(run.counts),
this.problemReporter
);
const resultSize = bucket.resultSize + deltaSize;
// Pointwise sum the deltas.
const newDependentPredicateSizes = bucket.dependentPredicateSizes.mergeWith(
(oldSize, newSize) => oldSize + newSize,
dependentPredicateSizes
);
orderTobucket.set(raReference, {
tupleCounts: newTupleCounts,
resultSize: resultSize,
dependentPredicateSizes: newDependentPredicateSizes
});
});
return nameToOrderToBucket;
}
}
export class JoinOrderScannerProvider implements EvaluationLogScannerProvider {
public createScanner(problemReporter: EvaluationLogProblemReporter): EvaluationLogScanner {
return new JoinOrderScanner(problemReporter, DEFAULT_WARNING_THRESHOLD);
}
}

View File

@@ -0,0 +1,23 @@
import * as fs from 'fs-extra';
/**
* Read a file consisting of multiple JSON objects. Each object is separated from the previous one
* by a double newline sequence. This is basically a more human-readable form of JSONL.
*
* The current implementation reads the entire text of the document into memory, but in the future
* it will stream the document to improve the performance with large documents.
*
* @param path The path to the file.
* @param handler Callback to be invoked for each top-level JSON object in order.
*/
export async function readJsonlFile(path: string, handler: (value: any) => Promise<void>): Promise<void> {
const logSummary = await fs.readFile(path, 'utf-8');
// Remove newline delimiters because summary is in .jsonl format.
const jsonSummaryObjects: string[] = logSummary.split(/\r?\n\r?\n/g);
for (const obj of jsonSummaryObjects) {
const jsonObj = JSON.parse(obj);
await handler(jsonObj);
}
}

View File

@@ -0,0 +1,109 @@
import { Diagnostic, DiagnosticSeverity, languages, Range, Uri } from 'vscode';
import { DisposableObject } from '../pure/disposable-object';
import { QueryHistoryManager } from '../query-history';
import { QueryHistoryInfo } from '../query-history-info';
import { EvaluationLogProblemReporter, EvaluationLogScannerSet } from './log-scanner';
import { PipelineInfo, SummarySymbols } from './summary-parser';
import * as fs from 'fs-extra';
import { logger } from '../logging';
/**
* Compute the key used to find a predicate in the summary symbols.
* @param name The name of the predicate.
* @param raHash The RA hash of the predicate.
* @returns The key of the predicate, consisting of `name@shortHash`, where `shortHash` is the first
* eight characters of `raHash`.
*/
function predicateSymbolKey(name: string, raHash: string): string {
return `${name}@${raHash.substring(0, 8)}`;
}
/**
* Implementation of `EvaluationLogProblemReporter` that generates `Diagnostic` objects to display
* in the VS Code "Problems" view.
*/
class ProblemReporter implements EvaluationLogProblemReporter {
public readonly diagnostics: Diagnostic[] = [];
constructor(private readonly symbols: SummarySymbols | undefined) {
}
public reportProblem(predicateName: string, raHash: string, iteration: number, message: string): void {
const nameWithHash = predicateSymbolKey(predicateName, raHash);
const predicateSymbol = this.symbols?.predicates[nameWithHash];
let predicateInfo: PipelineInfo | undefined = undefined;
if (predicateSymbol !== undefined) {
predicateInfo = predicateSymbol.iterations[iteration];
}
if (predicateInfo !== undefined) {
const range = new Range(predicateInfo.raStartLine, 0, predicateInfo.raEndLine + 1, 0);
this.diagnostics.push(new Diagnostic(range, message, DiagnosticSeverity.Error));
}
}
public log(message: string): void {
void logger.log(message);
}
}
export class LogScannerService extends DisposableObject {
public readonly scanners = new EvaluationLogScannerSet();
private readonly diagnosticCollection = this.push(languages.createDiagnosticCollection('ql-eval-log'));
private currentItem: QueryHistoryInfo | undefined = undefined;
constructor(qhm: QueryHistoryManager) {
super();
this.push(qhm.onDidChangeCurrentQueryItem(async (item) => {
if (item !== this.currentItem) {
this.currentItem = item;
await this.scanEvalLog(item);
}
}));
this.push(qhm.onDidCompleteQuery(async (item) => {
if (item === this.currentItem) {
await this.scanEvalLog(item);
}
}));
}
/**
* Scan the evaluation log for a query, and report any diagnostics.
*
* @param query The query whose log is to be scanned.
*/
public async scanEvalLog(
query: QueryHistoryInfo | undefined
): Promise<void> {
this.diagnosticCollection.clear();
if ((query?.t !== 'local')
|| (query.evalLogSummaryLocation === undefined)
|| (query.jsonEvalLogSummaryLocation === undefined)) {
return;
}
const diagnostics = await this.scanLog(query.jsonEvalLogSummaryLocation, query.evalLogSummarySymbolsLocation);
const uri = Uri.file(query.evalLogSummaryLocation);
this.diagnosticCollection.set(uri, diagnostics);
}
/**
* Scan the evaluator summary log for problems, using the scanners for all registered providers.
* @param jsonSummaryLocation The file path of the JSON summary log.
* @param symbolsLocation The file path of the symbols file for the human-readable log summary.
* @returns An array of `Diagnostic`s representing the problems found by scanners.
*/
private async scanLog(jsonSummaryLocation: string, symbolsLocation: string | undefined): Promise<Diagnostic[]> {
let symbols: SummarySymbols | undefined = undefined;
if (symbolsLocation !== undefined) {
symbols = JSON.parse(await fs.readFile(symbolsLocation, { encoding: 'utf-8' }));
}
const problemReporter = new ProblemReporter(symbols);
await this.scanners.scanLog(jsonSummaryLocation, problemReporter);
return problemReporter.diagnostics;
}
}

View File

@@ -0,0 +1,103 @@
import { SummaryEvent } from './log-summary';
import { readJsonlFile } from './jsonl-reader';
/**
* Callback interface used to report diagnostics from a log scanner.
*/
export interface EvaluationLogProblemReporter {
/**
* Report a potential problem detected in the evaluation log.
*
* @param predicateName The mangled name of the predicate with the problem.
* @param raHash The RA hash of the predicate with the problem.
* @param iteration The iteration number with the problem. For a non-recursive predicate, this
* must be zero.
* @param message The problem message.
*/
reportProblem(predicateName: string, raHash: string, iteration: number, message: string): void;
/**
* Log a message about a problem in the implementation of the scanner. These will typically be
* displayed separate from any problems reported via `reportProblem()`.
*/
log(message: string): void;
}
/**
* Interface implemented by a log scanner. Instances are created via
* `EvaluationLogScannerProvider.createScanner()`.
*/
export interface EvaluationLogScanner {
/**
* Called for each event in the log summary, in order. The implementation can report problems via
* the `EvaluationLogProblemReporter` interface that was supplied to `createScanner()`.
* @param event The log summary event.
*/
onEvent(event: SummaryEvent): void;
/**
* Called after all events in the log summary have been processed. The implementation can report
* problems via the `EvaluationLogProblemReporter` interface that was supplied to
* `createScanner()`.
*/
onDone(): void;
}
/**
* A factory for log scanners. When a log is to be scanned, all registered
* `EvaluationLogScannerProviders` will be asked to create a new instance of `EvaluationLogScanner`
* to do the scanning.
*/
export interface EvaluationLogScannerProvider {
/**
* Create a new instance of `EvaluationLogScanner` to scan a single summary log.
* @param problemReporter Callback interface for reporting any problems discovered.
*/
createScanner(problemReporter: EvaluationLogProblemReporter): EvaluationLogScanner;
}
/**
* Same as VSCode's `Disposable`, but avoids a dependency on VS Code.
*/
export interface Disposable {
dispose(): void;
}
export class EvaluationLogScannerSet {
private readonly scannerProviders = new Map<number, EvaluationLogScannerProvider>();
private nextScannerProviderId = 0;
/**
* Register a provider that can create instances of `EvaluationLogScanner` to scan evaluation logs
* for problems.
* @param provider The provider.
* @returns A `Disposable` that, when disposed, will unregister the provider.
*/
public registerLogScannerProvider(provider: EvaluationLogScannerProvider): Disposable {
const id = this.nextScannerProviderId;
this.nextScannerProviderId++;
this.scannerProviders.set(id, provider);
return {
dispose: () => {
this.scannerProviders.delete(id);
}
};
}
/**
* Scan the evaluator summary log for problems, using the scanners for all registered providers.
* @param jsonSummaryLocation The file path of the JSON summary log.
* @param problemReporter Callback interface for reporting any problems discovered.
*/
public async scanLog(jsonSummaryLocation: string, problemReporter: EvaluationLogProblemReporter): Promise<void> {
const scanners = [...this.scannerProviders.values()].map(p => p.createScanner(problemReporter));
await readJsonlFile(jsonSummaryLocation, async obj => {
scanners.forEach(scanner => {
scanner.onEvent(obj);
});
});
scanners.forEach(scanner => scanner.onDone());
}
}

View File

@@ -0,0 +1,93 @@
export interface PipelineRun {
raReference: string;
counts: number[];
duplicationPercentages: number[];
}
export interface Ra {
[key: string]: string[];
}
export type EvaluationStrategy =
'COMPUTE_SIMPLE' |
'COMPUTE_RECURSIVE' |
'IN_LAYER' |
'COMPUTED_EXTENSIONAL' |
'EXTENSIONAL' |
'SENTINEL_EMPTY' |
'CACHACA' |
'CACHE_HIT';
interface SummaryEventBase {
evaluationStrategy: EvaluationStrategy;
predicateName: string;
raHash: string;
appearsAs: { [key: string]: { [key: string]: number[] } };
completionType?: string;
}
interface ResultEventBase extends SummaryEventBase {
resultSize: number;
}
export interface ComputeSimple extends ResultEventBase {
evaluationStrategy: 'COMPUTE_SIMPLE';
ra: Ra;
pipelineRuns?: [PipelineRun];
queryCausingWork?: string;
dependencies: { [key: string]: string };
}
export interface ComputeRecursive extends ResultEventBase {
evaluationStrategy: 'COMPUTE_RECURSIVE';
deltaSizes: number[];
ra: Ra;
pipelineRuns: PipelineRun[];
queryCausingWork?: string;
dependencies: { [key: string]: string };
predicateIterationMillis: number[];
}
export interface InLayer extends ResultEventBase {
evaluationStrategy: 'IN_LAYER';
deltaSizes: number[];
ra: Ra;
pipelineRuns: PipelineRun[];
queryCausingWork?: string;
mainHash: string;
predicateIterationMillis: number[];
}
export interface ComputedExtensional extends ResultEventBase {
evaluationStrategy: 'COMPUTED_EXTENSIONAL';
queryCausingWork?: string;
}
export interface NonComputedExtensional extends ResultEventBase {
evaluationStrategy: 'EXTENSIONAL';
queryCausingWork?: string;
}
export interface SentinelEmpty extends SummaryEventBase {
evaluationStrategy: 'SENTINEL_EMPTY';
sentinelRaHash: string;
}
export interface Cachaca extends ResultEventBase {
evaluationStrategy: 'CACHACA';
}
export interface CacheHit extends ResultEventBase {
evaluationStrategy: 'CACHE_HIT';
}
export type Extensional = ComputedExtensional | NonComputedExtensional;
export type SummaryEvent =
| ComputeSimple
| ComputeRecursive
| InLayer
| Extensional
| SentinelEmpty
| Cachaca
| CacheHit;

View File

@@ -0,0 +1,154 @@
import * as fs from 'fs-extra';
import { RawSourceMap, SourceMapConsumer } from 'source-map';
import { commands, Position, Selection, TextDocument, TextEditor, TextEditorRevealType, TextEditorSelectionChangeEvent, ViewColumn, window, workspace } from 'vscode';
import { DisposableObject } from '../pure/disposable-object';
import { commandRunner } from '../commandRunner';
import { logger } from '../logging';
import { getErrorMessage } from '../pure/helpers-pure';
/** A `Position` within a specified file on disk. */
interface PositionInFile {
filePath: string;
position: Position;
}
/**
* Opens the specified source location in a text editor.
* @param position The position (including file path) to show.
*/
async function showSourceLocation(position: PositionInFile): Promise<void> {
const document = await workspace.openTextDocument(position.filePath);
const editor = await window.showTextDocument(document, ViewColumn.Active);
editor.selection = new Selection(position.position, position.position);
editor.revealRange(editor.selection, TextEditorRevealType.InCenterIfOutsideViewport);
}
/**
* Simple language support for human-readable evaluator log summaries.
*
* This class implements the `codeQL.gotoQL` command, which jumps from RA code to the corresponding
* QL code that generated it. It also tracks the current selection and active editor to enable and
* disable that command based on whether there is a QL mapping for the current selection.
*/
export class SummaryLanguageSupport extends DisposableObject {
/**
* The last `TextDocument` (with language `ql-summary`) for which we tried to find a sourcemap, or
* `undefined` if we have not seen such a document yet.
*/
private lastDocument: TextDocument | undefined = undefined;
/**
* The sourcemap for `lastDocument`, or `undefined` if there was no such sourcemap or document.
*/
private sourceMap: SourceMapConsumer | undefined = undefined;
constructor() {
super();
this.push(window.onDidChangeActiveTextEditor(this.handleDidChangeActiveTextEditor));
this.push(window.onDidChangeTextEditorSelection(this.handleDidChangeTextEditorSelection));
this.push(workspace.onDidCloseTextDocument(this.handleDidCloseTextDocument));
this.push(commandRunner('codeQL.gotoQL', this.handleGotoQL));
}
/**
* Gets the location of the QL code that generated the RA at the current selection in the active
* editor, or `undefined` if there is no mapping.
*/
private async getQLSourceLocation(): Promise<PositionInFile | undefined> {
const editor = window.activeTextEditor;
if (editor === undefined) {
return undefined;
}
const document = editor.document;
if (document.languageId !== 'ql-summary') {
return undefined;
}
if (document.uri.scheme !== 'file') {
return undefined;
}
if (this.lastDocument !== document) {
this.clearCache();
const mapPath = document.uri.fsPath + '.map';
try {
const sourceMapText = await fs.readFile(mapPath, 'utf-8');
const rawMap: RawSourceMap = JSON.parse(sourceMapText);
this.sourceMap = await new SourceMapConsumer(rawMap);
} catch (e: unknown) {
// Error reading sourcemap. Pretend there was no sourcemap.
void logger.log(`Error reading sourcemap file '${mapPath}': ${getErrorMessage(e)}`);
this.sourceMap = undefined;
}
this.lastDocument = document;
}
if (this.sourceMap === undefined) {
return undefined;
}
const qlPosition = this.sourceMap.originalPositionFor({
line: editor.selection.start.line + 1,
column: editor.selection.start.character,
bias: SourceMapConsumer.GREATEST_LOWER_BOUND
});
if ((qlPosition.source === null) || (qlPosition.line === null)) {
// No position found.
return undefined;
}
const line = qlPosition.line - 1; // In `source-map`, lines are 1-based...
const column = qlPosition.column ?? 0; // ...but columns are 0-based :(
return {
filePath: qlPosition.source,
position: new Position(line, column)
};
}
/**
* Clears the cached sourcemap and its corresponding `TextDocument`.
*/
private clearCache(): void {
if (this.sourceMap !== undefined) {
this.sourceMap.destroy();
this.sourceMap = undefined;
this.lastDocument = undefined;
}
}
/**
* Updates the `codeql.hasQLSource` context variable based on the current selection. This variable
* controls whether or not the `codeQL.gotoQL` command is enabled.
*/
private async updateContext(): Promise<void> {
const position = await this.getQLSourceLocation();
await commands.executeCommand('setContext', 'codeql.hasQLSource', position !== undefined);
}
handleDidChangeActiveTextEditor = async (_editor: TextEditor | undefined): Promise<void> => {
await this.updateContext();
}
handleDidChangeTextEditorSelection = async (_e: TextEditorSelectionChangeEvent): Promise<void> => {
await this.updateContext();
}
handleDidCloseTextDocument = (document: TextDocument): void => {
if (this.lastDocument === document) {
this.clearCache();
}
}
handleGotoQL = async (): Promise<void> => {
const position = await this.getQLSourceLocation();
if (position !== undefined) {
await showSourceLocation(position);
}
};
}

View File

@@ -0,0 +1,113 @@
import * as fs from 'fs-extra';
/**
* Location information for a single pipeline invocation in the RA.
*/
export interface PipelineInfo {
startLine: number;
raStartLine: number;
raEndLine: number;
}
/**
* Location information for a single predicate in the RA.
*/
export interface PredicateSymbol {
/**
* `PipelineInfo` for each iteration. A non-recursive predicate will have a single iteration `0`.
*/
iterations: Record<number, PipelineInfo>;
}
/**
* Location information for the RA from an evaluation log. Line numbers point into the
* human-readable log summary.
*/
export interface SummarySymbols {
predicates: Record<string, PredicateSymbol>;
}
// Tuple counts for Expr::Expr::getParent#dispred#f0820431#ff@76d6745o:
const NON_RECURSIVE_TUPLE_COUNT_REGEXP = /^Evaluated relational algebra for predicate (?<predicateName>\S+) with tuple counts:$/;
// Tuple counts for Expr::Expr::getEnclosingStmt#f0820431#bf@923ddwj9 on iteration 0 running pipeline base:
const RECURSIVE_TUPLE_COUNT_REGEXP = /^Evaluated relational algebra for predicate (?<predicateName>\S+) on iteration (?<iteration>\d+) running pipeline (?<pipeline>\S+) with tuple counts:$/;
const RETURN_REGEXP = /^\s*return /;
/**
* Parse a human-readable evaluation log summary to find the location of the RA for each pipeline
* run.
*
* TODO: Once we're more certain about the symbol format, we should have the CLI generate this as it
* generates the human-readabe summary to avoid having to rely on regular expression matching of the
* human-readable text.
*
* @param summaryPath The path to the summary file.
* @param symbolsPath The path to the symbols file to generate.
*/
export async function generateSummarySymbolsFile(summaryPath: string, symbolsPath: string): Promise<void> {
const symbols = await generateSummarySymbols(summaryPath);
await fs.writeFile(symbolsPath, JSON.stringify(symbols));
}
/**
* Parse a human-readable evaluation log summary to find the location of the RA for each pipeline
* run.
*
* @param fileLocation The path to the summary file.
* @returns Symbol information for the summary file.
*/
async function generateSummarySymbols(summaryPath: string): Promise<SummarySymbols> {
const summary = await fs.promises.readFile(summaryPath, { encoding: 'utf-8' });
const symbols: SummarySymbols = {
predicates: {}
};
const lines = summary.split(/\r?\n/);
let lineNumber = 0;
while (lineNumber < lines.length) {
const startLineNumber = lineNumber;
lineNumber++;
const startLine = lines[startLineNumber];
const nonRecursiveMatch = startLine.match(NON_RECURSIVE_TUPLE_COUNT_REGEXP);
let predicateName: string | undefined = undefined;
let iteration = 0;
if (nonRecursiveMatch) {
predicateName = nonRecursiveMatch.groups!.predicateName;
} else {
const recursiveMatch = startLine.match(RECURSIVE_TUPLE_COUNT_REGEXP);
if (recursiveMatch?.groups) {
predicateName = recursiveMatch.groups.predicateName;
iteration = parseInt(recursiveMatch.groups.iteration);
}
}
if (predicateName !== undefined) {
const raStartLine = lineNumber;
let raEndLine: number | undefined = undefined;
while ((lineNumber < lines.length) && (raEndLine === undefined)) {
const raLine = lines[lineNumber];
const returnMatch = raLine.match(RETURN_REGEXP);
if (returnMatch) {
raEndLine = lineNumber;
}
lineNumber++;
}
if (raEndLine !== undefined) {
let symbol = symbols.predicates[predicateName];
if (symbol === undefined) {
symbol = {
iterations: {}
};
symbols.predicates[predicateName] = symbol;
}
symbol.iterations[iteration] = {
startLine: lineNumber,
raStartLine: raStartLine,
raEndLine: raEndLine
};
}
}
}
return symbols;
}

View File

@@ -1,5 +1,5 @@
import { window as Window, OutputChannel, Progress, Disposable } from 'vscode';
import { DisposableObject } from './vscode-utils/disposable-object';
import { window as Window, OutputChannel, Progress } from 'vscode';
import { DisposableObject } from './pure/disposable-object';
import * as fs from 'fs-extra';
import * as path from 'path';
@@ -26,11 +26,6 @@ export interface Logger {
* @param location log to remove
*/
removeAdditionalLogLocation(location: string | undefined): void;
/**
* The base location location where all side log files are stored.
*/
getBaseLocation(): string | undefined;
}
export type ProgressReporter = Progress<{ message: string }>;
@@ -39,19 +34,13 @@ export type ProgressReporter = Progress<{ message: string }>;
export class OutputChannelLogger extends DisposableObject implements Logger {
public readonly outputChannel: OutputChannel;
private readonly additionalLocations = new Map<string, AdditionalLogLocation>();
private additionalLogLocationPath: string | undefined;
isCustomLogDirectory: boolean;
constructor(private title: string) {
constructor(title: string) {
super();
this.outputChannel = Window.createOutputChannel(title);
this.push(this.outputChannel);
}
init(storagePath: string): void {
this.additionalLogLocationPath = path.join(storagePath, this.title);
// clear out any old state from previous runs
fs.remove(this.additionalLogLocationPath);
this.isCustomLogDirectory = false;
}
/**
@@ -61,31 +50,41 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
* continuing.
*/
async log(message: string, options = {} as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
if (options.trailingNewline) {
this.outputChannel.appendLine(message);
} else {
this.outputChannel.append(message);
}
if (this.additionalLogLocationPath && options.additionalLogLocation) {
const logPath = path.join(this.additionalLogLocationPath, 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);
this.track(additional);
try {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
if (options.trailingNewline) {
this.outputChannel.appendLine(message);
} else {
this.outputChannel.append(message);
}
await additional.log(message, options);
if (options.additionalLogLocation) {
if (!path.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
console.log('Output channel is closed logging to console instead:', message);
} else {
throw e;
}
}
}
@@ -94,26 +93,15 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
}
removeAdditionalLogLocation(location: string | undefined): void {
if (this.additionalLogLocationPath && location) {
const logPath = location.startsWith(this.additionalLogLocationPath)
? location
: path.join(this.additionalLogLocationPath, location);
const additional = this.additionalLocations.get(logPath);
if (additional) {
this.disposeAndStopTracking(additional);
this.additionalLocations.delete(logPath);
}
if (location) {
this.additionalLocations.delete(location);
}
}
getBaseLocation() {
return this.additionalLogLocationPath;
}
}
class AdditionalLogLocation extends Disposable {
class AdditionalLogLocation {
constructor(private location: string) {
super(() => { /**/ });
/**/
}
async log(message: string, options = {} as LogOptions): Promise<void> {
@@ -126,10 +114,6 @@ class AdditionalLogLocation extends Disposable {
encoding: 'utf8'
});
}
async dispose(): Promise<void> {
await fs.remove(this.location);
}
}
/** The global logger for the extension. */

View File

@@ -0,0 +1,146 @@
import { CliVersionConstraint, CodeQLCliServer } from './cli';
import {
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogInformationMessage,
} from './helpers';
import { QuickPickItem, window } from 'vscode';
import { ProgressCallback, UserCancellationException } from './commandRunner';
import { logger } from './logging';
const QUERY_PACKS = [
'codeql/cpp-queries',
'codeql/csharp-queries',
'codeql/go-queries',
'codeql/java-queries',
'codeql/javascript-queries',
'codeql/python-queries',
'codeql/ruby-queries',
'codeql/csharp-solorigate-queries',
'codeql/javascript-experimental-atm-queries',
];
/**
* Prompts user to choose packs to download, and downloads them.
*
* @param cliServer The CLI server.
* @param progress A progress callback.
*/
export async function handleDownloadPacks(
cliServer: CodeQLCliServer,
progress: ProgressCallback,
): Promise<void> {
if (!(await cliServer.cliConstraints.supportsPackaging())) {
throw new Error(`Packaging commands are not supported by this version of CodeQL. Please upgrade to v${CliVersionConstraint.CLI_VERSION_WITH_PACKAGING
} or later.`);
}
progress({
message: 'Choose packs to download',
step: 1,
maxStep: 2,
});
let packsToDownload: string[] = [];
const queryPackOption = 'Download all core query packs';
const customPackOption = 'Download custom specified pack';
const quickpick = await window.showQuickPick(
[queryPackOption, customPackOption],
{ ignoreFocusOut: true }
);
if (quickpick === queryPackOption) {
packsToDownload = QUERY_PACKS;
} else if (quickpick === customPackOption) {
const customPack = await window.showInputBox({
prompt:
'Enter the <package-scope/name[@version]> of the pack to download',
ignoreFocusOut: true,
});
if (customPack) {
packsToDownload.push(customPack);
} else {
throw new UserCancellationException('No pack specified.');
}
}
if (packsToDownload?.length > 0) {
progress({
message: 'Downloading packs. This may take a few minutes.',
step: 2,
maxStep: 2,
});
try {
await cliServer.packDownload(packsToDownload);
void showAndLogInformationMessage('Finished downloading packs.');
} catch (error) {
void showAndLogErrorMessage(
'Unable to download all packs. See log for more details.'
);
}
}
}
interface QLPackQuickPickItem extends QuickPickItem {
packRootDir: string[];
}
/**
* Prompts user to choose packs to install, and installs them.
*
* @param cliServer The CLI server.
* @param progress A progress callback.
*/
export async function handleInstallPackDependencies(
cliServer: CodeQLCliServer,
progress: ProgressCallback,
): Promise<void> {
if (!(await cliServer.cliConstraints.supportsPackaging())) {
throw new Error(`Packaging commands are not supported by this version of CodeQL. Please upgrade to v${CliVersionConstraint.CLI_VERSION_WITH_PACKAGING
} or later.`);
}
progress({
message: 'Choose packs to install dependencies for',
step: 1,
maxStep: 2,
});
const workspacePacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const quickPickItems = Object.entries(workspacePacks).map<QLPackQuickPickItem>(([key, value]) => ({
label: key,
packRootDir: value,
}));
const packsToInstall = await window.showQuickPick(quickPickItems, {
placeHolder: 'Select packs to install dependencies for',
canPickMany: true,
ignoreFocusOut: true,
});
const numberOfPacks = packsToInstall?.length || 0;
if (packsToInstall && numberOfPacks > 0) {
const failedPacks = [];
const errors = [];
// Start at 1 because we already have the first step
let count = 1;
for (const pack of packsToInstall) {
count++;
progress({
message: `Installing dependencies for ${pack.label}`,
step: count,
maxStep: numberOfPacks + 1,
});
try {
for (const dir of pack.packRootDir) {
await cliServer.packInstall(dir);
}
} catch (error) {
failedPacks.push(pack.label);
errors.push(error);
}
}
if (failedPacks.length > 0) {
void logger.log(`Errors:\n${errors.join('\n')}`);
throw new Error(
`Unable to install pack dependencies for: ${failedPacks.join(', ')}. See log for more details.`
);
} else {
void showAndLogInformationMessage('Finished installing pack dependencies.');
}
} else {
throw new UserCancellationException('No packs selected.');
}
}

View File

@@ -79,11 +79,11 @@ export interface WholeFileLocation {
export type ResolvableLocationValue = WholeFileLocation | LineColumnLocation;
export type UrlValue = ResolvableLocationValue | string;
export type UrlValue = ResolvableLocationValue | string;
export type ColumnValue = EntityValue | number | string | boolean;
export type CellValue = EntityValue | number | string | boolean;
export type ResultRow = ColumnValue[];
export type ResultRow = CellValue[];
export interface RawResultSet {
readonly schema: ResultSetSchema;
@@ -103,7 +103,14 @@ export function transformBqrsResultSet(
};
}
export interface DecodedBqrsChunk {
tuples: ColumnValue[][];
next?: number;
type BqrsKind = 'String' | 'Float' | 'Integer' | 'String' | 'Boolean' | 'Date' | 'Entity';
interface BqrsColumn {
name: string;
kind: BqrsKind;
}
export interface DecodedBqrsChunk {
tuples: CellValue[][];
next?: number;
columns: BqrsColumn[];
}

View File

@@ -4,6 +4,7 @@ import {
LineColumnLocation,
WholeFileLocation
} from './bqrs-cli-types';
import { createRemoteFileRef } from './location-link-utils';
/**
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
@@ -83,8 +84,7 @@ export function isLineColumnLoc(loc: UrlValue): loc is LineColumnLocation {
&& 'startLine' in loc
&& 'startColumn' in loc
&& 'endLine' in loc
&& 'endColumn' in loc
&& loc.endColumn > 0;
&& 'endColumn' in loc;
}
export function isWholeFileLoc(loc: UrlValue): loc is WholeFileLocation {
@@ -94,3 +94,45 @@ export function isWholeFileLoc(loc: UrlValue): loc is WholeFileLocation {
export function isStringLoc(loc: UrlValue): loc is string {
return typeof loc === 'string';
}
export function tryGetRemoteLocation(
loc: UrlValue | undefined,
fileLinkPrefix: string,
sourceLocationPrefix: string | undefined,
): string | undefined {
const resolvableLocation = tryGetResolvableLocation(loc);
if (!resolvableLocation) {
return undefined;
}
let trimmedLocation: string;
// Remote locations have the following format:
// "file:${sourceLocationPrefix}/relative/path/to/file"
// So we need to strip off the first part to get the relative path.
if (sourceLocationPrefix) {
if (!resolvableLocation.uri.startsWith(`file:${sourceLocationPrefix}/`)) {
return undefined;
}
trimmedLocation = resolvableLocation.uri.replace(`file:${sourceLocationPrefix}/`, '');
} else {
// If the source location prefix is empty (e.g. for older remote queries), we assume that the database
// was created on a Linux actions runner and has the format:
// "file:/home/runner/work/<repo>/<repo>/relative/path/to/file"
// So we need to drop the first 6 parts of the path.
if (!resolvableLocation.uri.startsWith('file:/home/runner/work/')) {
return undefined;
}
const locationParts = resolvableLocation.uri.split('/');
trimmedLocation = locationParts.slice(6, locationParts.length).join('/');
}
const fileLink = {
fileLinkPrefix,
filePath: trimmedLocation,
};
return createRemoteFileRef(
fileLink,
resolvableLocation.startLine,
resolvableLocation.endLine);
}

View File

@@ -0,0 +1,39 @@
/*
* Contains an assortment of helper constants and functions for working with dates.
*/
const dateWithoutYearFormatter = new Intl.DateTimeFormat(undefined, {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
const dateFormatter = new Intl.DateTimeFormat(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
export function formatDate(value: Date): string {
if (value.getFullYear() === new Date().getFullYear()) {
return dateWithoutYearFormatter.format(value);
}
return dateFormatter.format(value);
}
// These are overloads for the function that allow us to not add an extra
// type check when the value is definitely not undefined.
export function parseDate(value: string): Date;
export function parseDate(value: string | undefined | null): Date | undefined;
export function parseDate(value: string | undefined | null): Date | undefined {
if (value === undefined || value === null) {
return undefined;
}
return new Date(value);
}

View File

@@ -1,4 +1,11 @@
import { Disposable } from 'vscode';
// Avoid explicitly referencing Disposable type in vscode.
// This file cannot have dependencies on the vscode API.
interface Disposable {
dispose(): any;
}
export type DisposeHandler = (disposable: Disposable) => void;
/**
* Base class to make it easier to implement a `Disposable` that owns other disposable object.
@@ -40,21 +47,39 @@ export abstract class DisposableObject implements Disposable {
* @param obj The object to stop tracking.
*/
protected disposeAndStopTracking(obj: Disposable): void {
if (obj !== undefined) {
this.tracked!.delete(obj);
if (obj && this.tracked) {
this.tracked.delete(obj);
obj.dispose();
}
}
public dispose() {
/**
* Dispose this object and all contained objects
*
* @param disposeHandler An optional dispose handler that gets
* passed each element to dispose. The dispose handler
* can choose how (and if) to dispose the object. The
* primary usage is for tests that should not dispose
* all items of a disposable.
*/
public dispose(disposeHandler?: DisposeHandler) {
if (this.tracked !== undefined) {
for (const trackedObject of this.tracked.values()) {
trackedObject.dispose();
if (disposeHandler) {
disposeHandler(trackedObject);
} else {
trackedObject.dispose();
}
}
this.tracked = undefined;
}
while (this.disposables.length > 0) {
this.disposables.pop()!.dispose();
const disposable = this.disposables.pop()!;
if (disposeHandler) {
disposeHandler(disposable);
} else {
disposable.dispose();
}
}
}
}

View File

@@ -1,8 +1,9 @@
/**
* helpers-pure.ts
* ------------
*
* Helper functions that don't depend on vscode and therefore can be used by the front-end and pure unit tests.
* Helper functions that don't depend on vscode or the CLI and therefore can be used by the front-end and pure unit tests.
*/
/**
@@ -29,3 +30,28 @@ export const asyncFilter = async function <T>(arr: T[], predicate: (arg0: T) =>
const results = await Promise.all(arr.map(predicate));
return arr.filter((_, index) => results[index]);
};
/**
* This regex matches strings of the form `owner/repo` where:
* - `owner` is made up of alphanumeric characters, hyphens, underscores, or periods
* - `repo` is made up of alphanumeric characters, hyphens, underscores, or periods
*/
export const REPO_REGEX = /^[a-zA-Z0-9-_\.]+\/[a-zA-Z0-9-_\.]+$/;
/**
* This regex matches GiHub organization and user strings. These are made up for alphanumeric
* characters, hyphens, underscores or periods.
*/
export const OWNER_REGEX = /^[a-zA-Z0-9-_\.]+$/;
export function getErrorMessage(e: any) {
return e instanceof Error ? e.message : String(e);
}
export function getErrorStack(e: any) {
return e instanceof Error ? e.stack ?? '' : '';
}
export function asError(e: any): Error {
return e instanceof Error ? e : new Error(String(e));
}

View File

@@ -1,5 +1,12 @@
import * as sarif from 'sarif';
import { AnalysisResults } from '../remote-queries/shared/analysis-result';
import { AnalysisSummary, RemoteQueryResult } from '../remote-queries/shared/remote-query-result';
import { RawResultSet, ResultRow, ResultSetSchema, Column, ResolvableLocationValue } from './bqrs-cli-types';
import {
VariantAnalysis,
VariantAnalysisScannedRepositoryResult,
VariantAnalysisScannedRepositoryState,
} from '../remote-queries/shared/variant-analysis';
/**
* This module contains types and code that are shared between
@@ -8,15 +15,17 @@ import { RawResultSet, ResultRow, ResultSetSchema, Column, ResolvableLocationVal
export const SELECT_TABLE_NAME = '#select';
export const ALERTS_TABLE_NAME = 'alerts';
export const GRAPH_TABLE_NAME = 'graph';
export type RawTableResultSet = { t: 'RawResultSet' } & RawResultSet;
export type PathTableResultSet = {
t: 'SarifResultSet';
export type InterpretedResultSet<T> = {
t: 'InterpretedResultSet';
readonly schema: ResultSetSchema;
name: string;
} & Interpretation;
interpretation: InterpretationT<T>;
};
export type ResultSet = RawTableResultSet | PathTableResultSet;
export type ResultSet = RawTableResultSet | InterpretedResultSet<InterpretationData>;
/**
* Only ever show this many rows in a raw result table.
@@ -34,6 +43,7 @@ export interface QueryMetadata {
description?: string;
id?: string;
kind?: string;
scored?: string;
}
export interface PreviousExecution {
@@ -43,18 +53,31 @@ export interface PreviousExecution {
durationSeconds: number;
}
export interface Interpretation {
sourceLocationPrefix: string;
numTruncatedResults: number;
numTotalResults: number;
export type SarifInterpretationData = {
t: 'SarifInterpretationData';
/**
* sortState being undefined means don't sort, just present results in the order
* they appear in the sarif file.
*/
sortState?: InterpretedResultsSortState;
sarif: sarif.Log;
} & sarif.Log;
export type GraphInterpretationData = {
t: 'GraphInterpretationData';
dot: string[];
};
export type InterpretationData = SarifInterpretationData | GraphInterpretationData;
export interface InterpretationT<T> {
sourceLocationPrefix: string;
numTruncatedResults: number;
numTotalResults: number;
data: T;
}
export type Interpretation = InterpretationT<InterpretationData>;
export interface ResultsPaths {
resultsPath: string;
interpretedResultsPath: string;
@@ -88,6 +111,8 @@ export interface SetStateMsg {
interpretation: undefined | Interpretation;
database: DatabaseInfo;
metadata?: QueryMetadata;
queryName: string;
queryPath: string;
/**
* Whether to keep displaying the old results while rendering the new results.
*
@@ -116,6 +141,8 @@ export interface ShowInterpretedPageMsg {
numPages: number;
pageSize: number;
resultSetNames: string[];
queryName: string;
queryPath: string;
}
/** Advance to the next or previous path no in the path viewer */
@@ -152,8 +179,9 @@ export type FromResultsViewMsg =
| ToggleDiagnostics
| ChangeRawResultsSortMsg
| ChangeInterpretedResultsSortMsg
| ResultViewLoaded
| ChangePage;
| ViewLoadedMsg
| ChangePage
| OpenFileMsg;
/**
* Message from the results view to open a database source
@@ -165,6 +193,19 @@ export interface ViewSourceFileMsg {
databaseUri: string;
}
/**
* Message from the results view to open a file in an editor.
*/
export interface OpenFileMsg {
t: 'openFile';
/* Full path to the file to open. */
filePath: string;
}
export interface OpenVirtualFileMsg {
t: 'openVirtualFile';
queryText: string;
}
/**
* Message from the results view to toggle the display of
@@ -180,11 +221,11 @@ interface ToggleDiagnostics {
}
/**
* Message from the results view to signal that loading the results
* is complete.
* Message from a view signal that loading is complete.
*/
interface ResultViewLoaded {
t: 'resultViewLoaded';
interface ViewLoadedMsg {
t: 'viewLoaded';
viewName: string;
}
/**
@@ -243,18 +284,11 @@ interface ChangeInterpretedResultsSortMsg {
* Message from the compare view to the extension.
*/
export type FromCompareViewMessage =
| CompareViewLoadedMessage
| ViewLoadedMsg
| ChangeCompareMessage
| ViewSourceFileMsg
| OpenQueryMessage;
/**
* Message from the compare view to signal the completion of loading results.
*/
interface CompareViewLoadedMessage {
t: 'compareViewLoaded';
}
/**
* Message from the compare view to request opening a query.
*/
@@ -295,7 +329,7 @@ export interface SetComparisonsMessage {
readonly currentResultSetName: string;
readonly rows: QueryCompareResult | undefined;
readonly message: string | undefined;
readonly datebaseUri: string;
readonly databaseUri: string;
}
export enum DiffKind {
@@ -336,8 +370,9 @@ export function getDefaultResultSetName(
// Choose first available result set from the array
return [
ALERTS_TABLE_NAME,
GRAPH_TABLE_NAME,
SELECT_TABLE_NAME,
resultSetNames[0],
resultSetNames[0]
].filter((resultSetName) => resultSetNames.includes(resultSetName))[0];
}
@@ -350,3 +385,101 @@ export interface ParsedResultSets {
resultSetNames: string[];
resultSet: ResultSet;
}
export type FromRemoteQueriesMessage =
| ViewLoadedMsg
| RemoteQueryErrorMessage
| OpenFileMsg
| OpenVirtualFileMsg
| RemoteQueryDownloadAnalysisResultsMessage
| RemoteQueryDownloadAllAnalysesResultsMessage
| RemoteQueryExportResultsMessage
| CopyRepoListMessage;
export type ToRemoteQueriesMessage =
| SetRemoteQueryResultMessage
| SetAnalysesResultsMessage;
export interface SetRemoteQueryResultMessage {
t: 'setRemoteQueryResult';
queryResult: RemoteQueryResult
}
export interface SetAnalysesResultsMessage {
t: 'setAnalysesResults';
analysesResults: AnalysisResults[];
}
export interface RemoteQueryErrorMessage {
t: 'remoteQueryError';
error: string;
}
export interface RemoteQueryDownloadAnalysisResultsMessage {
t: 'remoteQueryDownloadAnalysisResults';
analysisSummary: AnalysisSummary
}
export interface RemoteQueryDownloadAllAnalysesResultsMessage {
t: 'remoteQueryDownloadAllAnalysesResults';
analysisSummaries: AnalysisSummary[];
}
export interface RemoteQueryExportResultsMessage {
t: 'remoteQueryExportResults';
queryId: string;
}
export interface CopyRepoListMessage {
t: 'copyRepoList';
queryId: string;
}
export interface SetVariantAnalysisMessage {
t: 'setVariantAnalysis';
variantAnalysis: VariantAnalysis;
}
export type StopVariantAnalysisMessage = {
t: 'stopVariantAnalysis';
variantAnalysisId: number;
}
export type VariantAnalysisState = {
variantAnalysisId: number;
}
export interface SetRepoResultsMessage {
t: 'setRepoResults';
repoResults: VariantAnalysisScannedRepositoryResult[];
}
export interface SetRepoStatesMessage {
t: 'setRepoStates';
repoStates: VariantAnalysisScannedRepositoryState[];
}
export interface RequestRepositoryResultsMessage {
t: 'requestRepositoryResults';
repositoryFullName: string;
}
export interface OpenQueryFileMessage {
t: 'openQueryFile';
}
export interface OpenQueryTextMessage {
t: 'openQueryText';
}
export type ToVariantAnalysisMessage =
| SetVariantAnalysisMessage
| SetRepoResultsMessage
| SetRepoStatesMessage;
export type FromVariantAnalysisMessage =
| ViewLoadedMsg
| StopVariantAnalysisMessage
| RequestRepositoryResultsMessage
| OpenQueryFileMessage
| OpenQueryTextMessage;

View File

@@ -15,38 +15,7 @@
*/
import * as rpc from 'vscode-jsonrpc';
/**
* A position within a QL file.
*/
export interface Position {
/**
* The one-based index of the start line
*/
line: number;
/**
* The one-based offset of the start column within
* the start line in UTF-16 code-units
*/
column: number;
/**
* The one-based index of the end line line
*/
endLine: number;
/**
* The one-based offset of the end column within
* the end line in UTF-16 code-units
*/
endColumn: number;
/**
* The path of the file.
* If the file name is "Compiler Generated" the
* the position is not a real position but
* arises from compiler generated code.
*/
fileName: string;
}
import * as shared from './messages-shared';
/**
* A query that should be checked for any errors or warnings
@@ -150,6 +119,15 @@ export interface CompilationOptions {
* Whether to disable toString values in the results.
*/
noComputeToString: boolean;
/**
* Whether to ensure that elements that do not have a displayString
* get reported anyway. Useful for universal compilation options.
*/
computeDefaultStrings: boolean;
/**
* Emit debug information in compiled query.
*/
emitDebugInfo: boolean;
}
/**
@@ -249,28 +227,6 @@ export interface DILQuery {
dilSource: string;
}
/**
* The way of compiling the query, as a normal query
* or a subset of it. Note that precisely one of the two options should be set.
*/
export interface CompilationTarget {
/**
* Compile as a normal query
*/
query?: {};
/**
* Compile as a quick evaluation
*/
quickEval?: QuickEvalOptions;
}
/**
* Options for quick evaluation
*/
export interface QuickEvalOptions {
quickEvalPos?: Position;
}
/**
* The result of checking a query.
*/
@@ -380,8 +336,8 @@ export namespace ResultColumnKind {
*/
export const BOOLEAN = 3;
/**
* A column of type `date`
*/
* A column of type `date`
*/
export const DATE = 4;
/**
* A column of a non-primitive type
@@ -401,6 +357,25 @@ export interface CompileUpgradeParams {
* A directory to store parts of the compiled upgrade
*/
upgradeTempDir: string;
/**
* Enable single file upgrades, set to true to allow
* using single file upgrades.
*/
singleFileUpgrades: true;
}
/**
* Parameters for compiling an upgrade.
*/
export interface CompileUpgradeSequenceParams {
/**
* The sequence of upgrades to compile
*/
upgradePaths: string[];
/**
* A directory to store parts of the compiled upgrade
*/
upgradeTempDir: string;
}
/**
@@ -450,6 +425,19 @@ export interface CompileUpgradeResult {
*/
error?: string;
}
export interface CompileUpgradeSequenceResult {
/**
* The compiled upgrades as a single file.
*/
compiledUpgrade?: string;
/**
* Any errors that occurred when checking the scripts.
*/
error?: string;
}
/**
* A description of a upgrade process
*/
@@ -487,10 +475,13 @@ export interface UpgradeDescription {
newSha: string;
}
export type CompiledUpgrades = MultiFileCompiledUpgrades | SingleFileCompiledUpgrades
/**
* A compiled upgrade.
* The parts shared by all compiled upgrades
*/
export interface CompiledUpgrades {
interface CompiledUpgradesBase {
/**
* The initial sha of the dbscheme to upgrade from
*/
@@ -499,14 +490,46 @@ export interface CompiledUpgrades {
* The path to the new dataset statistics
*/
newStatsPath: string;
/**
* The sha of the target dataset.
*/
targetSha: string;
}
/**
* A compiled upgrade.
* The upgrade is spread among multiple files.
*/
interface MultiFileCompiledUpgrades extends CompiledUpgradesBase {
/**
* The path to the new dataset dbscheme
*/
newDbscheme: string;
/**
* The steps in the upgrade path
*/
scripts: CompiledUpgradeScript[];
/**
* The sha of the target dataset.
* Will never exist in an old result
*/
targetSha: string;
compiledUpgradeFile?: never;
}
/**
* A compiled upgrade.
* The upgrade is in a single file.
*/
export interface SingleFileCompiledUpgrades extends CompiledUpgradesBase {
/**
* The steps in the upgrade path
*/
descriptions: UpgradeDescription[];
/**
* A path to a file containing the upgrade
*/
compiledUpgradeFile: string;
}
/**
@@ -574,6 +597,35 @@ export interface ClearCacheParams {
*/
dryRun: boolean;
}
/**
* Parameters to start a new structured log
*/
export interface StartLogParams {
/**
* The dataset for which we want to start a new structured log
*/
db: Dataset;
/**
* The path where we want to place the new structured log
*/
logPath: string;
}
/**
* Parameters to terminate a structured log
*/
export interface EndLogParams {
/**
* The dataset for which we want to terminated the log
*/
db: Dataset;
/**
* The path of the log to terminate, will be a no-op if we aren't logging here
*/
logPath: string;
}
/**
* Parameters for trimming the cache of a dataset
*/
@@ -610,6 +662,26 @@ export interface ClearCacheResult {
deletionMessage: string;
}
/**
* The result of starting a new structured log.
*/
export interface StartLogResult {
/**
* A user friendly message saying what happened.
*/
outcomeMessage: string;
}
/**
* The result of terminating a structured log.
*/
export interface EndLogResult {
/**
* A user friendly message saying what happened.
*/
outcomeMessage: string;
}
/**
* Parameters for running a set of queries
*/
@@ -639,6 +711,11 @@ export interface EvaluateQueriesParams {
export type TemplateDefinitions = { [key: string]: TemplateSource }
export interface MlModel {
/** A URI pointing to the root directory of the model. */
uri: string;
}
/**
* A single query that should be run
*/
@@ -651,6 +728,10 @@ export interface QueryToRun {
* A uri pointing to the qlo to run.
*/
qlo: string;
/**
* A uri pointing to the compiled upgrade file.
*/
compiledUpgrade?: string;
/**
* The path where we should save this queries results
*/
@@ -668,6 +749,11 @@ export interface QueryToRun {
* map should be set to the empty set or give an error.
*/
allowUnknownTemplates: boolean;
/**
* The list of ML models that should be made available
* when evaluating the query.
*/
availableMlModels?: MlModel[];
}
/**
@@ -750,7 +836,7 @@ export interface ResultSet {
/**
* The type returned when the evaluation is complete
*/
export type EvaluationComplete = {};
export type EvaluationComplete = Record<string, never>;
/**
* The result of a single query
@@ -837,7 +923,6 @@ export interface RunUpgradeParams {
toRun: CompiledUpgrades;
}
/**
* The result of running an upgrade
*/
@@ -857,39 +942,37 @@ export interface RunUpgradeResult {
finalSha: string;
}
export interface RegisterDatabasesParams {
databases: Dataset[];
}
export interface DeregisterDatabasesParams {
databases: Dataset[];
}
export type RegisterDatabasesResult = {
registeredDatabases: Dataset[];
};
export type DeregisterDatabasesResult = {
registeredDatabases: Dataset[];
};
/**
* Type for any action that could have progress messages.
* A position within a QL file.
*/
export interface WithProgressId<T> {
/**
* The main body
*/
body: T;
/**
* The id used to report progress updates
*/
progressId: number;
}
export type Position = shared.Position;
export interface ProgressMessage {
/**
* The id of the operation that is running
*/
id: number;
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
/**
* The way of compiling the query, as a normal query
* or a subset of it. Note that precisely one of the two options should be set.
*/
export type CompilationTarget = shared.CompilationTarget;
export type QuickEvalOptions = shared.QuickEvalOptions;
export type WithProgressId<T> = shared.WithProgressId<T>;
export type ProgressMessage = shared.ProgressMessage;
/**
* Check a Ql query for errors without compiling it
@@ -913,7 +996,20 @@ export const checkUpgrade = new rpc.RequestType<WithProgressId<UpgradeParams>, C
* Compile an upgrade script to upgrade a dataset.
*/
export const compileUpgrade = new rpc.RequestType<WithProgressId<CompileUpgradeParams>, CompileUpgradeResult, void, void>('compilation/compileUpgrade');
/**
* Compile an upgrade script to upgrade a dataset.
*/
export const compileUpgradeSequence = new rpc.RequestType<WithProgressId<CompileUpgradeSequenceParams>, CompileUpgradeSequenceResult, void, void>('compilation/compileUpgradeSequence');
/**
* Start a new structured log in the evaluator, terminating the previous one if it exists
*/
export const startLog = new rpc.RequestType<WithProgressId<StartLogParams>, StartLogResult, void, void>('evaluation/startLog');
/**
* Terminate a structured log in the evaluator. Is a no-op if we aren't logging to the given location
*/
export const endLog = new rpc.RequestType<WithProgressId<EndLogParams>, EndLogResult, void, void>('evaluation/endLog');
/**
* Clear the cache of a dataset
@@ -934,13 +1030,24 @@ export const runQueries = new rpc.RequestType<WithProgressId<EvaluateQueriesPara
*/
export const runUpgrade = new rpc.RequestType<WithProgressId<RunUpgradeParams>, RunUpgradeResult, void, void>('evaluation/runUpgrade');
export const registerDatabases = new rpc.RequestType<
WithProgressId<RegisterDatabasesParams>,
RegisterDatabasesResult,
void,
void
>('evaluation/registerDatabases');
export const deregisterDatabases = new rpc.RequestType<
WithProgressId<DeregisterDatabasesParams>,
DeregisterDatabasesResult,
void,
void
>('evaluation/deregisterDatabases');
/**
* Request returned to the client to notify completion of a query.
* The full runQueries job is completed when all queries are acknowledged.
*/
export const completeQuery = new rpc.RequestType<EvaluationResult, Record<string, any>, void, void>('evaluation/queryCompleted');
/**
* A notification that the progress has been changed.
*/
export const progress = new rpc.NotificationType<ProgressMessage, void>('ql/progressUpdated');
export const progress = shared.progress;

View File

@@ -0,0 +1,15 @@
import { FileLink } from '../remote-queries/shared/analysis-result';
export function createRemoteFileRef(
fileLink: FileLink,
startLine?: number,
endLine?: number
): string {
if (startLine && endLine) {
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}#L${startLine}-L${endLine}`;
} else if (startLine) {
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}#L${startLine}`;
} else {
return `${fileLink.fileLinkPrefix}/${fileLink.filePath}`;
}
}

View File

@@ -0,0 +1,34 @@
import { readJsonlFile } from '../log-insights/jsonl-reader';
// TODO(angelapwen): Only load in necessary information and
// location in bytes for this log to save memory.
export interface EvalLogData {
predicateName: string;
millis: number;
resultSize: number;
// Key: pipeline identifier; Value: array of pipeline steps
ra: Record<string, string[]>;
}
/**
* A pure method that parses a string of evaluator log summaries into
* an array of EvalLogData objects.
*/
export async function parseViewerData(jsonSummaryPath: string): Promise<EvalLogData[]> {
const viewerData: EvalLogData[] = [];
await readJsonlFile(jsonSummaryPath, async jsonObj => {
// Only convert log items that have an RA and millis field
if (jsonObj.ra !== undefined && jsonObj.millis !== undefined) {
const newLogData: EvalLogData = {
predicateName: jsonObj.predicateName,
millis: jsonObj.millis,
resultSize: jsonObj.resultSize,
ra: jsonObj.ra
};
viewerData.push(newLogData);
}
});
return viewerData;
}

View File

@@ -0,0 +1,110 @@
/**
* Types for messages exchanged during jsonrpc communication with the
* the CodeQL query server.
*
* This file exists in the queryserver and in the vscode extension, and
* should be kept in sync between them.
*
* A note about the namespaces below, which look like they are
* essentially enums, namely Severity, ResultColumnKind, and
* QueryResultType. By design, for the sake of extensibility, clients
* receiving messages of this protocol are supposed to accept any
* number for any of these types. We commit to the given meaning of
* the numbers listed in constants in the namespaces, and we commit to
* the fact that any unknown QueryResultType value counts as an error.
*/
import * as rpc from 'vscode-jsonrpc';
/**
* A position within a QL file.
*/
export interface Position {
/**
* The one-based index of the start line
*/
line: number;
/**
* The one-based offset of the start column within
* the start line in UTF-16 code-units
*/
column: number;
/**
* The one-based index of the end line line
*/
endLine: number;
/**
* The one-based offset of the end column within
* the end line in UTF-16 code-units
*/
endColumn: number;
/**
* The path of the file.
* If the file name is "Compiler Generated" the
* the position is not a real position but
* arises from compiler generated code.
*/
fileName: string;
}
/**
* The way of compiling the query, as a normal query
* or a subset of it. Note that precisely one of the two options should be set.
*/
export interface CompilationTarget {
/**
* Compile as a normal query
*/
query?: Record<string, never>;
/**
* Compile as a quick evaluation
*/
quickEval?: QuickEvalOptions;
}
/**
* Options for quick evaluation
*/
export interface QuickEvalOptions {
quickEvalPos?: Position;
}
/**
* Type for any action that could have progress messages.
*/
export interface WithProgressId<T> {
/**
* The main body
*/
body: T;
/**
* The id used to report progress updates
*/
progressId: number;
}
export interface ProgressMessage {
/**
* The id of the operation that is running
*/
id: number;
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
/**
* A notification that the progress has been changed.
*/
export const progress = new rpc.NotificationType<ProgressMessage, void>('ql/progressUpdated');

View File

@@ -0,0 +1,215 @@
/**
* Types for messages exchanged during jsonrpc communication with the
* the CodeQL query server.
*
* This file exists in the queryserver and in the vscode extension, and
* should be kept in sync between them.
*
* A note about the namespaces below, which look like they are
* essentially enums, namely Severity, ResultColumnKind, and
* QueryResultType. By design, for the sake of extensibility, clients
* receiving messages of this protocol are supposed to accept any
* number for any of these types. We commit to the given meaning of
* the numbers listed in constants in the namespaces, and we commit to
* the fact that any unknown QueryResultType value counts as an error.
*/
import * as rpc from 'vscode-jsonrpc';
import * as shared from './messages-shared';
/**
* Parameters to clear the cache
*/
export interface ClearCacheParams {
/**
* The dataset for which we want to clear the cache
*/
db: string;
/**
* Whether the cache should actually be cleared.
*/
dryRun: boolean;
}
/**
* Parameters for trimming the cache of a dataset
*/
export interface TrimCacheParams {
/**
* The dataset that we want to trim the cache of.
*/
db: string;
}
/**
* The result of trimming or clearing the cache.
*/
export interface ClearCacheResult {
/**
* A user friendly message saying what was or would be
* deleted.
*/
deletionMessage: string;
}
export type QueryResultType = number;
/**
* The result of running a query. This namespace is intentionally not
* an enum, see "for the sake of extensibility" comment above.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace QueryResultType {
/**
* The query ran successfully
*/
export const SUCCESS = 0;
/**
* The query failed due to an reason
* that isn't listed
*/
export const OTHER_ERROR = 1;
/**
* The query failed do to compilation erorrs
*/
export const COMPILATION_ERROR = 2;
/**
* The query failed due to running out of
* memory
*/
export const OOM = 3;
/**
* The query failed because it was cancelled.
*/
export const CANCELLATION = 4;
/**
* The dbscheme basename was not the same
*/
export const DBSCHEME_MISMATCH_NAME = 5;
/**
* No upgrade was found
*/
export const DBSCHEME_NO_UPGRADE = 6;
}
export interface RegisterDatabasesParams {
databases: string[];
}
export interface DeregisterDatabasesParams {
databases: string[];
}
export type RegisterDatabasesResult = {
registeredDatabases: string[];
};
export type DeregisterDatabasesResult = {
registeredDatabases: string[];
};
export interface RunQueryParams {
/**
* The path of the query
*/
queryPath: string,
/**
* The output path
*/
outputPath: string,
/**
* The database path
*/
db: string,
additionalPacks: string[],
target: CompilationTarget,
externalInputs: Record<string, string>,
singletonExternalInputs: Record<string, string>,
dilPath?: string,
logPath?: string
}
export interface RunQueryResult {
resultType: QueryResultType,
message?: string,
expectedDbschemeName?: string,
evaluationTime: number;
}
export interface UpgradeParams {
db: string,
additionalPacks: string[],
}
export type UpgradeResult = Record<string, unknown>;
export type ClearPackCacheParams = Record<string, unknown>;
export type ClearPackCacheResult = Record<string, unknown>;
/**
* A position within a QL file.
*/
export type Position = shared.Position;
/**
* The way of compiling the query, as a normal query
* or a subset of it. Note that precisely one of the two options should be set.
*/
export type CompilationTarget = shared.CompilationTarget;
export type QuickEvalOptions = shared.QuickEvalOptions;
export type WithProgressId<T> = shared.WithProgressId<T>;
export type ProgressMessage = shared.ProgressMessage;
/**
* Clear the cache of a dataset
*/
export const clearCache = new rpc.RequestType<WithProgressId<ClearCacheParams>, ClearCacheResult, void, void>('evaluation/clearCache');
/**
* Trim the cache of a dataset
*/
export const trimCache = new rpc.RequestType<WithProgressId<TrimCacheParams>, ClearCacheResult, void, void>('evaluation/trimCache');
/**
* Clear the pack cache
*/
export const clearPackCache = new rpc.RequestType<WithProgressId<ClearPackCacheParams>, ClearPackCacheResult, void, void>('evaluation/clearPackCache');
/**
* Run a query on a database
*/
export const runQuery = new rpc.RequestType<WithProgressId<RunQueryParams>, RunQueryResult, void, void>('evaluation/runQuery');
export const registerDatabases = new rpc.RequestType<
WithProgressId<RegisterDatabasesParams>,
RegisterDatabasesResult,
void,
void
>('evaluation/registerDatabases');
export const deregisterDatabases = new rpc.RequestType<
WithProgressId<DeregisterDatabasesParams>,
DeregisterDatabasesResult,
void,
void
>('evaluation/deregisterDatabases');
export const upgradeDatabase = new rpc.RequestType<
WithProgressId<UpgradeParams>,
UpgradeResult,
void,
void
>('evaluation/runUpgrade');
/**
* A notification that the progress has been changed.
*/
export const progress = shared.progress;

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