Compare commits

...

572 Commits

Author SHA1 Message Date
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
Andrew Eisenberg
b7b5a6ec30 v1.3.7
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-11-24 14:02:25 -08:00
Andrew Eisenberg
da9576fee0 Add workflow_dispatch to release workflow 2020-11-23 17:56:36 -08:00
Andrew Eisenberg
579df25be4 Use the ast edge label when building the ast node label
The C PrintAST library now includes the edge name in the AST Viewer
tree.
2020-11-23 15:27:24 -08:00
Andrew Eisenberg
1886c0c9ec Add a setting to control page size
Also, set a max and min value on the input control of the page. This
prevents going to a negative page, or a page after the last one.
2020-11-21 09:45:52 -08:00
Andrew Eisenberg
f48176bebf Re-sort databases list after db rename 2020-11-20 15:12:47 -08:00
Andrew Eisenberg
83f64fbdcd Avoid dependabot error 2020-11-19 14:02:21 -08:00
Andrew Eisenberg
a7bf5e60f3 Add debug flag for query server
And separate flag for IDE server. Setting these flags to `true` will
start the respective Java processes in debug mode so that they can
be attached to a debugger.
2020-11-18 15:55:24 -08:00
Andrew Eisenberg
e0cd041d98 Clean databases folder on startup (#675)
Cleans orphan databases on startup. This commit also bumps the fs-extra
dependency to get readdir with dirent objects.

Adds the `asyncFilter` to filter arrays asynchronously.
2020-11-16 16:32:05 +00:00
Andrew Eisenberg
4f76e9da60 Use the value not the label for the print ast node
Fixes #659
2020-11-13 09:45:30 -08:00
Andrew Eisenberg
966cc5af92 Add more structured output for tests
The diff and the errors were always available, but they were not being
sent to the output.

Additionally, make sure to send output to both the test explorer log and
the codeql test log.
2020-11-09 14:31:02 -08:00
Andrew Eisenberg
f4998d90e7 Creates an empty .expected file when running test output compare
If the expected file does not already exists. This helps with test
creation and allows users to create tests more quickly.
2020-11-09 14:27:20 -08:00
Andrew Eisenberg
245496c854 Remove QLPackDiscovery
We no longer rely on qlpacks for our ql test structure. For this reason,
we no longer need to do qlpack discovery.
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
d553f6c069 Restructure the tree in the Test Explorer View
With this change we display the tree based on the file system not based
on ql-packs. We also merge test folders whose only child is another
test folder.

Resolves #595
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
afd0694111 Update changelog 2020-11-09 11:53:42 -08:00
Andrew Eisenberg
32db9cdec6 Open editor containing query location in non-preview mode 2020-11-05 10:32:58 -08:00
github-actions[bot]
ad3cd7e7ac Bump version to v1.3.7 (#672)
Co-authored-by: aeisenberg <aeisenberg@users.noreply.github.com>
2020-11-04 14:09:37 -08:00
Andrew Eisenberg
e719c68321 Update the contributing docs
Just adds some more details.
2020-11-04 12:47:38 -08:00
Andrew Eisenberg
ce3b4ed43d v1.3.6 (#671)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-11-04 12:06:05 -08:00
Andrew Eisenberg
2953c15e5e Avoid recursive selection changes in ast viewer
This will prevent selections jumping around when an ast entry is
selected and its child has the same source location as the current
selection.
2020-11-04 07:14:39 -08:00
Andrew Eisenberg
b2b1021207 Disable codeql test commands from the command palette
These commands are not applicable from the global context. They require
an argument to be passed in. So, they should be hidden in the command
palette.
2020-11-03 15:52:00 -08:00
Andrew Eisenberg
9ddfd58a2b Adds interface-types and result-keys to pure
Will ensure that these files never have vscode dependencies.
2020-11-03 12:56:52 -08:00
Andrew Eisenberg
fe1476f875 Ensure uris are using encoded strings (#653)
This fixes a bug where if there are special characters in a database
path, it is not possible to navigate to that file from the results view.

Note that the results from our BQRS returned properly encoded URIs, but
our paths coming from sarif were unencoded. Our path parsing handled
the latter correctly (even though these are not correct URIs) and the
former incorrectly.

The fix here is to first ensure all uris are properly encoded. We do
this by running `encodeURI` in sarif-utils (can't run encodeURIComponent
or else the path separators `/` will also be encoded).

Then, we ensure that when we resolve locations, we decode all file
paths.

This works in all cases I have tried. I still have an issue with running
View AST on some of these databases, but that I believe is a separate
issue.
2020-11-03 18:06:44 +00:00
alexet
067a87a07c Results View: Fix display of booleans 2020-11-03 08:09:53 -05:00
Andrew Eisenberg
5133ee713f Add the assert-pure query
This query ensures that all of our files marked as "pure" remain that
way. In this case "pure" means that it does not depend on vscode and
can therefore be run in tests outside of a runtime environment.

This commit also explicitly moves all of our "pure" files to the
`src/pure` directory.
2020-11-02 18:40:45 -08:00
aeisenberg
2ac7881cf2 Bump version to v1.3.6 2020-10-27 12:56:22 -07:00
Andrew Eisenberg
5e8773b2b0 Prepare for release v1.3.5
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-10-27 12:37:06 -07:00
Andrew Eisenberg
2ac44b188c Ensure decoded archive fs paths are never empty (#648)
Empty paths should be replaced as '/'. This is a fix for a bug
introduced in 899f988df8.
2020-10-27 18:38:31 +00:00
Andrew Eisenberg
ef5d7bf684 Add version info to cli
And also only add the `--kind=DIL` to `generateDil` if version is
>= 2.3.0.
2020-10-26 09:03:43 -07:00
Andrew Eisenberg
ec98a577a2 Bump version of create-pull-request action
New version will avoid deprecation warnings for add-path
and set-var.
2020-10-22 14:17:53 -07:00
github-actions[bot]
ea9f8d494c Bump version to v1.3.5 2020-10-22 10:55:20 -07:00
Andrew Eisenberg
7cfaeddbc0 Prepare for release v1.3.4
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-10-22 10:45:18 -07:00
Andrew Eisenberg
093646c8a3 Remove the --kind dil options from decompile command
This option is not recognized.
2020-10-22 10:28:43 -07:00
Andrew Eisenberg
d8ab85748f Add some config listener tests
This also renames the config listeners for more consistency.
2020-10-21 15:48:35 -07:00
Andrew Eisenberg
1a5deab711 Remove unnecessary preLaunchTask 2020-10-21 15:48:35 -07:00
Andrew Eisenberg
68fe3bfbef Use the codeQL.runningTests.numberOfThreads
This setting has existed for a while, but it was not used for some
reason.
2020-10-21 15:48:35 -07:00
Andrew Eisenberg
899f988df8 Fix other locations where we create an invalid codeql-zip-archive uri
Also, create a convenience function for generating a codeql-zip-archive
at the root of the archive.
2020-10-21 14:23:51 -07:00
Andrew Eisenberg
9547aa3851 Remove feature flag for the AST Viewer 2020-10-21 14:07:05 -07:00
Andrew Eisenberg
e7e8ebab98 Fix flaky tests
These tests make API calls and may need extra time to complete.
2020-10-21 10:40:30 -07:00
Andrew Eisenberg
5b6371fb94 Fix archive encoding when there is an empty uri authority
This commit fixes a bug uncovered by
c66fe07b06.

The findSourceArchive function in databases.ts creates a
codeql-zip-archive uri with an empty authority component. This will
fail to decode. Until recently, this situation never happened. But in
the commit linked above, we start decoding some of these incorrectly
encoded uris.

This commit fixes that issue.
2020-10-21 08:07:48 -07:00
Andrew Eisenberg
542bb85490 Avoid running workflows on all push events
Only run on push events to main. This ensures that our main branch
is always passing.
2020-10-20 12:48:46 -07:00
Andrew Eisenberg
c66fe07b06 Return undefined for finding file ranges on empty URI
Also, refactor resolveSourceFile to make it easier to read.
And add unit tests for resolveSourceFile.

This commit fixes a bug in resolveSourceFile where the 
`pathWithinSourceArchive` was being removed and appended to the
`sourceArchiveZipPath`. In normal situations, we don't hit this bug
because most database source archive uris have an empty path for the
`pathWithinSourceArchive`.
2020-10-19 07:10:12 -07:00
Andrew Eisenberg
fe219e05d8 Refactor extension context subscriptions
Use DiposableObject more consistently and ensure all commands are
added as a disposable to the ExtensionContext.
2020-10-13 13:54:04 -07:00
Andrew Eisenberg
2dcf3b3feb Fix whitespace in CONTRIBUTING.md
Co-authored-by: jcreedcmu <jcreed@gmail.com>
2020-10-13 11:35:40 -07:00
Andrew Eisenberg
50efdea9d6 Remove build before launch in launch.json
And update contributing with new instructions.
2020-10-13 11:35:40 -07:00
Andrew Eisenberg
9300c07d42 Add command to view the DIL of a query 2020-10-13 11:07:43 -07:00
Andrew Eisenberg
8e817ee01a Refactor the commandRunner
Split commandRunner into two functions: commandRunner and
commandRunnerWithProgress.

Also, take advantage of default arguments for ProgressOptions.

And updates changelog.
2020-10-09 10:48:44 -07:00
Andrew Eisenberg
e5d439ae89 Update changelog
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-10-09 10:48:44 -07:00
Andrew Eisenberg
2c75a5c8cb Ensure database upgrade request happens only once
When a user runs multiple queries on a non-upgraded database, ensure
that only one dialog appears for upgrade.

This commit also migrates the upgrades.ts file to using the passed-in
cancellation token and progress monitor. This ensures that cancelling
a database upgrade command will also cancel out of any wrapper
operations.

Fixes #534
2020-10-09 10:48:44 -07:00
Andrew Eisenberg
7f472ac100 Add the commandRunner
The commandRunner wraps all vscode command registrations. It provides
uniform error handling and an optional progress monitor.

In general, progress monitors should only be created by the
commandRunner and passed through to the locations that use it.
2020-10-09 10:48:44 -07:00
Andrew Eisenberg
43d5ee78ea Add comments to interfaces.ts and databases-ui.ts
Also, small refactoring of the vscodeMessageHandler.
2020-10-08 12:32:27 -07:00
Andrew Eisenberg
54fee0bed8 Add unit tests for event emitting in database manager 2020-10-08 12:32:27 -07:00
Andrew Eisenberg
6bc720468c Update changelog 2020-10-08 12:32:27 -07:00
Andrew Eisenberg
7961816906 Only clear problems view when a database is removed
This commit adds DatabaseChangedEvent and ensures that all events
fired by the DatabaseManager includes one of these kinds.

Currently, the only kind that we care about is `Remove`. We ensure that
the problems view is only cleared on Remove events.
2020-10-08 12:32:27 -07:00
Andrew Eisenberg
672b20d4aa Clear problems view when a database is removed
This commit fixes the problem whereby a database is removed and the
problems associated with queries run from that database stick around
in the problems view.

Also, once problems are cleared, we need to make sure that we uncheck
the checkbox in the results view.

This commit has several limitations:

1. There is duplicated code for message handling in both results.tsx and
result-tables.tsx.
2. Problems are cleared whenever there is *any* change to any database.
Ideally we should only clear problems when a database is removed and
only problems associated with that database. I'll fix part of this in
a future commit.

Resolves #525
2020-10-08 12:32:27 -07:00
Andrew Eisenberg
c83d1b305e Rename AstItem -> ChildAstItem and RootAstItem -> AstItem
This simplifies some of our type conversions since all ChildAstItem
are AstItem.
2020-10-07 17:48:40 -07:00
Andrew Eisenberg
732eb83d07 Select the appropriate node in the AST viewer when the editor text selection changes
When a user clicks in an editor that whose source tree is currently being displayed in
the ast viewer, the viewer selection will stay in sync with the editor selection.
2020-10-07 17:48:40 -07:00
Andrew Eisenberg
7e5d5922db Update changelog 2020-10-06 08:42:29 -07:00
Andrew Eisenberg
15f38c6f18 Add icons for various query history view commands
And show these commands in the title bar.
2020-10-06 08:42:29 -07:00
Andrew Eisenberg
4adbfa4e81 Update changelog 2020-10-06 08:42:29 -07:00
Andrew Eisenberg
7c10d72117 Adds a message that appears in an empty databases view
Also, fixes a regex.
2020-10-06 08:42:29 -07:00
Andrew Eisenberg
7800c68065 Allow setting number of threads to 0
Fixes #603
2020-10-05 07:54:42 -07:00
Andrew Eisenberg
c4d9eed734 Update error message when there is a missing contextual query
References #476
2020-10-01 14:15:07 -07:00
Andrew Eisenberg
c34c9fae6a Avoid using path.join for sarif uris
These are uris, not paths and always use '/', even on windows.
2020-10-01 07:44:47 -07:00
Andrew Eisenberg
03f1e4ef08 Update changelog 2020-10-01 07:44:47 -07:00
Andrew Eisenberg
06b6a4705a Ensure backslashes are properly escaped in sarif messages
Problem was that we were not globally replaceing `\\` with `\`.

Also, this PR adds some new tests to sarif-utils.ts. In doing so, we
have fixed a small bug in getPathRelativeToSourceLocationPrefix.

Previously, we were uri decoding the sarifRelativeUri. However, this is
no longer correct because the result is another URI and it should
remain encoded if it originally was.

Resolves #585
2020-10-01 07:44:47 -07:00
Andrew Eisenberg
7ca456d6a0 Ensure all fields have labels
Never show an empty string in the results view. This fixes #535 (again).
2020-09-30 11:05:20 -07:00
Jason Reed
5244a1c3b0 Actually refresh the treeview when updating SARIF context value 2020-09-29 15:04:00 -07:00
Jason Reed
f4775954b6 Fix #597. 2020-09-29 15:04:00 -07:00
Andrew Eisenberg
7c48c5f887 Update changelog 2020-09-29 15:03:15 -07:00
Andrew Eisenberg
3e3a31d5e2 Add unit tests for query-results.ts 2020-09-29 15:03:15 -07:00
Andrew Eisenberg
72160a24bd Fix incorrect call to bqrs info when retrieving paginated results
When retrieving paginated results, need to make sure we are getting
page offsets from the correct results file.

Previously, we were incorrectly extracting page offsets from the default
(unsorted) file. With this change, we ensure that we get offsets from
the proper results file when there is a request for a page of results.
2020-09-29 15:03:15 -07:00
Andrew Eisenberg
456c25f617 Fix invalid sort after reloading query results
With this change, we use the stored sort order if it exists
after reloading a query results page.
2020-09-29 15:03:15 -07:00
Andrew Eisenberg
0c571b1942 Fix mocha test running (#600)
PR #591 broke the build.
2020-09-29 16:34:51 -04:00
Andrew Eisenberg
7e4491ac45 Update mocha version (#591)
This fixes a dependabot error on yargs-parser
2020-09-29 09:04:46 -04:00
Andrew Eisenberg
75b5c1d316 Allow max queries to be configurable
Max number of simultaneous queries launchable by runQueries command is
now configurable by codeQL.runningQueries.maxQueries.
2020-09-25 07:54:41 -07:00
Andrew Eisenberg
db6fc5d7f0 Refactor: Change renderLocation in webview
* It is now more general and the logic is simplified
* Also, add more comments
* Rename `adaptBqrs` to `transformBqrsResultSet`
* Remove a react error for missing a key attribute in a list
2020-09-24 07:54:50 -07:00
Andrew Eisenberg
84028434e0 Refactor: Remove duplicated BQRS types
This refactoring combines the types in `bqrs-types.ts` and
`bqrs-cli-types.ts`. Historically, the former was used for BQRS files
parsed by the extension and the latter for BQRS files parsed by the cli.
They describe the same file types, but using different property and type
names.

We have moved to parsing all BQRS files by the cli. This refactoring
removes the `bqrs-types.ts` file and replaces all BQRS references to
use types in `bqrs-cli-types.ts`.

Additionally, the `adapt.ts` file has been deleted since its purpose
was to convert between extension and cli BQRS types. Some one type and
one function from `adapt.ts` has been moved from `adapt.ts` to
`bqrs-types.ts`. It's possible that we want to do a further refactoring
to simply remove them both.
2020-09-24 07:54:50 -07:00
github-actions[bot]
b917a204ba Bump version to v1.3.4 (#580)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-09-16 16:35:21 -04:00
jcreedcmu
8a5514c696 Date CHANGELOG for release (#579)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-09-16 16:22:01 -04:00
Andrew Eisenberg
29f92575ee Update npm dependencies fix security vulns
* node-fetch
* bl
2020-09-16 07:57:31 -07:00
Aditya Sharad
5d63431b8c Actions: Pin version of upload-artifact action (#549) 2020-09-08 16:25:18 -07:00
Andrew Eisenberg
17eee86765 Update changelog 2020-09-02 08:16:27 -07:00
Andrew Eisenberg
95d5274fd4 Avoid showing a link when the underlying path is empty
A common situation when a file is not relevant for a particular result
is to return an empty file path location.

Currently, we are displaying this situation as a hyperlink in the
results, but when clicking on the link, there is an error.

To mirror the behaviour of Eclipse, we should avoid showing a link here.
This commit changes that behaviour.
2020-09-02 08:16:27 -07:00
Dave Bartolomeo
959552544a Fix highlighting after disembodied IPA branch
Fixes #543
```ql
newtype TA = TB()

private predicate foo() { any() }
```
Our TextMate grammar didn't realize that the newtype declaration ended after the closing paren of the branch's parameter list, so the `private` modifier was highlighted incorrectly.

It's surprisingly tricky to get TextMate to handle this correctly, so I wound up just treating the IPA declaration head (`newtype TA`), the branch head (`= TB`), the branch parameter list, and the branch body as directly children of the module body. This is kind of hacky, but it does fix the bug without introducing any new cases where we have incorrect highlighting of valid code.
2020-09-01 22:31:16 -07:00
Andrew Eisenberg
16fab7f45d Fix typo 2020-08-26 08:27:11 -07:00
Andrew Eisenberg
cb03da3716 Avoid running query when a user cancels when there are unsaved changes
Fixes #538

Adds a new menu item to cancel a query run when the query is unsaved.

Also, ensures that no warning message is sent to the console.
2020-08-25 07:43:52 -07:00
Andrew Eisenberg
f968f8e2f5 Add a top-level tsconfig.json
The reason to add this is that I am getting misleadings errors in
vscode that this file is missing. By adding this file, I no longer
see these errors.
2020-08-24 10:58:17 -07:00
jcreedcmu
c247292181 Merge pull request #537 from jcreedcmu/jcreed/fix-paginated-sorting
Fix changing page forgetting about sorting
2020-08-14 09:47:46 -04:00
Jason Reed
518e6c14cc Add changelog entry 2020-08-14 08:09:28 -04:00
Jason Reed
37cf525c8e Fix changing page forgetting about sorting 2020-08-14 08:06:31 -04:00
jcreedcmu
1f4e69940d Merge pull request #536 from jcreedcmu/jcreed/fix-none
Fix #535
2020-08-13 11:04:09 -04:00
Jason Reed
72878fb6fd Pass up empty string at this stage 2020-08-13 09:43:04 -04:00
Jason Reed
6b343b4581 Add changelog entry 2020-08-13 08:23:02 -04:00
Jason Reed
b191f68599 Fix #535. 2020-08-13 08:19:55 -04:00
Andrew Eisenberg
ef84d8d362 Update changelog after release
Add a simple perl script that will augment the CHANGELOG with
an [UNRELEASED] section when creating the PR after a release.
2020-08-12 11:33:18 -07:00
github-actions[bot]
ef55d9d4e0 Bump version to v1.3.3 2020-08-12 10:43:21 -07:00
Andrew Eisenberg
ff841950ae Update Chnagelog for v1.3.2
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-08-12 10:35:35 -07:00
Andrew Eisenberg
aaf9e1fb9c Update changelog 2020-08-12 10:35:35 -07:00
jcreedcmu
7f885755c2 Merge pull request #529 from jcreedcmu/jcreed/fix-527
Fix sorting of raw results
2020-08-12 12:31:39 -04:00
Jason Reed
8c55e3ef2d Simplify argument passing 2020-08-12 12:25:20 -04:00
Jason Reed
039343efa2 Fix #527. 2020-08-12 12:10:02 -04:00
Jason Reed
d0982f34a4 Defunctionalize updating sort state
This leads to less sharing of codepaths which is a little bad (slightly more
repetition and rendundancy) but a lot good (can independently fix the way
raw results are redisplayed so as to be actually correct).
2020-08-12 12:10:02 -04:00
jcreedcmu
890821b273 Merge pull request #528 from aeisenberg/aeisenberg/ast-changelog
Update changelog to include line about experimental AST Viewer
2020-08-12 11:22:33 -04:00
Andrew Eisenberg
84e2cf7986 Update changelog to include line about experimental AST Viewer 2020-08-12 07:37:08 -07:00
Andrew Eisenberg
648bf4b629 Add a debug flag to allow remote debugging (#524)
With this flag on, it is possible to remote-debug the language server in a java debugger.
2020-08-06 11:08:26 -07:00
Henning Makholm
8ccb7c4fa4 Merge pull request #522 from github/shati-patel-patch-1
Update pull_request_template.md
2020-07-31 21:31:31 +02:00
Shati Patel
73fc37d370 Update pull_request_template.md
The team has been renamed 🙂
2020-07-31 20:27:28 +01:00
Aditya Sharad
0a3d4095b7 Merge pull request #521 from adityasharad/actions/label-issue
Actions: Autolabel issues when opened
2020-07-31 09:40:41 -07:00
Aditya Sharad
32d4deb575 Update label-issue.yml 2020-07-31 08:57:33 -07:00
Aditya Sharad
d2409054e2 Actions: Autolabel issues when opened 2020-07-30 16:59:07 -07:00
jcreedcmu
6ae5cd3ac3 Merge pull request #519 from aeisenberg/aeisenberg/remove-from-changelog
Remove unreleased feature from changelog
2020-07-27 13:05:09 -04:00
Andrew Eisenberg
0dfc64c7e8 Remove unreleased feature from changelog 2020-07-27 09:53:31 -07:00
Andrew Eisenberg
6a9c9a1eb4 Add catch handler for discovery failures
Display a reasonable message to users if there is a failure.
2020-07-27 08:34:03 -07:00
Andrew Eisenberg
f62cce32da Change how we check for relevant ql packs 2020-07-27 08:34:03 -07:00
Andrew Eisenberg
a36ff8ca1e Update changelog 2020-07-27 08:34:03 -07:00
Andrew Eisenberg
0d1199bb64 Filters qltest-discovery
qlpack tests that are not contained within the current workspace folder
will be filtered from the test runner view.

This also fixes a test that should have been failing but wasn't.
2020-07-27 08:34:03 -07:00
jcreedcmu
3edd8ec1d1 Merge pull request #516 from aeisenberg/aeisenberg/refactor-contextual
Refactor contextual queries
2020-07-24 08:49:37 -04:00
jcreedcmu
4a030dc2f4 Merge pull request #514 from aeisenberg/aeisenberg/fix-ast-viewer-0-id
Fix AST viewer bug where nodes with id=0 did not have children
2020-07-24 08:47:55 -04:00
jcreedcmu
a4f19c9b5d Merge pull request #515 from aeisenberg/aeisenberg/launch-no-npx
Remove reference to npx in luanch config
2020-07-24 08:45:14 -04:00
Andrew Eisenberg
353a87de12 Refactor contextual queries
Break the  file into logically contained
smaller files. And add unit tests for .
2020-07-23 15:00:04 -07:00
Andrew Eisenberg
a2cda79ceb Remove reference to npx in luanch config
Users should not need to install npx in order to launch
the extension.
2020-07-23 12:45:08 -07:00
Andrew Eisenberg
bc73712987 Fix AST viewer bug where nodes with id=0 did not have children 2020-07-23 12:43:11 -07:00
Jason Reed
09c4e7e99b Fix broken launch config
We need to provide the `--extensionDevelopmentPath` flag in these
launch configurations.

It appears to be unnecessary to include
`${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js`
in addition to the strictly more general pattern
${workspaceRoot}/extensions/ql-vscode/out/**/*.js

An unfortunate UI fact appears to be that the log of the gulp build is
focused whenever these tasks are run, even though the log you actually
care about seeing is in the `Debug Console` section. Not sure how to
fix that.
2020-07-23 12:40:29 -07:00
jcreedcmu
d0e0ad619b Merge pull request #511 from ceh-forks/ceh-skip-log
Suppress database downloaded message when action canceled
2020-07-23 14:02:24 -04:00
Emil Hessman
e4ff8d1fa8 Only focus database panel on successful download 2020-07-23 19:44:36 +02:00
Andrew Eisenberg
9052851f9a Run CodeQL Action on PRs 2020-07-23 10:25:16 -07:00
jcreedcmu
a946965331 Merge pull request #508 from jcreedcmu/jcreed/untangle3
Fix documentation for current build process
2020-07-23 09:43:40 -04:00
Andrew Eisenberg
10177412f6 Merge pull request #492 from aeisenberg/aeisenberg/ast-viewer
Add the AST Viewer
2020-07-23 06:36:11 -07:00
jcreedcmu
4519e0f951 Update CONTRIBUTING.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-07-23 09:14:37 -04:00
Emil Hessman
0d2b44cdba Suppress database downloaded message when action canceled 2020-07-23 06:40:43 +02:00
Andrew Eisenberg
0045891f9d Clean up ast builder code 2020-07-22 13:34:01 -07:00
Jason Reed
2b712827df Clean up build instructions 2020-07-22 13:05:13 -04:00
Andrew Eisenberg
65b5b68df6 Remove duplicate changelog line 2020-07-21 12:28:50 -07:00
Andrew Eisenberg
f21296e4f6 Merge branch 'aeisenberg/ast-viewer' of github.com:aeisenberg/vscode-codeql into aeisenberg/ast-viewer 2020-07-21 10:10:23 -07:00
Jason Reed
762edd137c Fix CONTRIBUTING.md to reflect changes to build process. 2020-07-21 13:09:44 -04:00
jcreedcmu
b3dc7d75a8 Merge pull request #503 from jcreedcmu/jcreed/untangle2
Try moving build to just gulp
2020-07-21 12:56:34 -04:00
Jason Reed
9ad0bf6f43 Call into package.json scripts from actions workflow 2020-07-21 12:20:17 -04:00
Jason Reed
f8804f946c Use explicit path for vsce 2020-07-21 12:19:03 -04:00
Jason Reed
3c07be5f74 Move type dependency to devDependencies 2020-07-21 12:13:01 -04:00
Jason Reed
cd329eeaeb Fix source maps 2020-07-21 10:09:42 -04:00
Jason Reed
2671414f32 Extract rush from vscode tasks 2020-07-21 09:21:11 -04:00
Andrew Eisenberg
b6bd534857 Fixes pagination when there are no results
When there are no results, always ensure that max pages is 1.

This commit also changes the way pagination buttons are displayed,
removing their border.
2020-07-20 07:11:56 -07:00
Andrew Eisenberg
8093d9a529 Check window event origins
Fixes codescanning warnings:

- https://github.com/github/vscode-codeql/security/code-scanning/1
- https://github.com/github/vscode-codeql/security/code-scanning/2
2020-07-17 10:25:25 -07:00
jcreedcmu
aebab082c2 Merge branch 'main' into aeisenberg/ast-viewer 2020-07-17 10:53:15 -04:00
Andrew Eisenberg
36d612e5b0 Add feature flag for ast viewer
Set `codeQL.experimentalAstViewer` to true in settings
in order for component to be enabled.
2020-07-16 15:42:26 -07:00
Andrew Eisenberg
8459edb57c Fix tests and reformatting
* Fix command-linting tests.
* Fix failing windows test and Use Uri.parse(_, true)
* Use  Uri.parse(_, true). That is the preferred API.
* Reformat comments.
2020-07-16 14:42:48 -07:00
Andrew Eisenberg
af965c941a Update changelog 2020-07-16 14:42:48 -07:00
Andrew Eisenberg
eaa26e5ef7 Add the AST Viewer
This commit adds the AST Viewer for viewing the QL AST of a file in a
database.

The different components are as follows:

1. There is a new view `codeQLAstViewer`, which displays the AST
2. This view is backed by the `AstViewerDataProvider` and `AstViewer` classes in astView.ts
3. To generate an AST, we use contextual queries, similar to how Find references/declarations are implemented. In particular, in `definitions.ts` there is `TemplatePrintAstProvider` which provides an AST for a given source buffer.
  - Similar to the other queries, we first determine which database the buffer belongs to.
  - Based on that, we generate a synthetic qlpack and run the templatized `printAst.ql` query
  - We plug in the archive-relative path name of the source file.
  - After the query is run, we wrap the results in an `AstBuilder` instance.
  - When requested, the `AstBuilder` will generate the full AST of the file from the BQRS results.
  - The AST roots (all top-level elements, functions, variable declarations, etc, are roots) are passed to the `AstViewer` instance, which handles the display lifecycle and other VS Code-specific functions.

There are a few unrelated pieces here, which can be pulled out to another PR if required:

- The `codeQLQueryHistory` view now has a _welcome_ message to make it more obvious to users how to start.
- `definitions.ts` is moved to the `contextual` subfolder.
- `fileRangeFromURI` is extracted from `definitions.ts` to its own file so it can be reused.

Also, note that this relies on https://github.com/github/codeql/pull/3931 for the C/C++ query to be available in the QL sources. Other languages will need similar queries.
2020-07-16 14:42:47 -07:00
Andrew Eisenberg
546ec2eb1c Update changelog 2020-07-16 09:10:05 -07:00
Andrew Eisenberg
565ea0d8a0 Use proper check for existence of search path
Fixes #499
2020-07-16 09:10:05 -07:00
Jason Reed
258f43132c Relax version constraints in package.json 2020-07-16 09:19:07 -04:00
Jason Reed
b7a72b9d21 Remove now unused rush configuration 2020-07-16 09:10:53 -04:00
Jason Reed
d2138907b9 Fix test section of workflow file 2020-07-16 08:51:35 -04:00
Jason Reed
bce3413158 Run npm-installed copy of vsce 2020-07-16 08:49:47 -04:00
Jason Reed
2b53396146 Fix warning 2020-07-16 08:49:07 -04:00
Jason Reed
19a76dcbee Update action to not depend on rush 2020-07-16 08:43:07 -04:00
Jason Reed
56b62ff758 Fix package deploy to not depend on rush 2020-07-16 08:39:17 -04:00
Jason Reed
9083c5d649 Reconcile vscode-engine and api versions 2020-07-16 08:00:37 -04:00
Jason Reed
49c0d39a50 Replace javascript gulpfile with typescript 2020-07-14 13:51:49 -04:00
jcreedcmu
57ea215639 Merge pull request #496 from jcreedcmu/jcreed/untangle
Reduce dependencies on internal modules
2020-07-14 13:03:03 -04:00
Jason Reed
528cbc8d49 Move more config into local typescript gulpfile 2020-07-14 12:52:06 -04:00
Jason Reed
2c5b672c81 Make stub typescript gulpfile 2020-07-14 12:11:54 -04:00
Jason Reed
f0055910c1 Remove typescript-config package 2020-07-14 12:02:51 -04:00
Jason Reed
657df5e07d inline tsconfig inheritance 2020-07-14 11:54:34 -04:00
Jason Reed
53d5c2438a Remove now unused library. 2020-07-14 08:19:45 -04:00
Jason Reed
ac941eb9dd Copy semmle-vscode-utils into extension. 2020-07-14 08:17:30 -04:00
Jason Reed
e5e854822d Remove now-unused libraries. 2020-07-14 08:00:11 -04:00
Jason Reed
868b356588 Sharpen comment slightly. 2020-07-14 07:46:43 -04:00
Jason Reed
2dd841e667 Pacify lint.
Apparently the linter wants a tsconfig file to be able to lint the
compare view typescript. I made the configFile specification in the
webpack.config.ts more specific so that we use the same config
every time during webview build.
2020-07-13 13:04:22 -04:00
Jason Reed
609fea404d Remove extension dependency on semmle-io-node 2020-07-13 12:59:13 -04:00
Jason Reed
24da63fbfa Remove extension dependency on semmle-bqrs 2020-07-13 12:48:55 -04:00
Jason Reed
10156b1f49 Remove semmle-bqrs dependency from test. 2020-07-13 12:46:17 -04:00
Jason Reed
3694fdaecb Make tsconfig.json selection during webpack deterministic.
Without this `configFile` option, ts-loader apparently does not
guarantee a deterministic choice of which of the three `tsconfig.json`
files below `extensions/ql-vscode` actually gets used during webpack.
This leads to very strange behavior as even removing dead code can
change which `tsconfig.json` 'wins the race'. I observed that removing
a dependence on `semmle-bqrs` from `src/view` *tended* to make
`ts-loader` choose `src/compare/view/tsconfig.json` instead.
2020-07-13 12:39:37 -04:00
Jason Reed
4c30374dc3 Extract tryGetResolvableLocation from semmle-bqrs 2020-07-13 11:01:11 -04:00
Jason Reed
26d83b5cef Reduce dependencies on semmle-bqrs.
Eliminate references to types in library semmle-bqrs in favor of a
local copy of those same types in bqrs-types.ts.
2020-07-13 10:56:11 -04:00
Andrew Eisenberg
3639dcb806 Fix tests and reformatting
* Fix command-linting tests.
* Fix failing windows test and Use Uri.parse(_, true)
* Use  Uri.parse(_, true). That is the preferred API.
* Reformat comments.
2020-07-10 08:17:11 -07:00
Andrew Eisenberg
4aa752135d Update changelog 2020-07-10 08:16:40 -07:00
Andrew Eisenberg
80c6ea6eac Add the AST Viewer
This commit adds the AST Viewer for viewing the QL AST of a file in a
database.

The different components are as follows:

1. There is a new view `codeQLAstViewer`, which displays the AST
2. This view is backed by the `AstViewerDataProvider` and `AstViewer` classes in astView.ts
3. To generate an AST, we use contextual queries, similar to how Find references/declarations are implemented. In particular, in `definitions.ts` there is `TemplatePrintAstProvider` which provides an AST for a given source buffer.
  - Similar to the other queries, we first determine which database the buffer belongs to.
  - Based on that, we generate a synthetic qlpack and run the templatized `printAst.ql` query
  - We plug in the archive-relative path name of the source file.
  - After the query is run, we wrap the results in an `AstBuilder` instance.
  - When requested, the `AstBuilder` will generate the full AST of the file from the BQRS results.
  - The AST roots (all top-level elements, functions, variable declarations, etc, are roots) are passed to the `AstViewer` instance, which handles the display lifecycle and other VS Code-specific functions.

There are a few unrelated pieces here, which can be pulled out to another PR if required:

- The `codeQLQueryHistory` view now has a _welcome_ message to make it more obvious to users how to start.
- `definitions.ts` is moved to the `contextual` subfolder.
- `fileRangeFromURI` is extracted from `definitions.ts` to its own file so it can be reused.

Also, note that this relies on https://github.com/github/codeql/pull/3931 for the C/C++ query to be available in the QL sources. Other languages will need similar queries.
2020-07-10 08:16:40 -07:00
jcreedcmu
2243c21afc Merge pull request #494 from jcreedcmu/jcreed/fix-integration-tests
Remove failing integration test
2020-07-10 11:06:41 -04:00
Jason Reed
46bddcd8fa Remove dead code and associated test. 2020-07-10 09:11:08 -04:00
Jason Reed
df5dccc3f6 'Pin' to stable instead 2020-07-10 08:59:23 -04:00
Jason Reed
3207c594e7 Pin to vscode version for integration testing 2020-07-09 18:39:19 -04:00
jcreedcmu
70de59eabd Merge pull request #491 from jcreedcmu/jcreed/cleanup
Remove pagination feature flag
2020-07-08 11:31:23 -04:00
Jason Reed
27dd804731 Fix display of offsets in raw results table 2020-07-08 08:56:42 -04:00
Jason Reed
240e0fbd4e Remove feature flag 2020-07-08 08:56:42 -04:00
Jason Reed
f65caa0d85 Remove ExtensionParsedResultSets type 2020-07-08 08:56:42 -04:00
Jason Reed
e7192eb423 Remove WebviewParsed branch from ParsedResultSets
Also remove dead code downstream from it.
2020-07-08 08:56:42 -04:00
jcreedcmu
06b51326a3 Merge pull request #490 from github/version/bump-to-v1.3.2
Bump version to v1.3.2
2020-07-07 14:44:42 -04:00
github-actions[bot]
82a6ef4844 Bump version to v1.3.2 2020-07-07 18:36:48 +00:00
jcreedcmu
379b69a0e9 Merge pull request #489 from jcreedcmu/jcreed/v1.3.1
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
update CHANGELOG for release
2020-07-07 14:34:28 -04:00
Jason Reed
c4353981fa update CHANGELOG for release 2020-07-07 14:28:53 -04:00
jcreedcmu
cc7fb39be7 Merge pull request #488 from jcreedcmu/jcreed/query-text
Fix display of quick-query query text
2020-07-07 11:57:07 -04:00
Jason Reed
d8266b7bc1 Update CHANGELOG. 2020-07-07 08:40:07 -04:00
Jason Reed
d50277380b Fix display of quick-query query text 2020-07-07 08:39:03 -04:00
Andrew Eisenberg
3e149e7bb3 Update changelog 2020-07-06 07:32:35 -07:00
Andrew Eisenberg
00e252d48a Change styling on pagination section 2020-07-06 07:32:35 -07:00
Andrew Eisenberg
6a2832fcc7 Update changelog 2020-07-06 07:21:12 -07:00
Andrew Eisenberg
a7d99cc7e2 Fix nested problem adding database starting with db-* 2020-07-06 07:21:12 -07:00
jcreedcmu
454e8471a4 Merge pull request #481 from jcreedcmu/jcreed/interpreted-pagination
Allow pagination for interpreted results
2020-07-06 07:53:06 -04:00
Jason Reed
e2d125a558 Fix broken raw queries. 2020-07-02 10:32:38 -04:00
Jason Reed
e345425051 Fix wrong number of pages shown on initial display. 2020-07-02 10:27:15 -04:00
Jason Reed
0b32961f6d Unbreak typing page number into field 2020-07-02 09:39:47 -04:00
Jason Reed
e0a58a86fc Inline now-trivial function paginationEnabled 2020-07-01 09:20:31 -04:00
Jason Reed
ec45db3bc3 Appease linter. 2020-06-30 11:13:10 -04:00
Jason Reed
94d230308c Allow switching to alerts table 2020-06-30 10:17:46 -04:00
Jason Reed
96688e3379 Fix listing of tables when in alerts view 2020-06-30 10:14:14 -04:00
Jason Reed
88c27618b1 Show number of results correctly 2020-06-30 09:54:13 -04:00
Jason Reed
11c538a99d Teach webview to do pagination in interpreted results 2020-06-30 09:39:04 -04:00
Jason Reed
0e3b7a8eb5 Also show pagination interface for interpreted results 2020-06-29 12:52:19 -04:00
Jason Reed
65aa6928e4 Show number of pages in pagination interface 2020-06-29 12:38:48 -04:00
Jason Reed
fe02a58e45 Teach extension to accept ShowInterpretedPageMsg 2020-06-29 12:36:09 -04:00
Jason Reed
4030ddbdc2 Teach extension how to request display of alerts page 2020-06-29 11:57:44 -04:00
Jason Reed
b3642bd62e Compute truncation functionally rather than imperative update 2020-06-29 11:44:17 -04:00
Jason Reed
addddb0095 Factor out truncation of results from interpretation itself 2020-06-29 11:10:32 -04:00
Jason Reed
d7732c4ed6 Add interpreted results page size 2020-06-29 11:02:30 -04:00
Andrew Eisenberg
6e34c03b05 Update changelog 2020-06-26 11:40:40 -07:00
Andrew Eisenberg
75518a5d01 Ensure source folders are zipped
Zips source folders of databases when they are added. Only if
the databases are fully controlled by VS Code.

Fixes #479
2020-06-26 11:40:40 -07:00
Andrew Eisenberg
4beead54be Fix file extension of generated query suite
See https://github.com/github/codeql-coreql-team/issues/452
2020-06-26 11:40:40 -07:00
Andrew Eisenberg
7379f4996a Add the zip-a-folder package 2020-06-26 11:40:40 -07:00
Andrew Eisenberg
c40b8fe1a5 Remove unused code path
`resolveRawDataset` can not be called.
2020-06-26 07:56:59 -07:00
Andrew Eisenberg
210bbcd2e9 Avoid using a synchronous file system command
Add the tmp-promise package to allow for async tmp file
operations.
2020-06-25 13:25:10 -07:00
jcreedcmu
461892759b Merge pull request #474 from shati-patel/edits
Small editorials tweaks
2020-06-24 07:41:57 -04:00
Shati Patel
6277e5cecb Small editorials tweaks 2020-06-24 11:33:48 +01:00
Andrew Eisenberg
42ebc3fbe6 Update changelog 2020-06-23 13:27:20 -07:00
Andrew Eisenberg
77b13bd8e3 Ensure query compare order matches expectation
A user typically expects that the first selection would be
the query that they are comparing _from_ and the second query
is being compared _to_.

This commit ensures that something like this expectation will
always hold.

So, when there are two queries selected, the first one selected
will always be _from_ and appear on the left side of the compare
view. The one selected later will be _to_ and appear on the right.

There is a corner case when there are 3 or more selected queries
and a user *unselects* a query. We do not track the selection
order of the remaining two queries.
2020-06-23 13:27:20 -07:00
Andrew Eisenberg
f4e983e214 Change command name 2020-06-23 09:58:07 -07:00
Andrew Eisenberg
60620a5618 Update changelog 2020-06-23 08:14:05 -07:00
Andrew Eisenberg
25bac72ac5 Use Open instead of Extract to open zip files
This allows opening zip files whose local headers are not correct.
2020-06-23 08:14:05 -07:00
github-actions[bot]
e7ee1f86a8 Bump version to v1.3.1 2020-06-22 16:07:21 -07:00
jcreedcmu
1f3decc83a Merge pull request #466 from jcreedcmu/jcreed/1.3.0
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
Bump version and CHANGELOG for release
2020-06-22 14:23:51 -04:00
Jason Reed
c2ebaa2422 Bump version and CHANGELOG for release 2020-06-22 14:20:09 -04:00
Andrew Eisenberg
6f46bcc459 Add better error message when comparing queries
Also, fix type error.
2020-06-22 11:19:39 -07:00
Andrew Eisenberg
6ae6e91195 Add maximum number of queries to run
Throw error if user tries to run more than that.
2020-06-22 09:08:12 -07:00
Andrew Eisenberg
fabef96f08 Update changelog 2020-06-22 09:08:12 -07:00
Andrew Eisenberg
3a23f05a0a Add command to run multiple queries at once from file explorer
New command called `codeQL.runQueries`.

When invoked, gather all selected files and folders, and recursively
search for ql files to run. Warn the user if a directory is selected.
See comment inline for reason.
2020-06-22 09:08:12 -07:00
Andrew Eisenberg
52c6ee4477 Update vscode settings
Recommend formatting and linting extension. Also, use 100 columns
for formatting.
2020-06-22 09:08:12 -07:00
Andrew Eisenberg
727d0db387 Update changelog 2020-06-18 09:55:43 -07:00
Andrew Eisenberg
86f10fa41f Add multi-select support for db view
These commands now work on multiple databases:
* remove databases
* open external folder
* upgrade databases
2020-06-18 09:55:43 -07:00
Andrew Eisenberg
3d44b987d7 Update changelog 2020-06-17 10:54:08 -07:00
Andrew Eisenberg
bd6a6ff40d Add Multi-select support to query history view
This is not quite ideal due to
https://github.com/microsoft/vscode/issues/99767

Allow multiselection in the query-history view. For commands
that shouldn't accept multiple options, show a user message
to that effect.

For remove query, allow multiple removals at once.

For compare query, allow selecting of exactly two queries.
Otherwise, throw an error. Also, verify that the selected queries
are compatible to compare.
2020-06-17 10:54:08 -07:00
jcreedcmu
dd44bf74e3 Merge pull request #452 from jcreedcmu/jcreed/master-to-main
Change references to master branch to main branch
2020-06-17 08:37:16 -04:00
Andrew Eisenberg
95988f0960 Update changelog 2020-06-16 12:08:33 -07:00
Andrew Eisenberg
ab41be243b Vertically align the two compare tables
Avoids an issue when one table has many more rows than the other and
the tables are off-centered.
2020-06-16 12:08:33 -07:00
Andrew Eisenberg
75fe8fb040 Display error message when can't compare queries
* The error message will be displayed instead of the empty results
  tables.
* Also, uncomment onEnterRules. That should never have been committed.
* Also, extract CompareTable to its own component.
2020-06-16 12:08:33 -07:00
Andrew Eisenberg
15d65b308c Re-apply formatting to all files 2020-06-16 12:08:33 -07:00
Andrew Eisenberg
9be355aa9d Add link to open query results from compare view 2020-06-16 12:08:33 -07:00
Andrew Eisenberg
b803a80d39 Add unit tests for interface-utils.ts
Also, some moving around of functions and whitespace changes.
2020-06-16 12:08:33 -07:00
Andrew Eisenberg
fceea64a08 More work on diffs 2020-06-16 12:08:33 -07:00
Andrew Eisenberg
e9fbd6d430 Add the compare-interface
This module will behave like the interface.ts module and handle
interactions between the compare webview and the extension.
2020-06-16 12:08:33 -07:00
Andrew Eisenberg
2ab4c1ac14 Extract shared components out of interface.ts
This is in preparation for creating a new webview, extract shared
functionality to the webview-utils file
2020-06-16 12:08:33 -07:00
Andrew Eisenberg
e38a34edce Introduce the skeleton compare view
Adds the tsx file and updates the webpack config so that this new tsx
file is properly compiled.
2020-06-16 12:08:33 -07:00
jcreedcmu
ed04ae9364 Merge pull request #451 from jcreedcmu/jcreed/tiny-promisify-fix
Remove unnecessary promisify.
2020-06-16 14:03:28 -04:00
Jason Reed
963ff9f458 Change references to master branch to main branch
I have already pushed `main` upstream pointing at the same commit as
master, so this PR should be safe to merge. To the best of my
knowledge all that's necessary after that is to change the
default branch
in https://github.com/github/vscode-codeql/settings/branches.
2020-06-16 14:00:02 -04:00
Jason Reed
dfb7a8fd54 Remove unnecessary promisify.
fs-extra already has an async overload for `readdir`.
2020-06-16 13:18:52 -04:00
Andrew Eisenberg
ff8e72a318 Run eslint --fix on the extension 2020-06-16 08:44:37 -07:00
Andrew Eisenberg
45dc2a29cf Auto-format on commit and use single quotes 2020-06-16 08:44:37 -07:00
Andrew Eisenberg
c7ee9fa8c7 Fail linting if there are any warnings 2020-06-16 08:44:37 -07:00
jcreedcmu
1f3707f74e Merge pull request #450 from jcreedcmu/jcreed/gitattributes
Use union merge strategy for CHANGELOG.md
2020-06-15 15:35:07 -04:00
Jason Reed
249ab78249 Use union merge strategy for CHANGELOG.md 2020-06-15 15:31:19 -04:00
Andrew Eisenberg
a6ed674816 Compare against title property
Co-authored-by: jcreedcmu <jcreed@gmail.com>
2020-06-15 12:22:01 -07:00
Andrew Eisenberg
3c6169fe23 Add unit tests for tryOpenExternalFile 2020-06-15 12:22:01 -07:00
Andrew Eisenberg
4bc17ed333 Update changelog 2020-06-15 12:22:01 -07:00
Andrew Eisenberg
39a1524ad1 Ask user to open sarif file externally when too large
Use the same mechanism that we are using for log files to open
large sarif files. This is because the extension is not
capable of opening large (>50MB) files due to vscode restrictions.
2020-06-15 12:22:01 -07:00
Andrew Eisenberg
081aab7acb Clarify comment on locale compare 2020-06-15 12:18:40 -07:00
Andrew Eisenberg
7440e0d779 Update changelog 2020-06-15 12:18:40 -07:00
Andrew Eisenberg
7fae9ee175 Explicitly use the workspace-configured locale
When the `env.language` value is not passed as the locale, OS-default
locale is used. This change ensures that we use the workspace locale
where we want and explicitly calls out where we should continue to use
the OS-default.
2020-06-15 12:18:40 -07:00
Andrew Eisenberg
058c89114a Update changelog 2020-06-15 12:11:42 -07:00
Andrew Eisenberg
4680614455 Add descriptive message for archive import failure 2020-06-15 12:11:42 -07:00
Andrew Eisenberg
d360153d69 Update changelog 2020-06-15 12:04:25 -07:00
Andrew Eisenberg
2baae8481a Catch and report error when selecting invalid database 2020-06-15 12:04:25 -07:00
Andrew Eisenberg
bba2f0217b Force undefined version if version command fails
The assumption is that the cli is old or corrupted.
In either case we want to upgrade.
2020-06-15 08:06:49 -07:00
Dave Bartolomeo
7898463a27 Merge pull request #432 from dbartol/dbartol/utilities
Make a few internal packages publishable
2020-06-10 10:35:20 -04:00
Dave Bartolomeo
07d9bdb5fa Fix file paths in launch.json 2020-06-09 10:18:10 -04:00
Dave Bartolomeo
7c38af29ff Publish utility packages 2020-06-09 10:17:50 -04:00
Dave Bartolomeo
e9397bbba2 Change notes 2020-06-09 09:58:25 -04:00
Dave Bartolomeo
aa232849fd Support packages from multiple registries 2020-06-09 09:57:18 -04:00
Dave Bartolomeo
69dd8f5d89 Merge remote-tracking branch 'upstream/master' into dbartol/utilities 2020-06-09 08:36:30 -04:00
Dave Bartolomeo
c68c9e6b57 Rename @github/codeql-build-tasks to @github/codeql-gulp-tasks 2020-06-09 08:36:01 -04:00
Andrew Eisenberg
6b7cc9659f Update changelog with new section 2020-06-08 12:54:56 -07:00
github-actions[bot]
8e28c432bd Bump version to v1.2.3 2020-06-08 11:39:21 -07:00
Andrew Eisenberg
4bb48879ec Update changelog in preparation for v1.2.2 release (#433)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
* Update changelog in preparation for v1.2.2 release

Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-06-08 11:17:31 -07:00
Dave Bartolomeo
4c5361b611 Publish utility packages 2020-06-06 02:55:53 -04:00
Dave Bartolomeo
31ee9af939 Change notes 2020-06-06 02:54:23 -04:00
Dave Bartolomeo
8f49386a4a Remove unnecessary dependency on typescript 2020-06-06 02:53:02 -04:00
Dave Bartolomeo
69abf60581 Add missing peer dependency on glob 2020-06-06 02:52:47 -04:00
Dave Bartolomeo
9a7fdf8dda Publish utility packages 2020-06-06 02:46:43 -04:00
Dave Bartolomeo
d3caf77f90 Change notes 2020-06-05 17:45:58 -04:00
Dave Bartolomeo
4d90751638 Rename build-tasks to @github/codeql-build-tasks 2020-06-05 17:38:13 -04:00
Dave Bartolomeo
b436468ca9 Rename semmle-vscode-utils -> @github/codeql-vscode-utils
Also adds a version policy to support publishing as an npm package.
2020-06-05 17:25:52 -04:00
Andrew Eisenberg
46e7382832 Clarify log message (#430) 2020-06-05 13:28:41 -07:00
jcreedcmu
91bd7f5971 Merge pull request #401 from jcreedcmu/jcreed/pagination
Implement pagination for BQRS results.
2020-06-05 10:23:27 -04:00
jcreedcmu
109c8755c3 Merge pull request #421 from jcreedcmu/jcreed/fix-release-asset-search
Download platform-specific releases if they are available.
2020-06-05 09:50:51 -04:00
jcreedcmu
218a14a4a1 Update extensions/ql-vscode/src/distribution.ts
Co-authored-by: Henry Mercer <henry.mercer@me.com>
2020-06-05 09:01:07 -04:00
jcreedcmu
71efe355f0 Update extensions/ql-vscode/src/distribution.ts
Co-authored-by: Henry Mercer <henry.mercer@me.com>
2020-06-05 09:00:54 -04:00
jcreedcmu
f7eee72b93 Update extensions/ql-vscode/src/distribution.ts
Co-authored-by: Henry Mercer <henry.mercer@me.com>
2020-06-05 09:00:39 -04:00
jcreedcmu
3bc884f45d Update extensions/ql-vscode/src/distribution.ts
Co-authored-by: Henry Mercer <henry.mercer@me.com>
2020-06-05 08:32:53 -04:00
Andrew Eisenberg
ddf382d690 Update changelog 2020-06-04 07:42:01 -07:00
Andrew Eisenberg
b84c429882 Fix bad indentation on paste
I don't fully understand why this is working
differently, but these changes enable proper
behavior on pasting ql into the editor.
2020-06-04 07:42:01 -07:00
Jason Reed
73a0bcacc8 Don't update release.assets in place. 2020-06-04 10:23:38 -04:00
jcreedcmu
60f47e8ee3 Update extensions/ql-vscode/src/distribution.ts
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2020-06-03 14:55:00 -04:00
Jason Reed
c29f4d4c79 Download platform-specific releases if they are available. 2020-06-03 09:58:33 -04:00
Henry Mercer
71f74cb620 Merge pull request #427 from henrymercer/fix-semver-comparison
Use semver package for semantic version comparison and precedence checking
2020-06-02 22:01:09 +01:00
Henry Mercer
c4766e464b Add additional tests for choosing the latest release of the CodeQL CLI 2020-06-02 18:37:44 +01:00
Henry Mercer
eba67f8f4f Apply suggestions from review 2020-06-02 18:28:37 +01:00
Henry Mercer
b7a97d34e5 Apply suggestions from code review
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
2020-06-02 10:21:16 +01:00
Henry Mercer
18a9e2794e Update handling of prerelease versions of the CodeQL CLI.
Suppose a user has the includePrereleases config option set, installs an
extension-managed prerelease, then decides they no longer want
prereleases and disables includePrereleases.
In this case, we should prompt the user to downgrade the CLI to a
non-prerelease version.
However, if the user is managing their own CLI, we will allow them to
use prereleases without incompatibility prompts.
2020-06-01 22:26:48 +01:00
Henry Mercer
8208940532 Introduce release compatibility check before selecting the most recent 2020-06-01 22:18:09 +01:00
Henry Mercer
71d4038744 Use version ranges instead of version constraint for simplicity 2020-06-01 22:18:09 +01:00
Henry Mercer
034d8b7c68 Use semver package for version comparison and precedence checking 2020-06-01 22:18:08 +01:00
Henry Mercer
e686b421ec Merge pull request #426 from github/revert-425-fix-semver-comparison
Revert "Use semver package for semantic version comparison and precedence checking"
2020-06-01 21:20:34 +01:00
Henry Mercer
9191873eb1 Revert "Use semver package for semantic version comparison and precedence checking" 2020-06-01 21:11:10 +01:00
jcreedcmu
d924e9f649 Merge pull request #425 from henrymercer/fix-semver-comparison
Use semver package for semantic version comparison and precedence checking
2020-06-01 15:56:02 -04:00
Henry Mercer
e911bf4854 Introduce release compatibility check before selecting the most recent 2020-06-01 20:41:41 +01:00
Henry Mercer
7b9e540332 Use version ranges instead of version constraint for simplicity 2020-06-01 20:41:25 +01:00
Henry Mercer
577ce95cb1 Use semver package for version comparison and precedence checking 2020-06-01 20:41:00 +01:00
jcreedcmu
63c8afab44 Merge pull request #422 from jcreedcmu/jcreed/retry-harder-on-windows
Chore: Retry tests more aggressively on windows
2020-06-01 12:39:51 -04:00
Jason Reed
7777f9d643 Retry tests more aggressively on windows
There are some flaky CI test failures that manifest only as a message
like

    [main 2020-06-01T16:09:47.671Z] [VS Code]: render process crashed!

(and only afaict on windows) which I am not sure how to detect at the
moment. If that message is occurring in the exception caught at this
stage, we can check for it.
2020-06-01 12:23:57 -04:00
jcreedcmu
6505e97b98 Merge pull request #420 from jcreedcmu/jcreed/fix-release-asset-search
Only look for codeql.zip assets
2020-06-01 09:25:08 -04:00
Jason Reed
a6fc0d5493 Only look for codeql.zip assets
There are now multiple release assets available. Make sure we don't
throw an error when looking for the codeql distribution.
2020-06-01 09:01:31 -04:00
jcreedcmu
572e74e079 Merge pull request #416 from github/version/bump-to-v1.2.2
Bump version to v1.2.2
2020-05-29 14:06:49 -04:00
github-actions[bot]
c2de5fc9b6 Bump version to v1.2.2 2020-05-29 17:46:23 +00:00
jcreedcmu
728b8ca0fd Merge pull request #415 from jcreedcmu/jcreed/v1.2.1
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
Update CHANGELOG for release
2020-05-29 13:34:04 -04:00
Jason Reed
edd5734de8 Update CHANGELOG for release 2020-05-29 13:32:29 -04:00
jcreedcmu
88a4cc528e Merge pull request #414 from aeisenberg/aeisenberg/lgtm-explore
Update link to lgtm to go to the explore section
2020-05-29 13:31:59 -04:00
Andrew Eisenberg
a732f19a3d Update link to lgtm to go to the explore section
Makes it easier for new users to search for projects.
2020-05-29 10:29:04 -07:00
jcreedcmu
18c9333f37 Merge pull request #413 from aeisenberg/aeisenberg/rush
Add node v14 to allowed node versions
2020-05-29 13:21:52 -04:00
Andrew Eisenberg
010000b878 Add node v14 to allowed node versions 2020-05-29 10:20:13 -07:00
jcreedcmu
7b5f7499b4 Merge pull request #411 from aeisenberg/aeisenberg/readme-lgtm
Update the download from LGTM section in the readme
2020-05-29 11:32:41 -04:00
Andrew Eisenberg
292bec2ea5 Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:18:17 -07:00
Andrew Eisenberg
910a877d06 Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:18:11 -07:00
Andrew Eisenberg
80023f1304 Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:18:04 -07:00
Andrew Eisenberg
8e8247e986 Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:17:57 -07:00
Andrew Eisenberg
d92e0b5568 Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:17:50 -07:00
Andrew Eisenberg
d3c1e7688e Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:17:42 -07:00
Andrew Eisenberg
3e9c58869c Update extensions/ql-vscode/README.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-05-29 08:17:34 -07:00
Andrew Eisenberg
c0a8c7affd Update the download from LGTM section in the readme 2020-05-28 13:12:21 -07:00
Andrew Eisenberg
f2575e4d4a Better error handling for downloading dbs at invalid URLs
We do our best to extract a readable error message from the
response.
2020-05-28 11:53:41 -07:00
Andrew Eisenberg
87315b8f33 Update changelog 2020-05-28 11:53:41 -07:00
Andrew Eisenberg
a338683a71 Add unit tests for databaseFetcher 2020-05-28 11:53:41 -07:00
Andrew Eisenberg
a541b11a37 Add more flexibility with Uri parsing for LGTM
Ensure that providers other than `g` are accepted and that subpages
are ignored.
2020-05-28 11:53:41 -07:00
Andrew Eisenberg
e2771a8922 Remove code scanning on pull request 2020-05-28 11:53:41 -07:00
Andrew Eisenberg
16e09b7ae9 Add better error handling
* ensure error appears when an invalid URL is entered
* ensure error messages are understandable by users
2020-05-28 11:53:41 -07:00
Andrew Eisenberg
1c1dbc95c7 Allow download from lgtm
Adds a new command to download databases from lgtm. It's working,
though need to create better error messages on failure.
2020-05-28 11:53:41 -07:00
jcreedcmu
dd9fafc27c Merge pull request #407 from jcreedcmu/jcreed/view-sarif
Allow viewing SARIF from query history view
2020-05-28 08:08:56 -04:00
jcreedcmu
7172505e25 Merge pull request #328 from jcreedcmu/jcreed/restart-on-segfault
Retry integration tests only on segfault
2020-05-27 14:10:18 -04:00
Jason Reed
7b99bdfc88 Address review comments. 2020-05-27 14:08:03 -04:00
Jason Reed
bb16454ab7 Only show 'view SARIF' if SARIF exists. 2020-05-27 11:57:31 -04:00
Jason Reed
70529a81f3 Add "View SARIF" command to query history context menu. 2020-05-27 11:30:50 -04:00
Jason Reed
7db6bc8228 Remove dead code. 2020-05-27 10:59:32 -04:00
Jason Reed
41fab207dc Retry integration tests only on segfault 2020-05-27 10:52:31 -04:00
Jason Reed
a8bad9ecb8 Upgrade vscode-test dependency to 1.4.0 2020-05-27 10:52:31 -04:00
jcreedcmu
17901bee0c Merge pull request #406 from shati-patel/qldoc
Update capitalization of "QLDoc" in CHANGELOG.md
2020-05-27 10:34:36 -04:00
Shati Patel
e7d041af68 Update CHANGELOG.md 2020-05-27 10:09:22 +01:00
jcreedcmu
9afd676c1e Merge pull request #403 from aeisenberg/aeisenberg/codeql-action
Introduce codeql code scanning action
2020-05-26 17:05:04 -04:00
Andrew Eisenberg
7bf719f632 Introduce codeql code scanning action 2020-05-26 14:00:28 -07:00
Jason Reed
c90dae89c1 Fix LGTM warning. 2020-05-26 16:53:20 -04:00
Jason Reed
110cf0ddc0 Implement pagination for BQRS results. 2020-05-26 16:30:10 -04:00
Andrew Eisenberg
32622b1b9f Update changelog 2020-05-26 12:01:22 -07:00
Andrew Eisenberg
8262ecf990 Fix syntax error in ql library 2020-05-26 12:01:22 -07:00
Andrew Eisenberg
0817abd6ac Use Uri.file instead of Uri.parse 2020-05-26 12:01:22 -07:00
Andrew Eisenberg
821ec9b8f7 Add tests for database uri fixing 2020-05-26 12:01:22 -07:00
Andrew Eisenberg
b0328b03a0 Allow users more flexibility when opening a DB
Closes #383.

See the heuristics in the issue.
2020-05-26 12:01:22 -07:00
Andrew Eisenberg
2d7d6fb873 Update changelog 2020-05-26 11:39:57 -07:00
Andrew Eisenberg
b7201c04dc Add onEnterRules for language config
This change provides proper indent/outdent for block comments. Through
onEnterRules. Because onEnterRules are not exactly API, I had to use
a back door to implement them.

Also, it tweaks the language-configuration.json by adding more support
for things like word boundaries and auto-closing pairs.

Since QL has similar syntactical items as JavaScriot, I started with
the JS lang config and removed single quotes and back ticks.
2020-05-26 11:39:57 -07:00
Andrew Eisenberg
8db488563b Add more tests for the archive-filesystem-provider 2020-05-26 10:50:03 -07:00
jcreedcmu
fac5f98d80 Merge pull request #382 from jcreedcmu/jcreed/zip-logging
Log more details when source archive entries aren't found
2020-05-26 12:38:40 -04:00
jcreedcmu
fccec96926 Merge pull request #389 from jcreedcmu/jcreed/no-defs-err
Add error message when there are no definitions/references queries available.
2020-05-26 12:38:24 -04:00
Jason Reed
8cadd3dcab Add error message when no definitions queries. 2020-05-21 14:45:55 -04:00
jcreedcmu
d9e1a6f82a Merge pull request #385 from github/jcreedcmu-patch-1
Update README.md
2020-05-21 11:06:15 -04:00
Bas van Schaik
f47a88dcb1 Update README.md 2020-05-21 16:02:31 +01:00
Bas van Schaik
8cab3e9c6f Update README.md 2020-05-21 16:02:02 +01:00
jcreedcmu
165f3957ed Update README.md
Include instructions about how to update libraries
2020-05-21 10:58:00 -04:00
Jason Reed
3e4eeeb8fd Log more details when source archive entries aren't found 2020-05-20 08:49:58 -04:00
jcreedcmu
038e0a3c63 Merge pull request #381 from dbartol/dbartol/publish
Avoid need for `build` directory
2020-05-20 07:51:58 -04:00
Dave Bartolomeo
3e7084f65d Remove build directory 2020-05-19 17:13:30 -04:00
Dave Bartolomeo
18bb4b0231 Avoid need for build directory 2020-05-19 17:03:56 -04:00
github-actions[bot]
8cb5661330 Bump version to v1.2.1 2020-05-19 12:48:50 -07:00
jcreedcmu
f6f2b99c67 Merge pull request #379 from jcreedcmu/jcreed/v1.2.0-take-2
Some checks failed
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
Increment version for minor release
2020-05-19 14:28:49 -04:00
Jason Reed
b2c82029f6 Increment version for minor release 2020-05-19 14:26:16 -04:00
jcreedcmu
d18b524c81 Merge pull request #378 from jcreedcmu/jcreed/v1.2.0
Update CHANGELOG.md for release.
2020-05-19 14:25:07 -04:00
Jason Reed
6be2c8bb95 Update CHANGELOG.md for release. 2020-05-19 14:22:12 -04:00
jcreedcmu
c289f1f66f Merge pull request #368 from jcreedcmu/jcreed/jump-to-def-release
Remove feature flag guard around source archive jump-to-definition
2020-05-19 14:17:33 -04:00
jcreedcmu
c2717d7725 Merge branch 'master' into jcreed/jump-to-def-release 2020-05-19 14:14:59 -04:00
jcreedcmu
74e42b86a6 Merge pull request #375 from jcreedcmu/jcreed/more-logging
Logging: More chatty logging during extension initialization.
2020-05-19 14:14:00 -04:00
jcreedcmu
6db514843b Merge branch 'master' into jcreed/jump-to-def-release 2020-05-19 13:57:20 -04:00
jcreedcmu
c8d64e4c35 Merge branch 'master' into jcreed/more-logging 2020-05-19 13:56:10 -04:00
jcreedcmu
0e4c3be404 Merge pull request #377 from jcreedcmu/jcreed/prefix
Fix naming and availability in command palette of various commands
2020-05-19 13:54:10 -04:00
Jason Reed
dd1bdf54bb Add integrity check for commands in package.json
Attempt to enforce some regularity in how we name commands, and fix
one command that was showing up improperly in the command palette.
2020-05-19 12:20:45 -04:00
Jason Reed
c01772848c Add all db-getting commands (dl, folder, zip) to command palette 2020-05-19 11:32:54 -04:00
Jason Reed
ab09cdb66d Make capitalization consistent 2020-05-19 11:02:32 -04:00
Jason Reed
d92edfb058 Remove database panel icon commands from command palette
This corrects what is an unfortunately common accidental antipattern,
where creating a command meant just to be the handler of a user
interface button ends up in the command palette unless you explicitly
set `"when": "false"` in the command palette section of the
configuration.

Also enforce the naming convention that commands prefixed with
`codeQLDatabases.` are those meant for the databases panel only, while
prefixing `codeQL.` means that it's meant to be directly accessible
through the command palette.
2020-05-19 10:59:35 -04:00
jcreedcmu
1e86e08851 Merge pull request #376 from shati-patel/choose-db
Update README with new command for adding database
2020-05-19 09:14:38 -04:00
Shati Patel
c505996ca0 Update README with new command for adding database 2020-05-19 13:19:58 +01:00
Jason Reed
0796893017 Logging: More chatty logging during extension initialization.
Mainly intentded to make it easier to debug the cause of
command-palette commands being undefined.
2020-05-18 13:24:00 -04:00
jcreedcmu
6fdfade1ed Merge pull request #374 from aeisenberg/path-fix
Fix paths on windows when opening archive databases
2020-05-15 19:35:04 -04:00
Andrew Eisenberg
e31f8b73ac Fix paths on windows when opening archive databases 2020-05-15 16:30:50 -07:00
jcreedcmu
f38d0fd08e Merge pull request #372 from github/version/bump-to-v1.1.6
Bump version to v1.1.6
2020-05-15 15:39:14 -04:00
github-actions[bot]
579aba5abb Bump version to v1.1.6 2020-05-15 19:00:31 +00:00
jcreedcmu
31066be29e Merge pull request #371 from aeisenberg/aeisenberg/release
Some checks failed
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
Update changelog for release
2020-05-15 14:55:47 -04:00
Andrew Eisenberg
3bbecb248b Update changelog for release 2020-05-15 11:53:58 -07:00
jcreedcmu
691c9af1f7 Merge pull request #370 from aeisenberg/aeisenberg/import-notify
Refactor how we import database archives
2020-05-15 13:43:10 -04:00
Andrew Eisenberg
a137a72e02 Refactor how we import database archives
Now, importing an archive includes a progress monitor and will also
display a message when complete. Error handling is also more uniform.
2020-05-15 10:39:40 -07:00
Jason Reed
a98e3bc9ae Fix docs, comments. 2020-05-15 12:42:46 -04:00
Jason Reed
4ffab3c16d Remove feature flag guard around source archive jump-to-definition 2020-05-15 12:42:46 -04:00
Andrew Eisenberg
bb3aa79dad Update changelog 2020-05-15 08:27:51 -07:00
Andrew Eisenberg
7f34fcaa1c Update commands for importing databases
1. Add commands for importing an archive, folder, or from internet
2. Add new icons for all of them
3. Ensure that each command can only retrieve databases through a 
   single mechanism
2020-05-15 08:27:51 -07:00
Andrew Eisenberg
e42a39e5ec Look for either .dbinfo or codeql-database.yml 2020-05-15 08:27:51 -07:00
Andrew Eisenberg
bd22878ec8 Update changelog 2020-05-15 08:27:51 -07:00
Andrew Eisenberg
8dd1b9f44e Augments the add database command to handle zip files
The add database command can now add databases by zip file. When a
file is selected, the zip file is attempted to be extracted into a
directory managed by the extension.

Once extracted, a database is searched for, by looking for a .dbinfo
file.

Crucially, we are using the same infrastructure to download a database
as we are to add a database by zip file.
2020-05-15 08:27:51 -07:00
jcreedcmu
2da70d774d Merge pull request #367 from jbj/link-style
Make links variable-width without underline
2020-05-14 12:58:51 -04:00
Jonas Jensen
2fddc9cff1 Make links variable-width without underline
These are two independent changes.
- The monospace font used in links made the text larger so that less
  text could fit on the screen. It also suggested that all link text was
  an code snippet, which it isn't. The advantage of a fixed-width font,
  vertical alignment, was not put to any use.
- Underlining of links made it almost impossible to distinguish a space
  from an underscore.
2020-05-14 09:59:44 +02:00
jcreedcmu
11d9bdc8e1 Merge pull request #365 from aeisenberg/lint-semi
Add semi-colon linting
2020-05-13 15:40:47 -04:00
Andrew Eisenberg
7d23a833b1 Add semi-colon linting 2020-05-13 12:06:53 -07:00
Andrew Eisenberg
258322057f Ensure the extension dir exists before writing to it 2020-05-13 11:47:43 -07:00
jcreedcmu
6ded193891 Merge pull request #362 from github/version/bump-to-v1.1.5
Bump version to v1.1.5
2020-05-13 14:44:41 -04:00
jcreedcmu
bb6b90646f Merge pull request #363 from aeisenberg/lint-fixes
Fixes to how we lint automatically
2020-05-13 14:42:57 -04:00
Andrew Eisenberg
fece068800 Rename npm script for clarity 2020-05-13 11:36:05 -07:00
Andrew Eisenberg
de8b7d44cd Avoid linting indentation rules
This is already handled by tsfmt and eslint has
some slight differences.
2020-05-13 11:29:32 -07:00
Andrew Eisenberg
432c5c9ae7 Move linting from precommit to prepush
It's a longer action, so we don't want to run it on each commit.
2020-05-13 11:27:34 -07:00
github-actions[bot]
59433af8be Bump version to v1.1.5 2020-05-13 17:30:08 +00:00
230 changed files with 53543 additions and 16223 deletions

7
.gitattributes vendored
View File

@@ -12,3 +12,10 @@ yarn.lock merge=binary
# For more information, see this issue: https://github.com/Microsoft/web-build-tools/issues/1088
#
*.json linguist-language=JSON-with-Comments
# Reduce incidence of needless merge conflicts on CHANGELOG.md
# The man page at
# 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

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

@@ -9,7 +9,7 @@ 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 master of the format `vxx.xx.xx`
- [ ] 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).

12
.github/codeql/codeql-config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "CodeQL config"
queries:
- name: Run standard queries
uses: security-and-quality
- name: Run custom javascript queries
uses: ./.github/codeql/queries
paths:
- ./extensions/ql-vscode
paths-ignore:
- '**/node_modules'
- '**/build'
- '**/out'

21
.github/codeql/queries/assert-pure.ql vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* @name Unwanted dependency on vscode API
* @kind problem
* @problem.severity error
* @id vscode-codeql/assert-pure
* @description The modules stored under `pure` and tested in the `pure-tests`
* are intended to be "pure".
*/
import javascript
class VSCodeImport extends ASTNode {
VSCodeImport() {
this.(Import).getImportedPath().getValue() = "vscode"
}
}
from Module m, VSCodeImport v
where
m.getFile().getRelativePath().regexpMatch(".*src/pure/.*") and
m.getAnImportedModule*().getAnImport() = v
select m, "This module is not pure: it has a transitive dependency on the vscode API imported $@", v, "here"

3
.github/codeql/queries/qlpack.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
name: vscode-codeql-custom-queries-javascript
version: 0.0.0
libraryPathDependencies: codeql-javascript

View File

@@ -1,12 +1,12 @@
<!-- Thank you for submitting a pull request. Please read our pull request guidelines before
submitting your pull request:
https://github.com/github/vscode-codeql/blob/master/CONTRIBUTING.md#submitting-a-pull-request.
https://github.com/github/vscode-codeql/blob/main/CONTRIBUTING.md#submitting-a-pull-request.
-->
Replace this with a description of the changes your pull request makes.
## Checklist
- [ ] [CHANGELOG.md](https://github.com/github/vscode-codeql/blob/master/extensions/ql-vscode/CHANGELOG.md) has been updated to incorporate all user visible changes made by this pull request.
- [ ] [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/product-docs-dsp` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
- [ ] `@github/docs-content-codeql` has been cc'd in all issues for UI or other user-facing changes made by this pull request.

31
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: "Code Scanning - CodeQL"
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '21 17 * * 0'
jobs:
codeql:
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@main
with:
languages: javascript
config-file: ./.github/codeql/codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@main

15
.github/workflows/label-issue.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Label issue
on:
issues:
types: [opened]
jobs:
label:
name: Label issue
runs-on: ubuntu-latest
steps:
- name: Label issue
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo '{"labels": ["VSCode"]}' | gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels --input -

View File

@@ -1,5 +1,10 @@
name: Build Extension
on: [push, pull_request]
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jobs:
build:
@@ -16,13 +21,20 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '10.18.1'
node-version: '14.14.0'
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
npm install
shell: bash
- name: Build
working-directory: extensions/ql-vscode
env:
APP_INSIGHTS_KEY: '${{ secrets.APP_INSIGHTS_KEY }}'
run: |
cd build
npm install
npm run build-ci
npm run build
shell: bash
- name: Prepare artifacts
@@ -32,7 +44,7 @@ jobs:
cp dist/*.vsix artifacts
- name: Upload artifacts
uses: actions/upload-artifact@master
uses: actions/upload-artifact@v2
if: matrix.os == 'ubuntu-latest'
with:
name: vscode-codeql-extension
@@ -52,19 +64,25 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '10.18.1'
node-version: '14.14.0'
# We have to build the dependencies in `lib` before running any tests.
- name: Build
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
cd build
npm install
npm run build-ci
shell: bash
- name: Build
working-directory: extensions/ql-vscode
env:
APP_INSIGHTS_KEY: '${{ secrets.APP_INSIGHTS_KEY }}'
run: |
npm run build
shell: bash
- name: Lint
working-directory: extensions/ql-vscode
run: |
cd extensions/ql-vscode
npm run lint
- name: Install CodeQL
@@ -77,27 +95,76 @@ jobs:
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
- 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
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
run: |
cd extensions/ql-vscode
npm run integration
cli-test:
name: CLI Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
version: ['v2.2.6', 'v2.3.3', 'v2.4.5', 'v2.4.6', 'v2.5.3']
env:
CLI_VERSION: ${{ matrix.version }}
TEST_CODEQL_PATH: '${{ github.workspace }}/codeql'
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14.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: Checkout QL
uses: actions/checkout@v2
with:
repository: github/codeql
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,25 +6,20 @@
name: Release
on:
push:
# Path filters are not evaluated for pushes to tags.
# (source: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths)
# So this workflow is triggered in the following events:
# - Release event: a SemVer tag, e.g. v1.0.0 or v1.0.0-alpha, is pushed
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
# OR
# - Test event: this file is modified on a branch in the main repo containing `/actions/` in the name.
branches:
- '**/actions/**'
pull_request:
paths:
- '**/workflows/release.yml'
workflow_dispatch:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
build:
name: Release
runs-on: ubuntu-latest
# TODO Share steps with the main workflow.
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -33,12 +28,19 @@ jobs:
with:
node-version: '10.18.1'
- name: Build
- name: Install dependencies
run: |
cd build
npm install
# Release build instead of dev build.
npm run build-release
cd extensions/ql-vscode
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
- name: Prepare artifacts
@@ -55,11 +57,8 @@ 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@master
uses: actions/upload-artifact@v2
with:
name: vscode-codeql-extension
path: artifacts
@@ -93,12 +92,16 @@ jobs:
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
# The checkout action does not fetch the master branch.
# Fetch the master branch so that we can base the version bump PR against master.
- name: Fetch master branch
###
# 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
run: |
git fetch --depth=1 origin master:master
git checkout master
git fetch --depth=1 origin main:main
git checkout main
- name: Bump patch version
id: bump-patch-version
@@ -110,8 +113,14 @@ jobs:
NEXT_VERSION="$(npm version patch)"
echo "::set-output name=next_version::$NEXT_VERSION"
- name: Add changelog for next release
if: success()
run: |
cd extensions/ql-vscode
perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md
- name: Create version bump PR
uses: peter-evans/create-pull-request@c7b64af0a489eae91f7890f2c1b63d13cc2d8ab7 # v2.4.2
uses: peter-evans/create-pull-request@c7f493a8000b8aeb17a1332e326ba76b57cb83eb # v3.4.1
if: success()
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -119,4 +128,41 @@ jobs:
title: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
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: master
base: main
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,10 +1,12 @@
{
// 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"
"eamodio.tsl-problem-matcher",
"dbaeumer.vscode-eslint",
"eternalphane.tsfmt-vscode"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
}

48
.vscode/launch.json vendored
View File

@@ -8,18 +8,20 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql"
"--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}/dist/vscode-codeql/out/**/*.js",
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-bqrs/out/**/*.js",
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io/out/**/*.js",
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io-node/out/**/*.js",
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-vscode-utils/out/**/*.js"
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
"preLaunchTask": "Build"
"env": {
// change to 'true' debug the IDE or Query servers
"IDE_SERVER_JAVA_DEBUG": "false",
"QUERY_SERVER_JAVA_DEBUG": "false",
}
},
{
"name": "Launch Unit Tests (vscode-codeql)",
@@ -44,7 +46,6 @@
"port": 9229,
"stopOnEntry": false,
"sourceMaps": true,
"preLaunchTask": "Build",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
@@ -54,16 +55,14 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/vscode-codeql/out/**/*.js",
"${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js"
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
"preLaunchTask": "Build"
},
{
"name": "Launch Integration Tests - Minimal Workspace (vscode-codeql)",
@@ -71,17 +70,34 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/minimal-workspace/index",
"${workspaceRoot}/extensions/ql-vscode/test/data"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/vscode-codeql/out/**/*.js",
"${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js"
"${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",
"${workspaceRoot}/extensions/ql-vscode/src/vscode-tests/cli-integration/data",
// Add a path to a checked out instance of the codeql repository so the libraries are
// available in the workspace for the tests.
// "${workspaceRoot}/../codeql"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
"preLaunchTask": "Build"
}
]
}

View File

@@ -33,6 +33,8 @@
// This is necessary so that eslint can properly resolve its plugins
"resolvePluginsRelativeTo": "./extensions/ql-vscode"
},
// Force this to false since this will cause too many changes on each commit
"editor.formatOnSave": false
"editor.formatOnSave": false,
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
"editor.wordWrapColumn": 100
}

66
.vscode/tasks.json vendored
View File

@@ -10,7 +10,10 @@
"kind": "build",
"isDefault": true
},
"command": "node common/scripts/install-run-rush.js build --verbose",
"command": "npm run build",
"options": {
"cwd": "extensions/ql-vscode/"
},
"presentation": {
"echo": true,
"reveal": "always",
@@ -33,64 +36,13 @@
"$ts-webpack"
]
},
{
"label": "Rebuild",
"type": "shell",
"group": "build",
"command": "node common/scripts/install-run-rush.js rebuild --verbose",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
"problemMatcher": [
{
"owner": "typescript",
"fileLocation": "absolute",
"pattern": {
"regexp": "^\\[gulp-typescript\\] ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): error TS\\d+: (.*)$",
"file": 1,
"location": 2,
"message": 3
}
}
]
},
{
"label": "Update",
"type": "shell",
"command": "node common/scripts/install-run-rush.js update",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
"problemMatcher": []
},
{
"label": "Update (full)",
"type": "shell",
"command": "node common/scripts/install-run-rush.js update --full",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
"problemMatcher": []
},
{
"label": "Format",
"type": "shell",
"command": "node common/scripts/install-run-rush.js format",
"command": "npm run format",
"options": {
"cwd": "extensions/ql-vscode/"
},
"presentation": {
"echo": true,
"reveal": "always",
@@ -111,4 +63,4 @@
"group": "build"
}
]
}
}

View File

@@ -25,94 +25,40 @@ Here are a few things you can do that will increase the likelihood of your pull
* Follow the [style guide][style].
* Write tests. Tests that don't require the VS Code API are located [here](extensions/ql-vscode/test). Integration tests that do require the VS Code API are located [here](extensions/ql-vscode/src/vscode-tests).
* Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
* Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
* Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Setting up a local build
Make sure you have a fairly recent version of vscode (>1.32) and are using nodejs
version >=v10.13.0. (Tested on v10.15.1 and v10.16.0).
Make sure you have installed recent versions of vscode (>= v1.52), node (>=12.16), and npm (>= 7.5.2). Earlier versions will probably work, but we no longer test against them.
This repo uses [Rush](https://rushjs.io) to handle package management, building, and other
operations across multiple projects. See the Rush "[Getting started as a developer](https://rushjs.io/pages/developer/new_developer/)" docs
for more details.
### Installing all packages
If you plan on building from the command line, it's easiest if Rush is installed globally:
From the command line, go to the directory `extensions/ql-vscode` and run
```shell
npm install -g @microsoft/rush
npm install
```
To get started, run:
### Building the extension
From the command line, go to the directory `extensions/ql-vscode` and run
```shell
rush update && rush build
npm run build
npm run watch
```
Note that when you run the `rush` command from the globally installed version, it will examine the
`rushVersion` property in the repo's `rush.json`, and if it differs from the globally installed
version, it will download, cache, and run the version of Rush specified in the `rushVersion`
property.
Alternatively, you can build the extension within VS Code via `Terminal > Run Build Task...` (or `Ctrl+Shift+B` with the default key bindings). And you can run the watch command via `Terminal > Run Task` and then select `npm watch` from the menu.
A few more things to know about using rush:
Before running any of the launch commands, be sure to have run the `build` command to ensure that the JavaScript is compiled and the resources are copied to the proper location.
* Avoid running `npm` for any commands that install/link dependencies
* Instead use the *rush* equivalent: `rush add <package>`, `rush update`, etc.
* If you plan on only building via VS Code tasks, you don't need Rush installed at all, since those
tasks run `common/scripts/install-run-rush.js` to bootstrap a locally installed and cached copy of
Rush.
We recommend that you keep `npm run watch` running in the backgound and you only need to re-run `npm run build` in the following situations:
### Building
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 all packages (instead of `npm install`)
After updating any `package.json` file, or after checking or pulling a new branch, you need to
make sure all the right npm packages are installed, which you would normally do via `npm install` in
a single-project repo. With Rush, you need to do an "update" instead:
##### From VS Code
`Terminal > Run Task... > Update`
##### From the command line
```shell
rush update
```
#### Building all projects (instead of `gulp`)
Rush builds all projects in the repo, in dependency order, building multiple projects in parallel
where possible. By default, the build also packages the extension itself into a .vsix file in the
`dist` directory. To build:
##### From VS Code
`Terminal > Run Build Task...` (or just `Ctrl+Shift+B` with the default key bindings)
##### From the command line
```shell
rush build --verbose
```
#### Forcing a clean build
Rush does a reasonable job of detecting on its own which projects need to be rebuilt, but if you need to
force a full rebuild of all projects:
##### From VS Code
`Terminal > Run Task... > Rebuild`
##### From the command line
```shell
rush rebuild --verbose
```
Note that `rush rebuild` performs a complete rebuild, whereas `rush build` performs an incremental build and in many cases will not need to do anything at all.
### Installing
### Installing the extension
You can install the `.vsix` file from within VS Code itself, from the Extensions container in the sidebar:
@@ -144,24 +90,55 @@ Alternatively, you can run the tests inside of vscode. There are several vscode
## 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.
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. Trigger a release build on Actions by adding a new tag on master of the format `vxx.xx.xx`
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` 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:
```bash
git push upstream refs/tags/v1.3.6
```
* **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.
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. Optionally unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
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. 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:
1. Follow the instructions on [getting a PAT for Azure DevOps](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token).
1. Update the secret in the `publish-vscode-marketplace` environment in the project settings.
Not that Azure DevOps PATs expire yearly and must be regenerated.
## Resources
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)

View File

@@ -4,7 +4,7 @@ This project is an extension for Visual Studio Code that adds rich language supp
The extension is released. You can download it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql).
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/master/extensions/ql-vscode/CHANGELOG.md).
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md).
[![CI status badge](https://github.com/github/vscode-codeql/workflows/Build%20Extension/badge.svg)](https://github.com/github/vscode-codeql/actions?query=workflow%3A%22Build+Extension%22+branch%3Amaster)
[![VS Marketplace badge](https://vsmarketplacebadge.apphb.com/version/github.vscode-codeql.svg)](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
@@ -13,10 +13,9 @@ To see what has changed in the last few versions of the extension, see the [Chan
* Enables you to use CodeQL to query databases and discover problems in codebases.
* Shows the flow of data through the results of path queries, which is essential for triaging security results.
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/Semmle/ql).
* 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

@@ -1,12 +0,0 @@
GitHub Actions Build directory
===
The point of this directory is to allow us to do a local installation *of* the rush
tool, since
- installing globally is not permitted on github actions
- installing locally in the root directory of the repo creates `node_modules` there,
and rush itself gives error messages since it thinks `node_modules` is not supposed
to exist, since rush is supposed to be managing subproject dependencies.
Running rush from a subdirectory searches parent directories for `rush.json`
and does the build starting from that file's location.

1293
build/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
{
"name": "build",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"@microsoft/rush": "^5.10.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rush update && rush build",
"build-ci": "rush install && rush build",
"build-release": "rush install && rush build --release"
},
"author": "GitHub"
}

View File

@@ -1,28 +0,0 @@
This directory contains content from https://github.com/microsoft/rushstack,
used under the MIT license as follows.
See https://github.com/microsoft/rushstack/blob/master/stack/rush-stack/LICENSE.
@microsoft/rush-stack
Copyright (c) Microsoft Corporation. All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,12 +0,0 @@
# Rush uses this file to configure the package registry, regardless of whether the
# package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager,
# Rush will always copy this file to the folder where installation is performed.
# When NPM is the package manager, Rush works around NPM's processing of
# undefined environment variables by deleting any lines that reference undefined
# environment variables.
#
# DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used
# to configure registry sources.
registry=https://registry.npmjs.org/
always-auth=false

View File

@@ -1,32 +0,0 @@
/**
* This configuration file defines custom commands for the "rush" command-line.
* For full documentation, please see https://rushjs.io/pages/configs/command_line_json/
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
"commands": [
{
"commandKind": "bulk",
"name": "format",
"summary": "Reformat source code in all projects",
"description": "Runs the `format` npm task in each project, if present.",
"safeForSimultaneousRushProcesses": false,
"enableParallelism": true,
"ignoreDependencyOrder": true,
"ignoreMissingScript": true,
"allowWarningsInSuccessfulBuild": false
}
],
"parameters": [
{
"parameterKind": "flag",
"longName": "--release",
"shortName": "-r",
"description": "Perform a release build",
"associatedCommands": [
"build",
"rebuild"
],
}
]
}

View File

@@ -1,43 +0,0 @@
/**
* This configuration file specifies NPM dependency version selections that affect all projects
* in a Rush repo. For full documentation, please see https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
/**
* A table that specifies a "preferred version" for a dependency package. The "preferred version"
* is typically used to hold an indirect dependency back to a specific version, however generally
* it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible)
* SemVer range specifier. See the Rush documentation for details about this feature.
*/
"preferredVersions": {
/**
* When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo,
* instead of the latest version.
*/
// "some-library": "1.2.3"
},
/**
* The "rush check" command can be used to enforce that every project in the repo must specify
* the same SemVer range for a given dependency. However, sometimes exceptions are needed.
* The allowedAlternativeVersions table allows you to list other SemVer ranges that will be
* accepted by "rush check" for a given dependency.
*
* IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE
* USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO).
* This design avoids unnecessary churn in this file.
*/
"allowedAlternativeVersions": {
/**
* For example, allow some projects to use an older TypeScript compiler
* (in addition to whatever "usual" version is being used by other projects in the repo):
*/
// "typescript": [
// "~2.4.0"
// ]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
"use strict";
/**
* When using the PNPM package manager, you can use pnpmfile.js to workaround
* dependencies that have mistakes in their package.json file. (This feature is
* functionally similar to Yarn's "resolutions".)
*
* For details, see the PNPM documentation:
* https://pnpm.js.org/docs/en/hooks.html
*
* IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY
* TO INVALIDATE ANY CACHED DEPENDENCY ANALYSIS. We recommend to run "rush update --full"
* after any modification to pnpmfile.js.
*
*/
module.exports = {
hooks: {
readPackage
}
};
/**
* This hook is invoked during installation before a package's dependencies
* are selected.
* The `packageJson` parameter is the deserialized package.json
* contents for the package that is about to be installed.
* The `context` parameter provides a log() function.
* The return value is the updated object.
*/
function readPackage(packageJson, context) {
return packageJson;
}

View File

@@ -1,6 +0,0 @@
/**
* This is configuration file is used for advanced publishing configurations with Rush.
* For full documentation, please see https://rushjs.io/pages/configs/version_policies_json/
*/
[]

View File

@@ -1,67 +0,0 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See the @microsoft/rush package's LICENSE file for license information.
Object.defineProperty(exports, "__esModule", { value: true });
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
//
// This script is intended for usage in an automated build environment where the Rush command may not have
// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
// specified in the rush.json configuration file (if not already installed), and then pass a command-line to it.
// An example usage would be:
//
// node common/scripts/install-run-rush.js install
//
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
const path = require("path");
const fs = require("fs");
const install_run_1 = require("./install-run");
const PACKAGE_NAME = '@microsoft/rush';
const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION';
function _getRushVersion() {
const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION];
if (rushPreviewVersion !== undefined) {
console.log(`Using Rush version from environment variable ${RUSH_PREVIEW_VERSION}=${rushPreviewVersion}`);
return rushPreviewVersion;
}
const rushJsonFolder = install_run_1.findRushJsonFolder();
const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME);
try {
const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8');
// Use a regular expression to parse out the rushVersion value because rush.json supports comments,
// but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script.
const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/);
return rushJsonMatches[1];
}
catch (e) {
throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` +
'The \'rushVersion\' field is either not assigned in rush.json or was specified ' +
'using an unexpected syntax.');
}
}
function _run() {
const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ ...packageBinArgs /* [build, --to, myproject] */] = process.argv;
// Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the
// appropriate binary inside the rush package to run
const scriptName = path.basename(scriptPath);
const bin = scriptName.toLowerCase() === 'install-run-rushx.js' ? 'rushx' : 'rush';
if (!nodePath || !scriptPath) {
throw new Error('Unexpected exception: could not detect node path or script path');
}
if (process.argv.length < 3) {
console.log(`Usage: ${scriptName} <command> [args...]`);
if (scriptName === 'install-run-rush.js') {
console.log(`Example: ${scriptName} build --to myproject`);
}
else {
console.log(`Example: ${scriptName} custom-command`);
}
process.exit(1);
}
install_run_1.runWithErrorAndStatusCode(() => {
const version = _getRushVersion();
console.log(`The rush.json configuration requests Rush version ${version}`);
return install_run_1.installAndRun(PACKAGE_NAME, version, bin, packageBinArgs);
});
}
_run();
//# sourceMappingURL=install-run-rush.js.map

View File

@@ -1,18 +0,0 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See the @microsoft/rush package's LICENSE file for license information.
Object.defineProperty(exports, "__esModule", { value: true });
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
//
// This script is intended for usage in an automated build environment where the Rush command may not have
// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
// specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
// rushx command.
//
// An example usage would be:
//
// node common/scripts/install-run-rushx.js custom-command
//
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
require("./install-run-rush");
//# sourceMappingURL=install-run-rushx.js.map

View File

@@ -1,433 +0,0 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See the @microsoft/rush package's LICENSE file for license information.
Object.defineProperty(exports, "__esModule", { value: true });
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
//
// This script is intended for usage in an automated build environment where a Node tool may not have
// been preinstalled, or may have an unpredictable version. This script will automatically install the specified
// version of the specified tool (if not already installed), and then pass a command-line to it.
// An example usage would be:
//
// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io
//
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
const childProcess = require("child_process");
const fs = require("fs");
const os = require("os");
const path = require("path");
exports.RUSH_JSON_FILENAME = 'rush.json';
const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER';
const INSTALLED_FLAG_FILENAME = 'installed.flag';
const NODE_MODULES_FOLDER_NAME = 'node_modules';
const PACKAGE_JSON_FILENAME = 'package.json';
/**
* Parse a package specifier (in the form of name\@version) into name and version parts.
*/
function _parsePackageSpecifier(rawPackageSpecifier) {
rawPackageSpecifier = (rawPackageSpecifier || '').trim();
const separatorIndex = rawPackageSpecifier.lastIndexOf('@');
let name;
let version = undefined;
if (separatorIndex === 0) {
// The specifier starts with a scope and doesn't have a version specified
name = rawPackageSpecifier;
}
else if (separatorIndex === -1) {
// The specifier doesn't have a version
name = rawPackageSpecifier;
}
else {
name = rawPackageSpecifier.substring(0, separatorIndex);
version = rawPackageSpecifier.substring(separatorIndex + 1);
}
if (!name) {
throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`);
}
return { name, version };
}
/**
* As a workaround, copyAndTrimNpmrcFile() copies the .npmrc file to the target folder, and also trims
* unusable lines from the .npmrc file.
*
* Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in
* the .npmrc file to provide different authentication tokens for different registry.
* However, if the environment variable is undefined, it expands to an empty string, which
* produces a valid-looking mapping with an invalid URL that causes an error. Instead,
* we'd prefer to skip that line and continue looking in other places such as the user's
* home directory.
*
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._copyNpmrcFile()
*/
function _copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath) {
console.log(`Copying ${sourceNpmrcPath} --> ${targetNpmrcPath}`); // Verbose
let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n');
npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim());
const resultLines = [];
// Trim out lines that reference environment variables that aren't defined
for (const line of npmrcFileLines) {
// This finds environment variable tokens that look like "${VAR_NAME}"
const regex = /\$\{([^\}]+)\}/g;
const environmentVariables = line.match(regex);
let lineShouldBeTrimmed = false;
if (environmentVariables) {
for (const token of environmentVariables) {
// Remove the leading "${" and the trailing "}" from the token
const environmentVariableName = token.substring(2, token.length - 1);
if (!process.env[environmentVariableName]) {
lineShouldBeTrimmed = true;
break;
}
}
}
if (lineShouldBeTrimmed) {
// Example output:
// "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}"
resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line);
}
else {
resultLines.push(line);
}
}
fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL));
}
/**
* syncNpmrc() copies the .npmrc file to the target folder, and also trims unusable lines from the .npmrc file.
* If the source .npmrc file not exist, then syncNpmrc() will delete an .npmrc that is found in the target folder.
*
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc()
*/
function _syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder, useNpmrcPublish) {
const sourceNpmrcPath = path.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish');
const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc');
try {
if (fs.existsSync(sourceNpmrcPath)) {
_copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath);
}
else if (fs.existsSync(targetNpmrcPath)) {
// If the source .npmrc doesn't exist and there is one in the target, delete the one in the target
console.log(`Deleting ${targetNpmrcPath}`); // Verbose
fs.unlinkSync(targetNpmrcPath);
}
}
catch (e) {
throw new Error(`Error syncing .npmrc file: ${e}`);
}
}
let _npmPath = undefined;
/**
* Get the absolute path to the npm executable
*/
function getNpmPath() {
if (!_npmPath) {
try {
if (os.platform() === 'win32') {
// We're on Windows
const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString();
const lines = whereOutput.split(os.EOL).filter((line) => !!line);
// take the last result, we are looking for a .cmd command
// see https://github.com/microsoft/rushstack/issues/759
_npmPath = lines[lines.length - 1];
}
else {
// We aren't on Windows - assume we're on *NIX or Darwin
_npmPath = childProcess.execSync('which npm', { stdio: [] }).toString();
}
}
catch (e) {
throw new Error(`Unable to determine the path to the NPM tool: ${e}`);
}
_npmPath = _npmPath.trim();
if (!fs.existsSync(_npmPath)) {
throw new Error('The NPM executable does not exist');
}
}
return _npmPath;
}
exports.getNpmPath = getNpmPath;
function _ensureFolder(folderPath) {
if (!fs.existsSync(folderPath)) {
const parentDir = path.dirname(folderPath);
_ensureFolder(parentDir);
fs.mkdirSync(folderPath);
}
}
/**
* Create missing directories under the specified base directory, and return the resolved directory.
*
* Does not support "." or ".." path segments.
* Assumes the baseFolder exists.
*/
function _ensureAndJoinPath(baseFolder, ...pathSegments) {
let joinedPath = baseFolder;
try {
for (let pathSegment of pathSegments) {
pathSegment = pathSegment.replace(/[\\\/]/g, '+');
joinedPath = path.join(joinedPath, pathSegment);
if (!fs.existsSync(joinedPath)) {
fs.mkdirSync(joinedPath);
}
}
}
catch (e) {
throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`);
}
return joinedPath;
}
function _getRushTempFolder(rushCommonFolder) {
const rushTempFolder = process.env[RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME];
if (rushTempFolder !== undefined) {
_ensureFolder(rushTempFolder);
return rushTempFolder;
}
else {
return _ensureAndJoinPath(rushCommonFolder, 'temp');
}
}
/**
* Resolve a package specifier to a static version
*/
function _resolvePackageVersion(rushCommonFolder, { name, version }) {
if (!version) {
version = '*'; // If no version is specified, use the latest version
}
if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) {
// If the version contains only characters that we recognize to be used in static version specifiers,
// pass the version through
return version;
}
else {
// version resolves to
try {
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
_syncNpmrc(sourceNpmrcFolder, rushTempFolder);
const npmPath = getNpmPath();
// This returns something that looks like:
// @microsoft/rush@3.0.0 '3.0.0'
// @microsoft/rush@3.0.1 '3.0.1'
// ...
// @microsoft/rush@3.0.20 '3.0.20'
// <blank line>
const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], {
cwd: rushTempFolder,
stdio: []
});
if (npmVersionSpawnResult.status !== 0) {
throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`);
}
const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString();
const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line);
const latestVersion = versionLines[versionLines.length - 1];
if (!latestVersion) {
throw new Error('No versions found for the specified version range.');
}
const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/);
if (!versionMatches) {
throw new Error(`Invalid npm output ${latestVersion}`);
}
return versionMatches[1];
}
catch (e) {
throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`);
}
}
}
let _rushJsonFolder;
/**
* Find the absolute path to the folder containing rush.json
*/
function findRushJsonFolder() {
if (!_rushJsonFolder) {
let basePath = __dirname;
let tempPath = __dirname;
do {
const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME);
if (fs.existsSync(testRushJsonPath)) {
_rushJsonFolder = basePath;
break;
}
else {
basePath = tempPath;
}
} while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root
if (!_rushJsonFolder) {
throw new Error('Unable to find rush.json.');
}
}
return _rushJsonFolder;
}
exports.findRushJsonFolder = findRushJsonFolder;
/**
* Detects if the package in the specified directory is installed
*/
function _isPackageAlreadyInstalled(packageInstallFolder) {
try {
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
if (!fs.existsSync(flagFilePath)) {
return false;
}
const fileContents = fs.readFileSync(flagFilePath).toString();
return fileContents.trim() === process.version;
}
catch (e) {
return false;
}
}
/**
* Removes the following files and directories under the specified folder path:
* - installed.flag
* -
* - node_modules
*/
function _cleanInstallFolder(rushTempFolder, packageInstallFolder) {
try {
const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
if (fs.existsSync(flagFile)) {
fs.unlinkSync(flagFile);
}
const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json');
if (fs.existsSync(packageLockFile)) {
fs.unlinkSync(packageLockFile);
}
const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
if (fs.existsSync(nodeModulesFolder)) {
const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler', `install-run-${Date.now().toString()}`);
fs.renameSync(nodeModulesFolder, rushRecyclerFolder);
}
}
catch (e) {
throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`);
}
}
function _createPackageJson(packageInstallFolder, name, version) {
try {
const packageJsonContents = {
'name': 'ci-rush',
'version': '0.0.0',
'dependencies': {
[name]: version
},
'description': 'DON\'T WARN',
'repository': 'DON\'T WARN',
'license': 'MIT'
};
const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2));
}
catch (e) {
throw new Error(`Unable to create package.json: ${e}`);
}
}
/**
* Run "npm install" in the package install folder.
*/
function _installPackage(packageInstallFolder, name, version) {
try {
console.log(`Installing ${name}...`);
const npmPath = getNpmPath();
const result = childProcess.spawnSync(npmPath, ['install'], {
stdio: 'inherit',
cwd: packageInstallFolder,
env: process.env
});
if (result.status !== 0) {
throw new Error('"npm install" encountered an error');
}
console.log(`Successfully installed ${name}@${version}`);
}
catch (e) {
throw new Error(`Unable to install package: ${e}`);
}
}
/**
* Get the ".bin" path for the package.
*/
function _getBinPath(packageInstallFolder, binName) {
const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
const resolvedBinName = (os.platform() === 'win32') ? `${binName}.cmd` : binName;
return path.resolve(binFolderPath, resolvedBinName);
}
/**
* Write a flag file to the package's install directory, signifying that the install was successful.
*/
function _writeFlagFile(packageInstallFolder) {
try {
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
fs.writeFileSync(flagFilePath, process.version);
}
catch (e) {
throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
}
}
function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) {
const rushJsonFolder = findRushJsonFolder();
const rushCommonFolder = path.join(rushJsonFolder, 'common');
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`);
if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
// The package isn't already installed
_cleanInstallFolder(rushTempFolder, packageInstallFolder);
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
_syncNpmrc(sourceNpmrcFolder, packageInstallFolder);
_createPackageJson(packageInstallFolder, packageName, packageVersion);
_installPackage(packageInstallFolder, packageName, packageVersion);
_writeFlagFile(packageInstallFolder);
}
const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
const statusMessageLine = new Array(statusMessage.length + 1).join('-');
console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL);
const binPath = _getBinPath(packageInstallFolder, packageBinName);
const result = childProcess.spawnSync(binPath, packageBinArgs, {
stdio: 'inherit',
cwd: process.cwd(),
env: process.env
});
if (result.status !== null) {
return result.status;
}
else {
throw result.error || new Error('An unknown error occurred.');
}
}
exports.installAndRun = installAndRun;
function runWithErrorAndStatusCode(fn) {
process.exitCode = 1;
try {
const exitCode = fn();
process.exitCode = exitCode;
}
catch (e) {
console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL);
}
}
exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode;
function _run() {
const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* qrcode@^1.2.0 */ packageBinName, /* qrcode */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv;
if (!nodePath) {
throw new Error('Unexpected exception: could not detect node path');
}
if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') {
// If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control
// to the script that (presumably) imported this file
return;
}
if (process.argv.length < 4) {
console.log('Usage: install-run.js <package>@<version> <command> [args...]');
console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io');
process.exit(1);
}
runWithErrorAndStatusCode(() => {
const rushJsonFolder = findRushJsonFolder();
const rushCommonFolder = _ensureAndJoinPath(rushJsonFolder, 'common');
const packageSpecifier = _parsePackageSpecifier(rawPackageSpecifier);
const name = packageSpecifier.name;
const version = _resolvePackageVersion(rushCommonFolder, packageSpecifier);
if (packageSpecifier.version !== version) {
console.log(`Resolved to ${name}@${version}`);
}
return installAndRun(name, version, packageBinName, packageBinArgs);
});
}
_run();
//# sourceMappingURL=install-run.js.map

View File

@@ -1,8 +0,0 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "./common.tsconfig.json",
"compilerOptions": {
"declaration": false,
"strict": true
}
}

View File

@@ -1,4 +0,0 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "./common.tsconfig.json"
}

View File

@@ -1,18 +0,0 @@
{
"name": "typescript-config",
"description": "TypeScript configurations",
"author": "GitHub",
"private": true,
"version": "0.0.1",
"publisher": "GitHub",
"repository": {
"type": "git",
"url": "https://github.com/github/vscode-codeql"
},
"scripts": {
"build": "",
"format": ""
},
"devDependencies": {},
"dependencies": {}
}

View File

@@ -1,36 +1,35 @@
module.exports = {
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
project: ['tsconfig.json', './src/**/tsconfig.json'],
sourceType: "module",
project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json"],
},
plugins: ['@typescript-eslint'],
plugins: ["@typescript-eslint"],
env: {
node: true,
es6: true
es6: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
'@typescript-eslint/no-use-before-define': 0,
'@typescript-eslint/no-unused-vars': ["warn", {
"vars": "all",
"args": "none",
"ignoreRestSiblings": false
}],
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-unused-vars": [
"warn",
{
vars: "all",
args: "none",
ignoreRestSiblings: false,
},
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"prefer-const": ["warn", {"destructuring": "all"}],
"indent": "off",
"@typescript-eslint/indent": ["error", 2, {
"SwitchCase": 1,
"FunctionDeclaration": { "body": 1, "parameters": 1 }
}],
"prefer-const": ["warn", { destructuring: "all" }],
indent: "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/no-throw-literal": "error",
"no-useless-escape": 0
"no-useless-escape": 0,
semi: 2,
quotes: ["warn", "single"]
},
};

View File

@@ -1,5 +1,192 @@
# CodeQL for Visual Studio Code: Changelog
## 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)
- When comparing the results of a failed QL test run and the `.expected` file does not exist, an empty `.expected` file is created and compared against the `.actual` file. [#669](https://github.com/github/vscode-codeql/pull/669)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Add more structured output for tests. [#626](https://github.com/github/vscode-codeql/pull/626)
- Whenever the extension restarts, orphaned databases will be cleaned up. These are databases whose files are located inside of the extension's storage area, but are not imported into the workspace.
- After renaming a database, the database list is re-sorted. [#685](https://github.com/github/vscode-codeql/pull/685)
- Add a `codeQl.resultsDisplay.pageSize` setting to configure the number of results displayed in a single results view page. Increase the default page size from 100 to 200. [#686](https://github.com/github/vscode-codeql/pull/686)
- Update the AST Viewer to include edge labels (if available) in addition to the target node labels. So far, only C/C++ databases take advantage of this change. [#688](https://github.com/github/vscode-codeql/pull/688)
## 1.3.6 - 4 November 2020
- Fix URI encoding for databases that were created with special characters in their paths. [#648](https://github.com/github/vscode-codeql/pull/648)
- Disable CodeQL Test commands from the command palette [#667](https://github.com/github/vscode-codeql/pull/667)
- Fix display of booleans in results view. [#657](https://github.com/github/vscode-codeql/pull/657)
- Avoid recursive selection changes in AST Viewer. [#668](https://github.com/github/vscode-codeql/pull/668)
## 1.3.5 - 27 October 2020
- Fix a bug where archived source folders for databases were not showing any contents.
- Fix URI encoding for databases that were created with special characters in their paths.
## 1.3.4 - 22 October 2020
- Add friendly welcome message when the databases view is empty.
- Add open query, open results, and remove query commands in the query history view title bar.
- The maximum number of simultaneous queries launchable by the `CodeQL: Run Queries in Selected Files` command is now configurable by changing the `codeQL.runningQueries.maxQueries` setting.
- Allow simultaneously run queries to be canceled in a single-click.
- Prevent multiple upgrade dialogs from appearing when running simultaneous queries on upgradeable databases.
- Fix sorting of results. Some pages of results would have the wrong sort order and columns.
- Remember previous sort order when reloading query results.
- Fix proper escaping of backslashes in SARIF message strings.
- Allow setting `codeQL.runningQueries.numberOfThreads` and `codeQL.runningTests.numberOfThreads` to 0, (which is interpreted as 'use one thread per core on the machine').
- Clear the problems view of all CodeQL query results when a database is removed.
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
- Remove feature flag for the AST Viewer. For more information on how to use the AST Viewer, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/exploring-the-structure-of-your-source-code.html).
- The `codeQL.runningTests.numberOfThreads` setting is now used correctly when running tests.
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the qlpacks.
- Ensure output of CodeQL test runs includes compilation error messages and test failure messages.
## 1.3.3 - 16 September 2020
- Fix display of raw results entities with label but no url.
- Fix bug where sort order is forgotten when changing raw results page.
- Avoid showing a location link in results view when a result item has an empty location.
## 1.3.2 - 12 August 2020
- Fix error with choosing qlpack search path.
- Fix pagination when there are no results.
- Suppress database downloaded from URL message when action canceled.
- Fix QL test discovery to avoid showing duplicate tests in the test explorer.
- Enable pagination of query results
- Add experimental AST Viewer for Go and C++. To enable, add `"codeQL.experimentalAstViewer": true` to the user settings file.
## 1.3.1 - 7 July 2020
- Fix unzipping of large files.
- Ensure compare order is consistent when selecting two queries to compare. The first query selected is always the _from_ query and the query selected later is always the _to_ query.
- Ensure added databases have zipped source locations for databases added as archives or downloaded from the internet.
- Fix bug where it is not possible to add databases starting with `db-*`.
- Change styling of pagination section of the results page.
- Fix display of query text for stored quick queries.
## 1.3.0 - 22 June 2020
- Report error when selecting invalid database.
- Add descriptive message for database archive import failure.
- Respect VS Code's i18n locale setting when formatting dates and sorting strings.
- Allow the opening of large SARIF files externally from VS Code.
- Add new 'CodeQL: Compare Query' command that shows the differences between two queries.
- Allow multiple items in the query history view to be removed in one operation.
- Allow multiple items in the databases view to be removed in one operation.
- Allow multiple items in the databases view to be upgraded in one operation.
- Allow multiple items in the databases view to have their external folders opened.
- Allow all selected queries to be run in one command from the file explorer.
## 1.2.2 - 8 June 2020
- Fix auto-indentation rules.
- Add ability to download platform-specific releases of the CodeQL CLI if they are available.
- Fix handling of downloading prerelease versions of the CodeQL CLI.
- Add pagination for displaying non-interpreted results.
## 1.2.1 - 29 May 2020
- Better formatting and autoindentation when adding QLDoc comments to `.ql` and `.qll` files.
- Allow for more flexibility when opening a database in the workspace. A user can now choose the actual database folder, or the nested `db-*` folder.
- Add query history menu command for viewing corresponding SARIF file.
- Add ability for users to download databases directly from LGTM.com.
## 1.2.0 - 19 May 2020
- Enable 'Go to Definition' and 'Go to References' on source archive
files in CodeQL databases. This is handled by a CodeQL query.
- Fix adding database archive files on Windows.
- Enable adding remote and local database archive files from the
command palette.
## 1.1.5 - 15 May 2020
- Links in results are no longer underlined and monospaced.
- Add the ability to choose a database either from an archive, a folder, or from the internet.
- New icons for commands on the databases view.
## 1.1.4 - 13 May 2020
- Add the ability to download and install databases archives from the internet.

View File

@@ -1,31 +1,31 @@
# 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.
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/Semmle/ql).
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.
- 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.
- 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.
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/master/extensions/ql-vscode/CHANGELOG.md).
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md).
## 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**
### Quick start: Installing and configuring the extension
1. [Install the extension](#installing-the-extension).
1. [Check access to the CodeQL CLI](#checking-access-to-the-codeql-cli).
1. [Clone the CodeQL starter workspace](#cloning-the-codeql-starter-workspace).
**Quick start: Using CodeQL**
### Quick start: Using CodeQL
1. [Import a database from LGTM](#importing-a-database-from-lgtm).
1. [Run a query](#running-a-query).
-----
---
## Quick start: Installing and configuring the extension
@@ -40,19 +40,34 @@ 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.
### Cloning the CodeQL starter workspace
When you're working with CodeQL, you need access to the standard CodeQL libraries and queries.
Initially, we recommend that you clone and use the ready-to-use starter workspace, https://github.com/github/vscode-codeql-starter/.
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
You can easily keep up-to-date with the latest changes to the [CodeQL standard libraries](https://github.com/github/codeql).
If you're using the [CodeQL starter workspace](https://github.com/github/vscode-codeql-starter/), you can pull in the latest standard libraries by running:
```shell
git pull
git submodule update --recursive
```
in the starter workspace directory.
If you're using your own clone of the CodeQL standard libraries, you can do a `git pull` from where you have the libraries checked out.
## Quick start: Using CodeQL
@@ -60,18 +75,15 @@ You can find all the commands contributed by the extension in the Command Palett
### Importing a database from LGTM
While you can use the [CodeQL CLI to create your own databases](https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html), the simplest way to start is by downloading a database from LGTM.com.
While you can use the [CodeQL CLI to create your own databases](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/), the simplest way to start is by downloading a database from LGTM.com.
1. Log in to LGTM.com.
1. Find a project you're interested in and display the **Integrations** tab (for example, [Apache Kafka](https://lgtm.com/projects/g/apache/kafka/ci/)).
1. Scroll to the **CodeQL databases for local analysis** section at the bottom of the page.
1. Download databases for the languages that you want to explore.
1. Unzip the databases.
1. For each database that you want to import:
1. In the VS Code sidebar, go to **CodeQL** > **Databases** and click **+**.
1. Browse to the unzipped database folder (the parent folder that contains `db-<language>` and `src`) and select **Choose database** to add it.
When the import is complete, each CodeQL database is displayed in the CodeQL sidebar under **Databases**.
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. 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.
### Running a query
@@ -79,7 +91,7 @@ The instructions below assume that you're using the CodeQL starter workspace, or
1. Expand the `ql` folder and locate a query to run. The standard queries are grouped by target language and then type, for example: `ql/java/ql/src/Likely Bugs`.
1. Open a query (`.ql`) file.
3. Right-click in the query window and select **CodeQL: Run Query**. Alternatively, open the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**), type `Run Query`, then select **CodeQL: Run Query**.
1. Right-click in the query window and select **CodeQL: Run Query**. Alternatively, open the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**), type `Run Query`, then select **CodeQL: Run Query**.
The CodeQL extension runs the query on the current database using the CLI and reports progress in the bottom right corner of the application.
When the results are ready, they're displayed in the CodeQL Query Results view. Use the dropdown menu to choose between different forms of result output.
@@ -88,13 +100,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).
* [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/).
* [Read how security researchers use CodeQL to find CVEs](https://securitylab.github.com/research).
- [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://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

@@ -1,19 +0,0 @@
'use strict';
require('ts-node').register({});
const gulp = require('gulp');
const {
compileTypeScript,
watchTypeScript,
packageExtension,
compileTextMateGrammar,
copyTestData,
copyViewCss
} = require('build-tasks');
const { compileView } = require('./webpack');
exports.buildWithoutPackage = gulp.parallel(compileTypeScript, compileTextMateGrammar, compileView, copyTestData, copyViewCss);
exports.compileTextMateGrammar = compileTextMateGrammar;
exports.default = gulp.series(exports.buildWithoutPackage, packageExtension);
exports.watchTypeScript = watchTypeScript;
exports.compileTypeScript = compileTypeScript;

View File

@@ -1,28 +0,0 @@
import * as webpack from 'webpack';
import { config } from './webpack.config';
export function compileView(cb: (err?: Error) => void) {
webpack(config).run((error, 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;
}
cb();
});
}

View File

@@ -0,0 +1,16 @@
import * as gulp from 'gulp';
import * as replace from '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

@@ -0,0 +1,73 @@
import * as fs from 'fs-extra';
import * as jsonc from 'jsonc-parser';
import * as path from 'path';
export interface DeployedPackage {
distPath: string;
name: string;
version: string;
}
const packageFiles = [
'.vscodeignore',
'CHANGELOG.md',
'README.md',
'language-configuration.json',
'snippets.json',
'media',
'node_modules',
'out'
];
async function copyPackage(sourcePath: string, destPath: string): Promise<void> {
for (const file of packageFiles) {
console.log(`copying ${path.resolve(sourcePath, file)} to ${path.resolve(destPath, file)}`);
await fs.copy(path.resolve(sourcePath, file), path.resolve(destPath, file));
}
}
export async function deployPackage(packageJsonPath: string): Promise<DeployedPackage> {
try {
const packageJson: any = jsonc.parse(await fs.readFile(packageJsonPath, 'utf8'));
// Default to development build; use flag --release to indicate release build.
const isDevBuild = !process.argv.includes('--release');
const distDir = path.join(__dirname, '../../../dist');
await fs.mkdirs(distDir);
if (isDevBuild) {
// NOTE: rootPackage.name had better not have any regex metacharacters
const oldDevBuildPattern = new RegExp('^' + packageJson.name + '[^/]+-dev[0-9.]+\\.vsix$');
// Dev package filenames are of the form
// vscode-codeql-0.0.1-dev.2019.9.27.19.55.20.vsix
(await fs.readdir(distDir)).filter(name => name.match(oldDevBuildPattern)).map(build => {
console.log(`Deleting old dev build ${build}...`);
fs.unlinkSync(path.join(distDir, build));
});
const now = new Date();
packageJson.version = packageJson.version +
`-dev.${now.getUTCFullYear()}.${now.getUTCMonth() + 1}.${now.getUTCDate()}` +
`.${now.getUTCHours()}.${now.getUTCMinutes()}.${now.getUTCSeconds()}`;
}
const distPath = path.join(distDir, packageJson.name);
await fs.remove(distPath);
await fs.mkdirs(distPath);
await fs.writeFile(path.join(distPath, 'package.json'), JSON.stringify(packageJson, null, 2));
const sourcePath = path.join(__dirname, '..');
console.log(`Copying package '${packageJson.name}' and its dependencies to '${distPath}'...`);
await copyPackage(sourcePath, distPath);
return {
distPath: distPath,
name: packageJson.name,
version: packageJson.version
};
}
catch (e) {
console.error(e);
throw e;
}
}

View File

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

View File

@@ -1,6 +1,6 @@
import * as path from 'path';
import { deployPackage } from './deploy';
import * as child_process from 'child-process-promise';
import * as childProcess from 'child-process-promise';
export async function packageExtension(): Promise<void> {
const deployedPackage = await deployPackage(path.resolve('package.json'));
@@ -9,7 +9,7 @@ export async function packageExtension(): Promise<void> {
'package',
'--out', path.resolve(deployedPackage.distPath, '..', `${deployedPackage.name}-${deployedPackage.version}.vsix`)
];
const proc = child_process.spawn('vsce', args, {
const proc = childProcess.spawn('./node_modules/.bin/vsce', args, {
cwd: deployedPackage.distPath
});
proc.childProcess.stdout!.on('data', (data) => {

View File

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

@@ -1,5 +1,5 @@
import * as gulp from 'gulp';
import * as js_yaml from 'js-yaml';
import * as jsYaml from 'js-yaml';
import * as through from 'through2';
import * as PluginError from 'plugin-error';
import * as Vinyl from 'vinyl';
@@ -13,9 +13,10 @@ import * as Vinyl from 'vinyl';
*/
function replaceReferencesWithStrings(value: string, replacements: Map<string, string>): string {
let result = value;
// eslint-disable-next-line no-constant-condition
while (true) {
const original = result;
for (const key of replacements.keys()) {
for (const key of Array.from(replacements.keys())) {
result = result.replace(`(?#${key})`, `(?:${replacements.get(key)})`);
}
if (result === original) {
@@ -32,7 +33,7 @@ function replaceReferencesWithStrings(value: string, replacements: Map<string, s
*/
function gatherMacros(yaml: any): Map<string, string> {
const macros = new Map<string, string>();
for (var key in yaml.macros) {
for (const key in yaml.macros) {
macros.set(key, yaml.macros[key]);
}
@@ -55,7 +56,7 @@ function getNodeMatchText(rule: any): string {
else if (rule.patterns !== undefined) {
const patterns: string[] = [];
// For a list of patterns, use the disjunction of those patterns.
for (var patternIndex in rule.patterns) {
for (const patternIndex in rule.patterns) {
const pattern = rule.patterns[patternIndex];
if (pattern.include !== null) {
patterns.push('(?' + pattern.include + ')');
@@ -65,7 +66,7 @@ function getNodeMatchText(rule: any): string {
return '(?:' + patterns.join('|') + ')';
}
else {
return ''
return '';
}
}
@@ -78,7 +79,7 @@ function getNodeMatchText(rule: any): string {
*/
function gatherMatchTextForRules(yaml: any): Map<string, string> {
const replacements = new Map<string, string>();
for (var key in yaml.repository) {
for (const key in yaml.repository) {
const node = yaml.repository[key];
replacements.set(key, getNodeMatchText(node));
}
@@ -106,7 +107,7 @@ function visitAllRulesInFile(yaml: any, action: (rule: any) => void) {
* @param action Callback to invoke on each rule.
*/
function visitAllRulesInRuleMap(ruleMap: any, action: (rule: any) => void) {
for (var key in ruleMap) {
for (const key in ruleMap) {
const rule = ruleMap[key];
if ((typeof rule) === 'object') {
action(rule);
@@ -124,7 +125,7 @@ function visitAllRulesInRuleMap(ruleMap: any, action: (rule: any) => void) {
* @param action The transformation to make on each match pattern.
*/
function visitAllMatchesInRule(rule: any, action: (match: any) => any) {
for (var key in rule) {
for (const key in rule) {
switch (key) {
case 'begin':
case 'end':
@@ -184,10 +185,10 @@ function transformFile(yaml: any) {
visitAllRulesInFile(yaml, (rule) => {
visitAllMatchesInRule(rule, (match) => {
if ((typeof match) === 'object') {
for (var key in match) {
for (const key in match) {
return macros.get(key)!.replace('(?#)', `(?:${match[key]})`);
}
throw new Error("No key in macro map.")
throw new Error('No key in macro map.');
}
else {
return match;
@@ -225,7 +226,7 @@ export function transpileTextMateGrammar() {
else if (file.isBuffer()) {
const buf: Buffer = file.contents;
const yamlText: string = buf.toString('utf8');
const jsonData: any = js_yaml.safeLoad(yamlText);
const jsonData: any = jsYaml.safeLoad(yamlText);
transformFile(jsonData);
file.contents = Buffer.from(JSON.stringify(jsonData, null, 2), 'utf8');

View File

@@ -1,15 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"declaration": true,
"strict": true,
"module": "commonjs",
"target": "es2017",
"outDir": "out",
"lib": [
"es6"
],
"lib": ["es6"],
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "../../src",
"rootDir": ".",
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true,
"preserveWatchOutput": true,
@@ -19,12 +18,5 @@
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"../../src/**/*.ts"
],
"exclude": [
"../../node_modules",
"../../test",
"../../**/view"
]
"include": ["*.ts"]
}

View File

@@ -0,0 +1,42 @@
import * as colors from 'ansi-colors';
import * as gulp from 'gulp';
import * as sourcemaps from 'gulp-sourcemaps';
import * as ts from 'gulp-typescript';
function goodReporter(): ts.reporter.Reporter {
return {
error: (error, typescript) => {
if (error.tsFile) {
console.log('[' + colors.gray('gulp-typescript') + '] ' + colors.red(error.fullFilename
+ '(' + (error.startPosition!.line + 1) + ',' + error.startPosition!.character + '): ')
+ 'error TS' + error.diagnostic.code + ': ' + typescript.flattenDiagnosticMessageText(error.diagnostic.messageText, '\n'));
}
else {
console.log(error.message);
}
},
};
}
const tsProject = ts.createProject('tsconfig.json');
export function compileTypeScript() {
return tsProject.src()
.pipe(sourcemaps.init())
.pipe(tsProject(goodReporter()))
.pipe(sourcemaps.write('.', {
includeContent: false,
sourceRoot: '.',
}))
.pipe(gulp.dest('out'));
}
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

@@ -4,21 +4,28 @@ import * as webpack from 'webpack';
export const config: webpack.Configuration = {
mode: 'development',
entry: {
resultsView: './src/view/results.tsx'
resultsView: './src/view/results.tsx',
compareView: './src/compare/view/Compare.tsx',
},
output: {
path: path.resolve(__dirname, '..', 'out'),
filename: "[name].js"
filename: '[name].js'
},
devtool: "inline-source-map",
devtool: 'inline-source-map',
resolve: {
extensions: ['.js', '.ts', '.tsx', '.json']
extensions: ['.js', '.ts', '.tsx', '.json'],
fallback: {
path: require.resolve('path-browserify')
}
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: 'ts-loader',
options: {
configFile: 'src/view/tsconfig.json',
}
},
{
test: /\.less$/,

View File

@@ -0,0 +1,30 @@
import * as webpack from 'webpack';
import { config } from './webpack.config';
export function compileView(cb: (err?: Error) => void) {
webpack(config).run((error, stats) => {
if (error) {
cb(error);
}
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()) {
cb(new Error('Compilation errors detected.'));
return;
}
}
cb();
});
}

View File

@@ -1,72 +1,34 @@
{
"comments": {
// symbol used for single line comment. Remove this entry if your language does not support line comments
"lineComment": "//",
// symbols used for start and end a block comment. Remove this entry if your language does not support block comments
"blockComment": [
"/*",
"*/"
]
},
// symbols used as brackets
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
],
// symbols that are auto closed when typing
"autoClosingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
],
[
"'",
"'"
]
],
// symbols that that can be used to surround a selection
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
],
[
"'",
"'"
]
]
}
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "/**", "close": " */", "notIn": ["string"] }
],
"autoCloseBefore": ";:.=}])> \n\t",
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["'", "'"],
["\"", "\""]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
},
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\.\\<\\>\\/\\?\\s]+)"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,5 @@
<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="M14.5 1H1.5L1 1.5V4.5L1.5 5H2V13.5L2.5 14H13.5L14 13.5V5H14.5L15 4.5V1.5L14.5 1ZM13.5 4H2.5H2V2H14V4H13.5ZM3 13V5H13V13H3ZM11 7H5V8H11V7Z" fill="#C5C5C5"/>
<line y2="12" x2="8" y1="12" x1="16" stroke-width="1" stroke="green" fill="none"/>
<line y2="8" x2="12" y1="16" x1="12" stroke-width="1" stroke="green" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

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 d="M10 12.6L10.7 13.3L12.3 11.7L13.9 13.3L14.7 12.6L13 11L14.7 9.40005L13.9 8.60005L12.3 10.3L10.7 8.60005L10 9.40005L11.6 11L10 12.6Z" fill="#C5C5C5"/>
<path d="M1 4L15 4L15 3L1 3L1 4Z" fill="#C5C5C5"/>
<path d="M1 7L15 7L15 6L1 6L1 7Z" fill="#C5C5C5"/>
<path d="M9 9.5L9 9L1 9L1 10L9 10L9 9.5Z" fill="#C5C5C5"/>
<path d="M9 13L9 12.5L9 12L1 12L1 13L9 13Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,3 @@
<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="M11.9565 6H12.0064C12.8004 6 13.5618 6.31607 14.1232 6.87868C14.6846 7.44129 15 8.20435 15 9C15 9.79565 14.6846 10.5587 14.1232 11.1213C13.5618 11.6839 12.8004 12 12.0064 12V11C12.5357 11 13.0434 10.7893 13.4176 10.4142C13.7919 10.0391 14.0021 9.53044 14.0021 9C14.0021 8.46957 13.7919 7.96086 13.4176 7.58579C13.0434 7.21072 12.5357 7 12.0064 7H11.0924L10.9687 6.143C10.8938 5.60541 10.6456 5.10711 10.2618 4.72407C9.87801 4.34103 9.37977 4.09427 8.84303 4.02143C8.30629 3.94859 7.76051 4.05365 7.2889 4.3206C6.81729 4.58754 6.44573 5.00173 6.23087 5.5L5.89759 6.262L5.08933 6.073C4.90382 6.02699 4.71364 6.0025 4.52255 6C3.86093 6 3.22641 6.2634 2.75858 6.73224C2.29075 7.20108 2.02792 7.83696 2.02792 8.5C2.02792 9.16304 2.29075 9.79893 2.75858 10.2678C3.22641 10.7366 3.86093 11 4.52255 11H5.02148V12H4.52255C4.02745 12.0043 3.5371 11.903 3.08403 11.7029C2.63096 11.5028 2.22553 11.2084 1.89461 10.8394C1.5637 10.4703 1.31488 10.0349 1.16465 9.56211C1.01442 9.08932 0.966217 8.58992 1.02324 8.09704C1.08026 7.60416 1.24121 7.12906 1.4954 6.70326C1.74959 6.27745 2.09121 5.91068 2.49762 5.62727C2.90402 5.34385 3.36591 5.15027 3.85264 5.05937C4.33938 4.96847 4.83984 4.98232 5.32083 5.1C5.6241 4.40501 6.14511 3.82799 6.80496 3.45635C7.4648 3.08472 8.22753 2.9387 8.9776 3.04044C9.72768 3.14217 10.4242 3.4861 10.9618 4.02014C11.4993 4.55418 11.8485 5.24923 11.9565 6ZM6.70719 11.1214L8.0212 12.4354V7H9.01506V12.3992L10.2929 11.1214L11 11.8285L8.85356 13.9749H8.14645L6.00008 11.8285L6.70719 11.1214Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.23 1H11.77L3.52002 9.25L3.35999 9.46997L1 13.59L2.41003 15L6.53003 12.64L6.75 12.48L15 4.22998V2.77002L13.23 1ZM2.41003 13.59L3.92004 10.59L5.37 12.04L2.41003 13.59ZM6.23999 11.53L4.46997 9.76001L12.47 1.76001L14.24 3.53003L6.23999 11.53Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 14H12.5L12.98 13.63L15.61 6.63L15.13 6H13V3.5L12.5 3H6.70996L5.84998 2.15002L5.5 2H0.5L0 2.5V13.5L0.5 14ZM1 3H5.29004L6.15002 3.84998L6.5 4H12V6H8.5L8.15002 6.15002L7.29004 7H2.5L2.03003 7.33997L1.03003 10.42L1 3ZM12.13 13H1.18994L2.85999 8H7.5L7.84998 7.84998L8.70996 7H14.5L12.13 13Z" fill="#C5C5C5"/>
<line y2="12" x2="8" y1="12" x1="16" stroke-width="1" stroke="green" fill="none"/>
<line y2="8" x2="12" y1="16" x1="12" stroke-width="1" stroke="green" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 586 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M16.010 6.49c-3.885 0-7.167 0.906-9.328 2.813-0.063-0.12-0.109-0.219-0.188-0.339-0.224-0.365-0.438-0.776-1.104-1.188-0.411-0.26-0.87-0.438-1.349-0.516-0.208-0.021-0.422-0.021-0.63 0l0.135-0.016c-1.214 0-1.922 0.724-2.385 1.354-0.458 0.625-0.755 1.328-0.948 2.099-0.38 1.542-0.385 3.536 1.083 5.026 0.766 0.781 1.667 1.151 2.484 1.37 0.156 0.042 0.297 0.052 0.448 0.083 0.531 2.521 2.104 4.656 4.208 5.839v0.005c1.24 0.693 2.417 1.010 3.297 1.349 1.234 0.479 2.536 1 4.052 1.135l0.078 0.005h0.198c1.745 0 3.063-0.703 4.203-1.141 0.875-0.333 2.052-0.641 3.302-1.344 0.578-0.323 1.115-0.719 1.594-1.172 1.318-1.234 2.229-2.839 2.625-4.599 1.115-0.182 2.141-0.719 2.922-1.536 1.464-1.484 1.458-3.479 1.078-5.021-0.193-0.771-0.49-1.474-0.948-2.099-0.458-0.63-1.172-1.354-2.385-1.354l0.135 0.016c-0.208-0.021-0.422-0.021-0.63 0-0.479 0.078-0.938 0.255-1.344 0.516-0.667 0.411-0.88 0.823-1.104 1.182-0.073 0.12-0.12 0.219-0.188 0.333-2.156-1.901-5.432-2.802-9.313-2.802zM16.042 8.313c4.745 0 8.016 1.422 9.411 3.964 0.839-0.323 1.453-2.521 2.146-2.948 0.563-0.344 0.885-0.26 0.885-0.26 1.271 0 2.578 3.729 0.953 5.38-0.859 0.875-2.443 1.12-3.229 1.057-0.063 2.542-1.542 4.833-3.5 5.932-1 0.563-2.068 0.854-3.063 1.234-1.229 0.469-2.38 1.016-3.547 1.016h-0.125c-1.161-0.099-2.318-0.542-3.547-1.016-0.995-0.38-2.068-0.682-3.063-1.24-1.948-1.099-3.427-3.391-3.49-5.927-0.781 0.068-2.385-0.177-3.245-1.057-1.625-1.651-0.318-5.38 0.948-5.38 0 0 0.328-0.083 0.885 0.26 0.698 0.427 1.318 2.646 2.161 2.953 1.391-2.547 4.667-3.969 9.417-3.969zM10.875 11.422c-2.276-0.042-4.146 1.792-4.146 4.068 0 2.281 1.87 4.115 4.146 4.073 5.328-0.099 5.328-8.047 0-8.141zM21.208 11.422c-5.427 0-5.427 8.141 0 8.141s5.427-8.141 0-8.141zM11.453 13.708c2.349 0.063 2.349 3.552 0 3.615-1.182 0-2.042-1.115-1.75-2.255 0.318 0.771 1.469 0.547 1.464-0.292 0-0.406-0.318-0.745-0.729-0.76 0.302-0.203 0.656-0.313 1.016-0.307zM20.641 13.708c2.344 0.063 2.344 3.552 0 3.615-1.182 0-2.047-1.115-1.755-2.255 0.229 0.552 0.979 0.641 1.328 0.146 0.344-0.49 0.010-1.167-0.589-1.193 0.297-0.208 0.651-0.313 1.016-0.313zM15.359 19.906c-0.318 0.026-0.5 0.193-0.5 0.635 0 0.281 0.182 0.484 0.5 0.484 0.229 0 0.266-0.323 0.047-0.375-0.031-0.005-0.172-0.057-0.172-0.182 0-0.12 0-0.167 0.24-0.198 0.104-0.016 0.156-0.141 0.125-0.24s-0.125-0.135-0.24-0.125zM16.724 19.906c-0.115-0.005-0.208 0.026-0.24 0.125s0.021 0.224 0.125 0.24c0.24 0.031 0.24 0.078 0.24 0.198 0 0.125-0.141 0.177-0.172 0.182-0.219 0.052-0.182 0.375 0.042 0.375 0.323 0 0.51-0.203 0.51-0.484 0-0.443-0.188-0.609-0.505-0.635z" fill="#C5C5C5"/>
<line y2="24" x2="16" y1="26" x1="32" stroke-width="2" stroke="green" fill="none"/>
<line y2="16" x2="24" y1="32" x1="24" stroke-width="1" stroke="green" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,3 @@
<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="M2 2H14L15 3V13L14 14H2L1 13V3L2 2ZM2 13H14V3H2V13ZM13 4H3V7H13V4ZM12 6H4V5H12V6ZM9 12H13V8H9V12ZM10 9H12V11H10V9ZM7 8H3V9H7V8ZM3 11H7V12H3V11Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

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,3 @@
<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="M10 3H12H13V4H12V13L11 14H4L3 13V4H2V3H5V2C5 1.73478 5.10531 1.48038 5.29285 1.29285C5.48038 1.10531 5.73478 1 6 1H9C9.26522 1 9.51962 1.10531 9.70715 1.29285C9.89469 1.48038 10 1.73478 10 2V3ZM9 2H6V3H9V2ZM4 13H11V4H4V13ZM6 5H5V12H6V5ZM7 5H8V12H7V5ZM9 5H10V12H9V5Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -0,0 +1,5 @@
<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="M14.5 1H1.5L1 1.5V4.5L1.5 5H2V13.5L2.5 14H13.5L14 13.5V5H14.5L15 4.5V1.5L14.5 1ZM13.5 4H2.5H2V2H14V4H13.5ZM3 13V5H13V13H3ZM11 7H5V8H11V7Z" fill="#424242"/>
<line y2="12" x2="8" y1="12" x1="16" stroke-width="1" stroke="green" fill="none"/>
<line y2="8" x2="12" y1="16" x1="12" stroke-width="1" stroke="green" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

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 d="M10.0001 12.6L10.7001 13.3L12.3001 11.7L13.9001 13.3L14.7001 12.6L13.0001 11L14.7001 9.40005L13.9001 8.60005L12.3001 10.3L10.7001 8.60005L10.0001 9.40005L11.6001 11L10.0001 12.6Z" fill="#424242"/>
<path d="M1.00006 4L15.0001 4L15.0001 3L1.00006 3L1.00006 4Z" fill="#424242"/>
<path d="M1.00006 7L15.0001 7L15.0001 6L1.00006 6L1.00006 7Z" fill="#424242"/>
<path d="M9.00006 9.5L9.00006 9L1.00006 9L1.00006 10L9.00006 10L9.00006 9.5Z" fill="#424242"/>
<path d="M9.00006 13L9.00006 12.5L9.00006 12L1.00006 12L1.00006 13L9.00006 13Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 658 B

View File

@@ -0,0 +1,3 @@
<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="M11.9565 6H12.0064C12.8004 6 13.5618 6.31607 14.1232 6.87868C14.6846 7.44129 15 8.20435 15 9C15 9.79565 14.6846 10.5587 14.1232 11.1213C13.5618 11.6839 12.8004 12 12.0064 12V11C12.5357 11 13.0434 10.7893 13.4176 10.4142C13.7919 10.0391 14.0021 9.53044 14.0021 9C14.0021 8.46957 13.7919 7.96086 13.4176 7.58579C13.0434 7.21072 12.5357 7 12.0064 7H11.0924L10.9687 6.143C10.8938 5.60541 10.6456 5.10711 10.2618 4.72407C9.87801 4.34103 9.37977 4.09427 8.84303 4.02143C8.30629 3.94859 7.76051 4.05365 7.2889 4.3206C6.81729 4.58754 6.44573 5.00173 6.23087 5.5L5.89759 6.262L5.08933 6.073C4.90382 6.02699 4.71364 6.0025 4.52255 6C3.86093 6 3.22641 6.2634 2.75858 6.73224C2.29075 7.20108 2.02792 7.83696 2.02792 8.5C2.02792 9.16304 2.29075 9.79893 2.75858 10.2678C3.22641 10.7366 3.86093 11 4.52255 11H5.02148V12H4.52255C4.02745 12.0043 3.5371 11.903 3.08403 11.7029C2.63096 11.5028 2.22553 11.2084 1.89461 10.8394C1.5637 10.4703 1.31488 10.0349 1.16465 9.56211C1.01442 9.08932 0.966217 8.58992 1.02324 8.09704C1.08026 7.60416 1.24121 7.12906 1.4954 6.70326C1.74959 6.27745 2.09121 5.91068 2.49762 5.62727C2.90402 5.34385 3.36591 5.15027 3.85264 5.05937C4.33938 4.96847 4.83984 4.98232 5.32083 5.1C5.6241 4.40501 6.14511 3.82799 6.80496 3.45635C7.4648 3.08472 8.22753 2.9387 8.9776 3.04044C9.72768 3.14217 10.4242 3.4861 10.9618 4.02014C11.4993 4.55418 11.8485 5.24923 11.9565 6ZM6.70719 11.1214L8.0212 12.4354V7H9.01506V12.3992L10.2929 11.1214L11 11.8285L8.85356 13.9749H8.14645L6.00008 11.8285L6.70719 11.1214Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2302 1H11.7703L3.52026 9.25L3.36023 9.46997L1.00024 13.59L2.41028 15L6.53027 12.64L6.75024 12.48L15.0002 4.22998V2.77002L13.2302 1ZM2.41028 13.59L3.92029 10.59L5.37024 12.04L2.41028 13.59ZM6.24023 11.53L4.47021 9.76001L12.4702 1.76001L14.2402 3.53003L6.24023 11.53Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 399 B

View File

@@ -0,0 +1,12 @@
<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 d="M0.499817 14H12.4998L12.9798 13.63L15.6098 6.63L15.1298 6H12.9998V3.5L12.4998 3H6.70978L5.84979 2.15002L5.49982 2H0.499817L-0.000183105 2.5V13.5L0.499817 14ZM0.999817 3H5.28986L6.14984 3.84998L6.49982 4H11.9998V6H8.49982L8.14984 6.15002L7.28986 7H2.49982L2.02985 7.33997L1.02985 10.42L0.999817 3ZM12.1298 13H1.18976L2.8598 8H7.49982L7.84979 7.84998L8.70978 7H14.4998L12.1298 13Z" fill="#424242"/>
<line y2="12" x2="8" y1="12" x1="16" stroke-width="1" stroke="green" fill="none"/>
<line y2="8" x2="12" y1="16" x1="12" stroke-width="1" stroke="green" fill="none"/>
</g>
<defs>
<clipPath id="clip0">
<path d="M-0.000183105 0H15.9998V16H-0.000183105V0Z" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 824 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<path d="M16.010 6.49c-3.885 0-7.167 0.906-9.328 2.813-0.063-0.12-0.109-0.219-0.188-0.339-0.224-0.365-0.438-0.776-1.104-1.188-0.411-0.26-0.87-0.438-1.349-0.516-0.208-0.021-0.422-0.021-0.63 0l0.135-0.016c-1.214 0-1.922 0.724-2.385 1.354-0.458 0.625-0.755 1.328-0.948 2.099-0.38 1.542-0.385 3.536 1.083 5.026 0.766 0.781 1.667 1.151 2.484 1.37 0.156 0.042 0.297 0.052 0.448 0.083 0.531 2.521 2.104 4.656 4.208 5.839v0.005c1.24 0.693 2.417 1.010 3.297 1.349 1.234 0.479 2.536 1 4.052 1.135l0.078 0.005h0.198c1.745 0 3.063-0.703 4.203-1.141 0.875-0.333 2.052-0.641 3.302-1.344 0.578-0.323 1.115-0.719 1.594-1.172 1.318-1.234 2.229-2.839 2.625-4.599 1.115-0.182 2.141-0.719 2.922-1.536 1.464-1.484 1.458-3.479 1.078-5.021-0.193-0.771-0.49-1.474-0.948-2.099-0.458-0.63-1.172-1.354-2.385-1.354l0.135 0.016c-0.208-0.021-0.422-0.021-0.63 0-0.479 0.078-0.938 0.255-1.344 0.516-0.667 0.411-0.88 0.823-1.104 1.182-0.073 0.12-0.12 0.219-0.188 0.333-2.156-1.901-5.432-2.802-9.313-2.802zM16.042 8.313c4.745 0 8.016 1.422 9.411 3.964 0.839-0.323 1.453-2.521 2.146-2.948 0.563-0.344 0.885-0.26 0.885-0.26 1.271 0 2.578 3.729 0.953 5.38-0.859 0.875-2.443 1.12-3.229 1.057-0.063 2.542-1.542 4.833-3.5 5.932-1 0.563-2.068 0.854-3.063 1.234-1.229 0.469-2.38 1.016-3.547 1.016h-0.125c-1.161-0.099-2.318-0.542-3.547-1.016-0.995-0.38-2.068-0.682-3.063-1.24-1.948-1.099-3.427-3.391-3.49-5.927-0.781 0.068-2.385-0.177-3.245-1.057-1.625-1.651-0.318-5.38 0.948-5.38 0 0 0.328-0.083 0.885 0.26 0.698 0.427 1.318 2.646 2.161 2.953 1.391-2.547 4.667-3.969 9.417-3.969zM10.875 11.422c-2.276-0.042-4.146 1.792-4.146 4.068 0 2.281 1.87 4.115 4.146 4.073 5.328-0.099 5.328-8.047 0-8.141zM21.208 11.422c-5.427 0-5.427 8.141 0 8.141s5.427-8.141 0-8.141zM11.453 13.708c2.349 0.063 2.349 3.552 0 3.615-1.182 0-2.042-1.115-1.75-2.255 0.318 0.771 1.469 0.547 1.464-0.292 0-0.406-0.318-0.745-0.729-0.76 0.302-0.203 0.656-0.313 1.016-0.307zM20.641 13.708c2.344 0.063 2.344 3.552 0 3.615-1.182 0-2.047-1.115-1.755-2.255 0.229 0.552 0.979 0.641 1.328 0.146 0.344-0.49 0.010-1.167-0.589-1.193 0.297-0.208 0.651-0.313 1.016-0.313zM15.359 19.906c-0.318 0.026-0.5 0.193-0.5 0.635 0 0.281 0.182 0.484 0.5 0.484 0.229 0 0.266-0.323 0.047-0.375-0.031-0.005-0.172-0.057-0.172-0.182 0-0.12 0-0.167 0.24-0.198 0.104-0.016 0.156-0.141 0.125-0.24s-0.125-0.135-0.24-0.125zM16.724 19.906c-0.115-0.005-0.208 0.026-0.24 0.125s0.021 0.224 0.125 0.24c0.24 0.031 0.24 0.078 0.24 0.198 0 0.125-0.141 0.177-0.172 0.182-0.219 0.052-0.182 0.375 0.042 0.375 0.323 0 0.51-0.203 0.51-0.484 0-0.443-0.188-0.609-0.505-0.635z" fill="#424242"/>
<line y2="24" x2="16" y1="26" x1="32" stroke-width="2" stroke="green" fill="none"/>
<line y2="16" x2="24" y1="32" x1="24" stroke-width="1" stroke="green" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,3 @@
<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="M2.00024 2H14.0002L15.0002 3V13L14.0002 14H2.00024L1.00024 13V3L2.00024 2ZM2.00024 13H14.0002V3H2.00024V13ZM13.0002 4H3.00024V7H13.0002V4ZM12.0002 6H4.00024V5H12.0002V6ZM9.00024 12H13.0002V8H9.00024V12ZM10.0002 9H12.0002V11H10.0002V9ZM7.00024 8H3.00024V9H7.00024V8ZM3.00024 11H7.00024V12H3.00024V11Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

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

@@ -0,0 +1,3 @@
<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="M10.0002 3H12.0002H13.0002V4H12.0002V13L11.0002 14H4.00024L3.00024 13V4H2.00024V3H5.00024V2C5.00024 1.73478 5.10555 1.48038 5.29309 1.29285C5.48063 1.10531 5.73503 1 6.00024 1H9.00024C9.26546 1 9.51986 1.10531 9.7074 1.29285C9.89493 1.48038 10.0002 1.73478 10.0002 2V3ZM9.00024 2H6.00024V3H9.00024V2ZM4.00024 13H11.0002V4H4.00024V13ZM6.00024 5H5.00024V12H6.00024V5ZM7.00024 5H8.00024V12H7.00024V5ZM9.00024 5H10.0002V12H9.00024V5Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 599 B

20629
extensions/ql-vscode/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.1.4",
"version": "1.4.8",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -13,7 +13,7 @@
"url": "https://github.com/github/vscode-codeql"
},
"engines": {
"vscode": "^1.39.0"
"vscode": "^1.43.0"
},
"categories": [
"Programming Languages"
@@ -25,11 +25,20 @@
"onLanguage:ql",
"onView:codeQLDatabases",
"onView:codeQLQueryHistory",
"onView:codeQLAstViewer",
"onView:test-explorer",
"onCommand:codeQL.checkForUpdatesToCLI",
"onCommand:codeQL.chooseDatabase",
"onCommand:codeQLDatabases.chooseDatabaseFolder",
"onCommand:codeQLDatabases.chooseDatabaseArchive",
"onCommand:codeQLDatabases.chooseDatabaseInternet",
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
"onCommand:codeQL.setCurrentDatabase",
"onCommand:codeQL.downloadDatabase",
"onCommand:codeQL.viewAst",
"onCommand:codeQL.openReferencedFile",
"onCommand:codeQL.chooseDatabaseFolder",
"onCommand:codeQL.chooseDatabaseArchive",
"onCommand:codeQL.chooseDatabaseInternet",
"onCommand:codeQL.chooseDatabaseLgtm",
"onCommand:codeQLDatabases.chooseDatabase",
"onCommand:codeQLDatabases.setCurrentDatabase",
"onCommand:codeQL.quickQuery",
@@ -98,6 +107,12 @@
"path": "./out/syntaxes/dbscheme.tmLanguage.json"
}
],
"snippets": [
{
"language": "ql",
"path": "./snippets.json"
}
],
"configuration": {
"type": "object",
"title": "CodeQL",
@@ -106,15 +121,30 @@
"scope": "machine",
"type": "string",
"default": "",
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. This overrides all other CodeQL CLI settings."
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. If empty, the extension will look for a CodeQL executable on your shell PATH, or if CodeQL is not on your PATH, download and manage its own CodeQL executable."
},
"codeQL.runningQueries.numberOfThreads": {
"type": "integer",
"default": 1,
"minimum": 1,
"minimum": 0,
"maximum": 1024,
"description": "Number of threads for running queries."
},
"codeQL.runningQueries.saveCache": {
"type": "boolean",
"default": false,
"scope": "window",
"description": "Aggressively save intermediate results to the disk cache. This may speed up subsequent queries if they are similar. Be aware that using this option will greatly increase disk usage and initial evaluation time."
},
"codeQL.runningQueries.cacheSize": {
"type": [
"integer",
"null"
],
"default": null,
"minimum": 1024,
"description": "Maximum size of the disk cache (in MB). Leave blank to allow the evaluator to automatically adjust the size of the disk cache based on the size of the codebase and the complexity of the queries being executed."
},
"codeQL.runningQueries.timeout": {
"type": [
"integer",
@@ -144,18 +174,46 @@
"default": false,
"description": "Enable automatically saving a modified query file when running a query."
},
"codeQL.runningQueries.maxQueries": {
"type": "integer",
"default": 20,
"description": "Max number of simultaneous queries to run using the 'CodeQL: Run Queries' command."
},
"codeQL.resultsDisplay.pageSize": {
"type": "integer",
"default": 200,
"description": "Max number of query results to display per page in the results view."
},
"codeQL.queryHistory.format": {
"type": "string",
"default": "[%t] %q on %d - %s",
"description": "Default string for how to label query history items. %t is the time of the query, %q is the query name, %d is the database name, and %s is a status string."
"default": "%q on %d - %s, %r result count [%t]",
"description": "Default string for how to label query history items. %t is the time of the query, %q is the query name, %d is the database name, %r is the number of results, and %s is a status string."
},
"codeQL.runningTests.additionalTestArguments": {
"scope": "machine",
"type": "array",
"default": [],
"markdownDescription": "Additional command line arguments to pass to the CLI when [running tests](https://codeql.github.com/docs/codeql-cli/manual/test-run/). This setting should be an array of strings, each containing an argument to be passed."
},
"codeQL.runningTests.numberOfThreads": {
"scope": "window",
"type": "integer",
"default": 1,
"minimum": 1,
"minimum": 0,
"maximum": 1024,
"description": "Number of threads for running CodeQL tests."
},
"codeQL.telemetry.enableTelemetry": {
"type": "boolean",
"default": false,
"scope": "application",
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)"
},
"codeQL.telemetry.logTelemetry": {
"type": "boolean",
"default": false,
"scope": "application",
"description": "Specifies whether or not to write telemetry events to the extension log."
}
}
},
@@ -164,26 +222,74 @@
"command": "codeQL.runQuery",
"title": "CodeQL: Run Query"
},
{
"command": "codeQL.runQueries",
"title": "CodeQL: Run Queries in Selected Files"
},
{
"command": "codeQL.quickEval",
"title": "CodeQL: Quick Evaluation"
},
{
"command": "codeQL.openReferencedFile",
"title": "CodeQL: Open Referenced File"
},
{
"command": "codeQL.quickQuery",
"title": "CodeQL: Quick Query"
},
{
"command": "codeQL.chooseDatabase",
"title": "CodeQL: Choose Database",
"command": "codeQL.openDocumentation",
"title": "CodeQL: Open Documentation"
},
{
"command": "codeQL.copyVersion",
"title": "CodeQL: Copy Version Information"
},
{
"command": "codeQLDatabases.chooseDatabaseFolder",
"title": "Choose Database from Folder",
"icon": {
"light": "media/light/plus.svg",
"dark": "media/dark/plus.svg"
"light": "media/light/folder-opened-plus.svg",
"dark": "media/dark/folder-opened-plus.svg"
}
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"title": "Delete unused databases"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"title": "Choose Database from Archive",
"icon": {
"light": "media/light/archive-plus.svg",
"dark": "media/dark/archive-plus.svg"
}
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"title": "Download Database",
"icon": {
"light": "media/light/cloud-download.svg",
"dark": "media/dark/cloud-download.svg"
}
},
{
"command": "codeQLDatabases.chooseDatabaseLgtm",
"title": "Download from LGTM",
"icon": {
"light": "media/light/lgtm-plus.svg",
"dark": "media/dark/lgtm-plus.svg"
}
},
{
"command": "codeQL.setCurrentDatabase",
"title": "CodeQL: Set Current Database"
},
{
"command": "codeQL.viewAst",
"title": "CodeQL: View AST"
},
{
"command": "codeQL.upgradeCurrentDatabase",
"title": "CodeQL: Upgrade Current Database"
@@ -213,8 +319,20 @@
"title": "Show Database Directory"
},
{
"command": "codeQL.downloadDatabase",
"title": "CodeQL: Download database"
"command": "codeQL.chooseDatabaseFolder",
"title": "CodeQL: Choose Database from Folder"
},
{
"command": "codeQL.chooseDatabaseArchive",
"title": "CodeQL: Choose Database from Archive"
},
{
"command": "codeQL.chooseDatabaseInternet",
"title": "CodeQL: Download Database"
},
{
"command": "codeQL.chooseDatabaseLgtm",
"title": "CodeQL: Download Database from LGTM"
},
{
"command": "codeQLDatabases.sortByName",
@@ -238,15 +356,51 @@
},
{
"command": "codeQLQueryHistory.openQuery",
"title": "Open Query"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"title": "Remove History Item"
"title": "Open the query that produced these results",
"icon": {
"light": "media/light/edit.svg",
"dark": "media/dark/edit.svg"
}
},
{
"command": "codeQLQueryHistory.itemClicked",
"title": "Query History Item"
"title": "Open Query Results",
"icon": {
"light": "media/light/preview.svg",
"dark": "media/dark/preview.svg"
}
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"title": "Remove History Item(s)",
"icon": {
"light": "media/light/trash.svg",
"dark": "media/dark/trash.svg"
}
},
{
"command": "codeQLQueryHistory.sortByName",
"title": "Sort by Name",
"icon": {
"light": "media/light/sort-alpha.svg",
"dark": "media/dark/sort-alpha.svg"
}
},
{
"command": "codeQLQueryHistory.sortByDate",
"title": "Sort by Query Date",
"icon": {
"light": "media/light/sort-date.svg",
"dark": "media/dark/sort-date.svg"
}
},
{
"command": "codeQLQueryHistory.sortByCount",
"title": "Sort by Results Count",
"icon": {
"light": "media/light/sort-num.svg",
"dark": "media/dark/sort-num.svg"
}
},
{
"command": "codeQLQueryHistory.showQueryLog",
@@ -256,6 +410,26 @@
"command": "codeQLQueryHistory.showQueryText",
"title": "Show Query Text"
},
{
"command": "codeQLQueryHistory.viewCsvResults",
"title": "View Results (CSV)"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"title": "View Results (SARIF)"
},
{
"command": "codeQLQueryHistory.viewDil",
"title": "View DIL"
},
{
"command": "codeQLQueryHistory.setLabel",
"title": "Set Label"
},
{
"command": "codeQLQueryHistory.compareWith",
"title": "Compare Results"
},
{
"command": "codeQLQueryResults.nextPathStep",
"title": "CodeQL: Show Next Step on Path"
@@ -264,21 +438,29 @@
"command": "codeQLQueryResults.previousPathStep",
"title": "CodeQL: Show Previous Step on Path"
},
{
"command": "codeQLQueryHistory.setLabel",
"title": "Set Label"
},
{
"command": "codeQL.restartQueryServer",
"title": "CodeQL: Restart Query Server"
},
{
"command": "codeQLTests.showOutputDifferences",
"title": "CodeQL: Show Test Output Differences"
"title": "Show Test Output Differences"
},
{
"command": "codeQLTests.acceptOutput",
"title": "CodeQL: Accept Test Output"
"title": "Accept Test Output"
},
{
"command": "codeQLAstViewer.gotoCode",
"title": "Go To Code"
},
{
"command": "codeQLAstViewer.clear",
"title": "Clear AST",
"icon": {
"light": "media/light/clear-all.svg",
"dark": "media/dark/clear-all.svg"
}
}
],
"menus": {
@@ -294,9 +476,59 @@
"group": "navigation"
},
{
"command": "codeQL.chooseDatabase",
"command": "codeQLDatabases.chooseDatabaseFolder",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.chooseDatabaseLgtm",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.openQuery",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.itemClicked",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.removeHistoryItem",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.sortByName",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.sortByDate",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLQueryHistory.sortByCount",
"when": "view == codeQLQueryHistory",
"group": "navigation"
},
{
"command": "codeQLAstViewer.clear",
"when": "view == codeQLAstViewer",
"group": "navigation"
}
],
"view/item/context": [
@@ -340,6 +572,11 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.compareWith",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"group": "9_qlCommands",
@@ -350,6 +587,21 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.viewCsvResults",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
},
{
"command": "codeQLQueryHistory.viewDil",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLTests.showOutputDifferences",
"group": "qltest@1",
@@ -365,12 +617,22 @@
{
"command": "codeQL.setCurrentDatabase",
"group": "9_qlCommands",
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder"
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
},
{
"command": "codeQL.runQuery",
"command": "codeQL.viewAst",
"group": "9_qlCommands",
"when": "resourceLangId == ql && resourceExtname == .ql"
"when": "resourceScheme == codeql-zip-archive"
},
{
"command": "codeQL.runQueries",
"group": "9_qlCommands",
"when": "resourceScheme != codeql-zip-archive"
},
{
"command": "codeQL.openReferencedFile",
"group": "9_qlCommands",
"when": "resourceExtname == .qlref"
}
],
"commandPalette": [
@@ -379,17 +641,25 @@
"when": "resourceLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.downloadDatabase",
"when": "true"
"command": "codeQL.runQueries",
"when": "false"
},
{
"command": "codeQL.quickEval",
"when": "editorLangId == ql"
},
{
"command": "codeQL.openReferencedFile",
"when": "resourceExtname == .qlref"
},
{
"command": "codeQL.setCurrentDatabase",
"when": "false"
},
{
"command": "codeQL.viewAst",
"when": "resourceScheme == codeql-zip-archive"
},
{
"command": "codeQLDatabases.setCurrentDatabase",
"when": "false"
@@ -414,6 +684,30 @@
"command": "codeQLDatabases.removeDatabase",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseFolder",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "false"
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseLgtm",
"when": "false"
},
{
"command": "codeQLDatabases.upgradeDatabase",
"when": "false"
},
{
"command": "codeQLQueryHistory.openQuery",
"when": "false"
@@ -434,9 +728,53 @@
"command": "codeQLQueryHistory.showQueryText",
"when": "false"
},
{
"command": "codeQLQueryHistory.viewCsvResults",
"when": "false"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"when": "false"
},
{
"command": "codeQLQueryHistory.viewDil",
"when": "false"
},
{
"command": "codeQLQueryHistory.setLabel",
"when": "false"
},
{
"command": "codeQLQueryHistory.compareWith",
"when": "false"
},
{
"command": "codeQLQueryHistory.sortByName",
"when": "false"
},
{
"command": "codeQLQueryHistory.sortByDate",
"when": "false"
},
{
"command": "codeQLQueryHistory.sortByCount",
"when": "false"
},
{
"command": "codeQLAstViewer.gotoCode",
"when": "false"
},
{
"command": "codeQLAstViewer.clear",
"when": "false"
},
{
"command": "codeQLTests.acceptOutput",
"when": "false"
},
{
"command": "codeQLTests.showOutputDifferences",
"when": "false"
}
],
"editor/context": [
@@ -444,9 +782,17 @@
"command": "codeQL.runQuery",
"when": "editorLangId == ql && resourceExtname == .ql"
},
{
"command": "codeQL.viewAst",
"when": "resourceScheme == codeql-zip-archive"
},
{
"command": "codeQL.quickEval",
"when": "editorLangId == ql"
},
{
"command": "codeQL.openReferencedFile",
"when": "resourceExtname == .qlref"
}
]
},
@@ -468,9 +814,27 @@
{
"id": "codeQLQueryHistory",
"name": "Query History"
},
{
"id": "codeQLAstViewer",
"name": "AST Viewer"
}
]
}
},
"viewsWelcome": [
{
"view": "codeQLAstViewer",
"contents": "Run the 'CodeQL: View AST' command on an open source file from a CodeQL database.\n[View AST](command:codeQL.viewAst)"
},
{
"view": "codeQLQueryHistory",
"contents": "Run the 'CodeQL: Run Query' command on a QL query.\n[Run Query](command:codeQL.runQuery)"
},
{
"view": "codeQLDatabases",
"contents": "Add a CodeQL database:\n[From a folder](command:codeQLDatabases.chooseDatabaseFolder)\n[From an archive](command:codeQLDatabases.chooseDatabaseArchive)\n[From a URL (as a zip file)](command:codeQLDatabases.chooseDatabaseInternet)\n[From LGTM](command:codeQLDatabases.chooseDatabaseLgtm)"
}
]
},
"scripts": {
"build": "gulp",
@@ -478,96 +842,105 @@
"watch:extension": "tsc --watch",
"test": "mocha --exit -r ts-node/register test/pure-tests/**/*.ts",
"preintegration": "rm -rf ./out/vscode-tests && gulp",
"integration": "node ./out/vscode-tests/run-integration-tests.js",
"integration": "node ./out/vscode-tests/run-integration-tests.js no-workspace,minimal-workspace",
"cli-integration": "npm run preintegration && node ./out/vscode-tests/run-integration-tests.js cli-integration",
"update-vscode": "node ./node_modules/vscode/bin/install",
"postinstall": "node ./node_modules/vscode/bin/install",
"format": "tsfmt -r",
"lint": "eslint src test --ext .ts,.tsx",
"lint-staged": "lint-staged"
"format": "tsfmt -r && eslint src test --ext .ts,.tsx --fix",
"lint": "eslint src test --ext .ts,.tsx --max-warnings=0",
"format-staged": "lint-staged"
},
"dependencies": {
"child-process-promise": "^2.2.1",
"classnames": "~2.2.6",
"fs-extra": "^8.1.0",
"fs-extra": "^9.0.1",
"glob-promise": "^3.4.0",
"js-yaml": "^3.12.0",
"js-yaml": "^3.14.0",
"minimist": "~1.2.5",
"node-fetch": "~2.6.0",
"path-browserify": "^1.0.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"semmle-bqrs": "^0.0.1",
"semmle-io-node": "^0.0.1",
"semmle-vscode-utils": "^0.0.1",
"semver": "~7.3.2",
"tmp": "^0.1.0",
"tmp-promise": "~3.0.2",
"tree-kill": "~1.2.2",
"unzipper": "~0.10.5",
"vscode-extension-telemetry": "^0.1.6",
"vscode-jsonrpc": "^5.0.1",
"vscode-languageclient": "^6.1.3",
"vscode-test-adapter-api": "~1.7.0",
"vscode-test-adapter-util": "~0.7.0",
"minimist": "~1.2.5"
"zip-a-folder": "~0.0.12"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "~7.1.2",
"@types/child-process-promise": "^2.2.1",
"@types/classnames": "~2.2.9",
"@types/fs-extra": "^8.0.0",
"@types/fs-extra": "^9.0.6",
"@types/glob": "^7.1.1",
"@types/google-protobuf": "^3.2.7",
"@types/gulp": "^4.0.6",
"@types/js-yaml": "~3.12.1",
"@types/gulp-replace": "0.0.31",
"@types/gulp-sourcemaps": "0.0.32",
"@types/js-yaml": "^3.12.5",
"@types/jszip": "~3.1.6",
"@types/mocha": "~5.2.7",
"@types/node": "^12.0.8",
"@types/mocha": "^8.2.0",
"@types/node": "^12.14.1",
"@types/node-fetch": "~2.5.2",
"@types/proxyquire": "~1.3.28",
"@types/react": "^16.8.17",
"@types/react-dom": "^16.8.4",
"@types/sarif": "~2.1.2",
"@types/semver": "~7.2.0",
"@types/sinon": "~7.5.2",
"@types/sinon-chai": "~3.2.3",
"@types/through2": "^2.0.36",
"@types/tmp": "^0.1.0",
"@types/unzipper": "~0.10.1",
"@types/vscode": "^1.39.0",
"@types/vscode": "^1.43.0",
"@types/webpack": "^4.32.1",
"@types/xml2js": "~0.4.4",
"build-tasks": "^0.0.1",
"chai": "^4.2.0",
"css-loader": "~3.1.0",
"glob": "^7.1.4",
"gulp": "^4.0.2",
"gulp-sourcemaps": "^2.6.5",
"gulp-typescript": "^5.0.1",
"mocha": "~6.2.1",
"mocha-sinon": "~2.1.0",
"npm-run-all": "^4.1.5",
"sinon": "~9.0.0",
"style-loader": "~0.23.1",
"through2": "^3.0.1",
"ts-loader": "^5.4.5",
"ts-node": "^8.3.0",
"ts-protoc-gen": "^0.9.0",
"typescript": "^3.7.2",
"typescript-config": "^0.0.1",
"typescript-formatter": "^7.2.2",
"vsce": "^1.65.0",
"vscode-test": "^1.0.0",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.2",
"eslint": "~6.8.0",
"@typescript-eslint/eslint-plugin": "~2.23.0",
"@typescript-eslint/parser": "~2.23.0",
"ansi-colors": "^4.1.1",
"applicationinsights": "^1.8.7",
"chai": "^4.2.0",
"chai-as-promised": "~7.1.1",
"@types/chai-as-promised": "~7.1.2",
"@types/sinon": "~7.5.2",
"sinon-chai": "~3.5.0",
"@types/sinon-chai": "~3.2.3",
"proxyquire": "~2.1.3",
"@types/proxyquire": "~1.3.28",
"css-loader": "~3.1.0",
"eslint": "~6.8.0",
"eslint-plugin-react": "~7.19.0",
"glob": "^7.1.4",
"gulp": "^4.0.2",
"gulp-replace": "^1.0.0",
"gulp-sourcemaps": "^2.6.5",
"gulp-typescript": "^5.0.1",
"husky": "~4.2.5",
"jsonc-parser": "^2.3.0",
"lint-staged": "~10.2.2",
"prettier": "~2.0.5"
"mocha": "^8.2.1",
"mocha-sinon": "~2.1.0",
"npm-run-all": "^4.1.5",
"prettier": "~2.0.5",
"proxyquire": "~2.1.3",
"sinon": "~9.0.0",
"sinon-chai": "~3.5.0",
"style-loader": "~0.23.1",
"through2": "^3.0.1",
"ts-loader": "^8.1.0",
"ts-node": "^8.3.0",
"ts-protoc-gen": "^0.9.0",
"typescript": "~3.8.3",
"typescript-formatter": "^7.2.2",
"vsce": "^1.65.0",
"vscode-test": "^1.4.0",
"webpack": "^5.28.0",
"webpack-cli": "^4.6.0"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint-staged"
"pre-commit": "npm run format-staged",
"pre-push": "npm run lint"
}
},
"lint-staged": {
@@ -575,8 +948,8 @@
"prettier --write"
],
"./**/*.{ts,tsx}": [
"eslint --fix --debug",
"tsfmt -r"
"tsfmt -r",
"eslint --fix"
]
}
}

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

@@ -1,103 +0,0 @@
import { DecodedBqrsChunk, ResultSetSchema, ColumnKind, Column, ColumnValue } from "./bqrs-cli-types";
import { LocationValue, ResultSetSchema as AdaptedSchema, ColumnSchema, ColumnType, LocationStyle } from 'semmle-bqrs';
// FIXME: This is a temporary bit of impedance matching to convert
// from the types provided by ./bqrs-cli-types, to the types used by
// the view layer.
//
// The reason that it is benign for now is that it is only used by
// feature-flag-guarded codepaths that won't be encountered by normal
// users. It is not yet guaranteed to produce correct output for raw
// results.
//
// Eventually, the view layer should be refactored to directly accept data
// of types coming from bqrs-cli-types, and this file can be deleted.
export type ResultRow = ResultValue[];
export interface ResultElement {
label: string;
location?: LocationValue;
}
export interface ResultUri {
uri: string;
}
export type ResultValue = ResultElement | ResultUri | string;
export interface RawResultSet {
readonly schema: AdaptedSchema;
readonly rows: readonly ResultRow[];
}
function adaptKind(kind: ColumnKind): ColumnType {
// XXX what about 'u'?
if (kind === 'e') {
return { type: 'e', primitiveType: 's', locationStyle: LocationStyle.FivePart, hasLabel: true }
}
else {
return { type: kind };
}
}
function adaptColumn(col: Column): ColumnSchema {
return { name: col.name!, type: adaptKind(col.kind) };
}
export function adaptSchema(schema: ResultSetSchema): AdaptedSchema {
return {
columns: schema.columns.map(adaptColumn),
name: schema.name,
tupleCount: schema.rows,
version: 0,
}
}
export function adaptValue(val: ColumnValue): ResultValue {
// XXX taking a lot of incorrect shortcuts here
if (typeof val === 'string') {
return val;
}
if (typeof val === 'number' || typeof val === 'boolean') {
return val + '';
}
const url = val.url;
if (typeof url === 'string') {
return url;
}
if (url === undefined) {
return 'none';
}
return {
label: val.label || '',
location: {
t: LocationStyle.FivePart,
lineStart: url.startLine,
lineEnd: url.endLine,
colStart: url.startColumn,
colEnd: url.endColumn,
// FIXME: This seems definitely wrong. Should we be using
// something like the code in sarif-utils.ts?
file: url.uri.replace(/file:/, ''),
}
}
}
export function adaptRow(row: ColumnValue[]): ResultRow {
return row.map(adaptValue);
}
export function adaptBqrs(schema: AdaptedSchema, page: DecodedBqrsChunk): RawResultSet {
return {
schema,
rows: page.tuples.map(adaptRow),
}
}

View File

@@ -84,12 +84,25 @@ export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri {
// This lets us separate the paths, ignoring the leading slash if we added one.
const sourceArchiveZipPathEndIndex = sourceArchiveZipPathStartIndex + sourceArchiveZipPath.length;
const authority = `${sourceArchiveZipPathStartIndex}-${sourceArchiveZipPathEndIndex}`;
return vscode.Uri.parse(zipArchiveScheme + ':/').with({
return vscode.Uri.parse(zipArchiveScheme + ':/', true).with({
path: encodedPath,
authority,
});
}
/**
* Convenience method to create a codeql-zip-archive with a path to the root
* archive
*
* @param pathToArchive the filesystem path to the root of the archive
*/
export function encodeArchiveBasePath(sourceArchiveZipPath: string) {
return encodeSourceArchiveUri({
sourceArchiveZipPath,
pathWithinSourceArchive: ''
});
}
const sourceArchiveUriAuthorityPattern = /^(\d+)-(\d+)$/;
class InvalidSourceArchiveUriError extends Error {
@@ -100,6 +113,14 @@ class InvalidSourceArchiveUriError extends Error {
/** Decodes an encoded source archive URI into its corresponding paths. Inverse of `encodeSourceArchiveUri`. */
export function decodeSourceArchiveUri(uri: vscode.Uri): ZipFileReference {
if (!uri.authority) {
// Uri is malformed, but this is recoverable
logger.log(`Warning: ${new InvalidSourceArchiveUriError(uri).message}`);
return {
pathWithinSourceArchive: '/',
sourceArchiveZipPath: uri.path
};
}
const match = sourceArchiveUriAuthorityPattern.exec(uri.authority);
if (match === null)
throw new InvalidSourceArchiveUriError(uri);
@@ -108,7 +129,7 @@ export function decodeSourceArchiveUri(uri: vscode.Uri): ZipFileReference {
if (isNaN(zipPathStartIndex) || isNaN(zipPathEndIndex))
throw new InvalidSourceArchiveUriError(uri);
return {
pathWithinSourceArchive: uri.path.substring(zipPathEndIndex),
pathWithinSourceArchive: uri.path.substring(zipPathEndIndex) || '/',
sourceArchiveZipPath: uri.path.substring(zipPathStartIndex, zipPathEndIndex),
};
}
@@ -173,7 +194,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
const ref = decodeSourceArchiveUri(uri);
const archive = await this.getArchive(ref.sourceArchiveZipPath);
const contents = archive.dirMap.get(ref.pathWithinSourceArchive);
const result = contents === undefined ? [] : Array.from(contents.entries());
const result = contents === undefined ? undefined : Array.from(contents.entries());
if (result === undefined) {
throw vscode.FileSystemError.FileNotFound(uri);
}
@@ -238,7 +259,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
if (archive.dirMap.has(reqPath)) {
return new Directory(reqPath);
}
throw vscode.FileSystemError.FileNotFound(uri);
throw vscode.FileSystemError.FileNotFound(`uri '${uri.toString()}', interpreted as '${reqPath}' in archive '${ref.sourceArchiveZipPath}'`);
}
private async _lookupAsFile(uri: vscode.Uri): Promise<File> {

View File

@@ -0,0 +1,204 @@
import {
window,
TreeDataProvider,
EventEmitter,
Event,
ProviderResult,
TreeItemCollapsibleState,
TreeItem,
TreeView,
TextEditorSelectionChangeEvent,
TextEditorSelectionChangeKind,
Location,
Range
} from 'vscode';
import * as path from 'path';
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 './commandRunner';
import { DisposableObject } from './pure/disposable-object';
import { showAndLogErrorMessage } from './helpers';
export interface AstItem {
id: BqrsId;
label?: string;
location?: UrlValue;
fileLocation?: Location;
children: ChildAstItem[];
order: number;
}
export interface ChildAstItem extends AstItem {
parent: ChildAstItem | AstItem;
}
class AstViewerDataProvider extends DisposableObject implements TreeDataProvider<AstItem> {
public roots: AstItem[] = [];
public db: DatabaseItem | undefined;
private _onDidChangeTreeData =
this.push(new EventEmitter<AstItem | undefined>());
readonly onDidChangeTreeData: Event<AstItem | undefined> =
this._onDidChangeTreeData.event;
constructor() {
super();
this.push(
commandRunner('codeQLAstViewer.gotoCode',
async (item: AstItem) => {
await showLocation(item.fileLocation);
})
);
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
getChildren(item?: AstItem): ProviderResult<AstItem[]> {
const children = item ? item.children : this.roots;
return children.sort((c1, c2) => (c1.order - c2.order));
}
getParent(item: ChildAstItem): ProviderResult<AstItem> {
return item.parent;
}
getTreeItem(item: AstItem): TreeItem {
const line = this.extractLineInfo(item?.location);
const state = item.children.length
? TreeItemCollapsibleState.Collapsed
: TreeItemCollapsibleState.None;
const treeItem = new TreeItem(item.label || '', state);
treeItem.description = line ? `Line ${line}` : '';
treeItem.id = String(item.id);
treeItem.tooltip = `${treeItem.description} ${treeItem.label}`;
treeItem.command = {
command: 'codeQLAstViewer.gotoCode',
title: 'Go To Code',
tooltip: `Go To ${item.location}`,
arguments: [item]
};
return treeItem;
}
private extractLineInfo(loc?: UrlValue) {
if (!loc) {
return '';
} else if (isStringLoc(loc)) {
return loc;
} else if (isWholeFileLoc(loc)) {
return loc.uri;
} else if (isLineColumnLoc(loc)) {
return loc.startLine;
} else {
return '';
}
}
}
export class AstViewer extends DisposableObject {
private treeView: TreeView<AstItem>;
private treeDataProvider: AstViewerDataProvider;
private currentFile: string | undefined;
constructor() {
super();
this.treeDataProvider = new AstViewerDataProvider();
this.treeView = window.createTreeView('codeQLAstViewer', {
treeDataProvider: this.treeDataProvider,
showCollapseAll: true
});
this.push(this.treeView);
this.push(this.treeDataProvider);
this.push(
commandRunner('codeQLAstViewer.clear', async () => {
this.clear();
})
);
this.push(window.onDidChangeTextEditorSelection(this.updateTreeSelection, this));
}
updateRoots(roots: AstItem[], db: DatabaseItem, fileName: string) {
this.treeDataProvider.roots = roots;
this.treeDataProvider.db = db;
this.treeDataProvider.refresh();
this.treeView.message = `AST for ${path.basename(fileName)}`;
this.currentFile = fileName;
// Handle error on reveal. This could happen if
// the tree view is disposed during the reveal.
this.treeView.reveal(roots[0], { focus: false })?.then(
() => { /**/ },
err => showAndLogErrorMessage(err)
);
}
private updateTreeSelection(e: TextEditorSelectionChangeEvent) {
function isInside(selectedRange: Range, astRange?: Range): boolean {
return !!astRange?.contains(selectedRange);
}
// Recursively iterate all children until we find the node with the smallest
// range that contains the selection.
// Some nodes do not have a location, but their children might, so must
// recurse though location-less AST nodes to see if children are correct.
function findBest(selectedRange: Range, items?: AstItem[]): AstItem | undefined {
if (!items || !items.length) {
return;
}
for (const item of items) {
let candidate: AstItem | undefined = undefined;
if (isInside(selectedRange, item.fileLocation?.range)) {
candidate = item;
}
// always iterate through children since the location of an AST node in code QL does not
// always cover the complete text of the node.
candidate = findBest(selectedRange, item.children) || candidate;
if (candidate) {
return candidate;
}
}
return;
}
// Avoid recursive tree-source code updates.
if (e.kind === TextEditorSelectionChangeKind.Command) {
return;
}
if (
this.treeView.visible &&
e.textEditor.document.uri.fsPath === this.currentFile &&
e.selections.length === 1
) {
const selection = e.selections[0];
const range = selection.anchor.isBefore(selection.active)
? new Range(selection.anchor, selection.active)
: new Range(selection.active, selection.anchor);
const targetItem = findBest(range, this.treeDataProvider.roots);
if (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)
);
}
}
}
private clear() {
this.treeDataProvider.roots = [];
this.treeDataProvider.db = undefined;
this.treeDataProvider.refresh();
this.treeView.message = undefined;
this.currentFile = undefined;
}
}

View File

@@ -1,96 +1,24 @@
import { runCodeQlCliCommand } from "./cli";
import { Logger } from "./logging";
import * as semver from 'semver';
import { runCodeQlCliCommand } from './cli';
import { Logger } from './logging';
/**
* Get the version of a CodeQL CLI.
*/
export async function getCodeQlCliVersion(codeQlPath: string, logger: Logger): Promise<Version | undefined> {
const output: string = await runCodeQlCliCommand(
codeQlPath,
["version"],
["--format=terse"],
"Checking CodeQL version",
logger
);
return tryParseVersionString(output.trim());
}
/**
* Try to parse a version string, returning undefined if we can't parse it.
*
* Version strings must contain a major, minor, and patch version. They may optionally
* start with "v" and may optionally contain some "tail" string after the major, minor, and
* patch versions, for example as in `v2.1.0+baf5bff`.
*/
export function tryParseVersionString(versionString: string): Version | undefined {
const match = versionString.match(versionRegex);
if (match === null) {
export async function getCodeQlCliVersion(codeQlPath: string, logger: Logger): Promise<semver.SemVer | undefined> {
try {
const output: string = await runCodeQlCliCommand(
codeQlPath,
['version'],
['--format=terse'],
'Checking CodeQL version',
logger
);
return semver.parse(output.trim()) || undefined;
} 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}`);
return undefined;
}
return {
buildMetadata: match[5],
majorVersion: Number.parseInt(match[1], 10),
minorVersion: Number.parseInt(match[2], 10),
patchVersion: Number.parseInt(match[3], 10),
prereleaseVersion: match[4],
rawString: versionString,
}
}
/**
* Regex for parsing semantic versions
*
* From the semver spec https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
*/
const versionRegex = new RegExp(String.raw`^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` +
String.raw`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` +
String.raw`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`);
/**
* A version of the CodeQL CLI.
*/
export interface Version {
/**
* Build metadata
*
* For example, this will be `abcdef0` for version 2.1.0-alpha.1+abcdef0.
* Build metadata must be ignored when comparing versions.
*/
buildMetadata: string | undefined;
/**
* Major version number
*
* For example, this will be `2` for version 2.1.0-alpha.1+abcdef0.
*/
majorVersion: number;
/**
* Minor version number
*
* For example, this will be `1` for version 2.1.0-alpha.1+abcdef0.
*/
minorVersion: number;
/**
* Patch version number
*
* For example, this will be `0` for version 2.1.0-alpha.1+abcdef0.
*/
patchVersion: number;
/**
* Prerelease version
*
* For example, this will be `alpha.1` for version 2.1.0-alpha.1+abcdef0.
* The prerelease version must be considered when comparing versions.
*/
prereleaseVersion: string | undefined;
/**
* Raw version string
*
* For example, this will be `2.1.0-alpha.1+abcdef0` for version 2.1.0-alpha.1+abcdef0.
*/
rawString: string;
}

View File

@@ -4,21 +4,30 @@ import * as child_process from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as sarif from 'sarif';
import { SemVer } from 'semver';
import { Readable } from 'stream';
import { StringDecoder } from 'string_decoder';
import * as tk from 'tree-kill';
import * as util from 'util';
import { promisify } from 'util';
import { CancellationToken, Disposable } from 'vscode';
import { BQRSInfo, DecodedBqrsChunk } from "./bqrs-cli-types";
import { DistributionProvider } from "./distribution";
import { assertNever } from "./helpers-pure";
import { QueryMetadata, SortDirection } from "./interface-types";
import { Logger, ProgressReporter } from "./logging";
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
import { CliConfig } from './config';
import { DistributionProvider, FindDistributionResultKind } from './distribution';
import { assertNever } from './pure/helpers-pure';
import { QueryMetadata, SortDirection } from './pure/interface-types';
import { Logger, ProgressReporter } from './logging';
import { CompilationMessage } from './pure/messages';
/**
* The version of the SARIF format that we are using.
*/
const SARIF_FORMAT = "sarifv2.1.0";
const SARIF_FORMAT = 'sarifv2.1.0';
/**
* The string used to specify CSV format.
*/
const CSV_FORMAT = 'csv';
/**
* Flags to pass to all cli commands.
@@ -46,6 +55,7 @@ export interface DbInfo {
sourceArchiveRoot: string;
datasetFolder: string;
logsFolder: string;
languages: string[];
}
/**
@@ -54,6 +64,7 @@ export interface DbInfo {
export interface UpgradesInfo {
scripts: string[];
finalDbscheme: string;
matchesTarget?: boolean;
}
/**
@@ -61,6 +72,11 @@ export interface UpgradesInfo {
*/
export type QlpacksInfo = { [name: string]: string[] };
/**
* 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 {
@@ -87,10 +103,24 @@ export interface TestRunOptions {
export interface TestCompleted {
test: string;
pass: boolean;
messages: string[];
messages: CompilationMessage[];
compilationMs: number;
evaluationMs: number;
expected: string;
diff: string[] | undefined;
failureDescription?: string;
}
/**
* Optional arguments for the `bqrsDecode` function
*/
interface BqrsDecodeOptions {
/** How many results to get. */
pageSize?: number;
/** The 0-based index of the first result to get. */
offset?: number;
/** The entity names to retrieve from the bqrs file. Default is url, string */
entities?: string[];
}
/**
@@ -101,6 +131,7 @@ export interface TestCompleted {
*/
export class CodeQLCliServer implements Disposable {
/** The process for the cli server, or undefined if one doesn't exist yet */
process?: child_process.ChildProcessWithoutNullStreams;
/** Queue of future commands*/
@@ -110,18 +141,41 @@ export class CodeQLCliServer implements Disposable {
/** A buffer with a single null byte. */
nullBuffer: Buffer;
constructor(private config: DistributionProvider, private logger: Logger) {
/** Version of current cli, lazily computed by the `getVersion()` method */
private _version: SemVer | 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
) {
this.commandQueue = [];
this.commandInProcess = false;
this.nullBuffer = Buffer.alloc(1);
if (this.config.onDidChangeDistribution) {
this.config.onDidChangeDistribution(() => {
if (this.distributionProvider.onDidChangeDistribution) {
this.distributionProvider.onDidChangeDistribution(() => {
this.restartCliServer();
this._version = undefined;
});
}
if (this.cliConfig.onDidChangeConfiguration) {
this.cliConfig.onDidChangeConfiguration(() => {
this.restartCliServer();
this._version = undefined;
});
}
}
dispose(): void {
this.killProcessIfRunning();
}
@@ -131,7 +185,7 @@ export class CodeQLCliServer implements Disposable {
// Tell the Java CLI server process to shut down.
this.logger.log('Sending shutdown request');
try {
this.process.stdin.write(JSON.stringify(["shutdown"]), "utf8");
this.process.stdin.write(JSON.stringify(['shutdown']), 'utf8');
this.process.stdin.write(this.nullBuffer);
this.logger.log('Sent shutdown request');
} catch (e) {
@@ -165,7 +219,7 @@ export class CodeQLCliServer implements Disposable {
// If the server is not running a command run this immediately
// otherwise add to the front of the queue (as we want to run this after the next command()).
if (this.commandInProcess) {
this.commandQueue.unshift(callback)
this.commandQueue.unshift(callback);
} else {
callback();
}
@@ -176,7 +230,7 @@ export class CodeQLCliServer implements Disposable {
* Get the path to the CodeQL CLI distribution, or throw an exception if not found.
*/
private async getCodeQlPath(): Promise<string> {
const codeqlPath = await this.config.getCodeQlPathWithoutVersionCheck();
const codeqlPath = await this.distributionProvider.getCodeQlPathWithoutVersionCheck();
if (!codeqlPath) {
throw new Error('Failed to find CodeQL distribution.');
}
@@ -187,20 +241,27 @@ export class CodeQLCliServer implements Disposable {
* Launch the cli server
*/
private async launchProcess(): Promise<child_process.ChildProcessWithoutNullStreams> {
const config = await this.getCodeQlPath();
return spawnServer(config, "CodeQL CLI Server", ["execute", "cli-server"], [], this.logger, _data => { /**/ })
const codeQlPath = await this.getCodeQlPath();
return await spawnServer(
codeQlPath,
'CodeQL CLI Server',
['execute', 'cli-server'],
[],
this.logger,
_data => { /**/ }
);
}
private async runCodeQlCliInternal(command: string[], commandArgs: string[], description: string): Promise<string> {
const stderrBuffers: Buffer[] = [];
if (this.commandInProcess) {
throw new Error("runCodeQlCliInternal called while cli was running")
throw new Error('runCodeQlCliInternal called while cli was running');
}
this.commandInProcess = true;
try {
//Launch the process if it doesn't exist
if (!this.process) {
this.process = await this.launchProcess()
this.process = await this.launchProcess();
}
// Grab the process so that typescript know that it is always defined.
const process = this.process;
@@ -209,10 +270,10 @@ export class CodeQLCliServer implements Disposable {
// Compute the full args array
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
const argsString = args.join(" ");
const argsString = args.join(' ');
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);
@@ -228,16 +289,16 @@ export class CodeQLCliServer implements Disposable {
stderrBuffers.push(newData);
});
// Listen for process exit.
process.addListener("close", (code) => reject(code));
process.addListener('close', (code) => reject(code));
// Write the command followed by a null terminator.
process.stdin.write(JSON.stringify(args), "utf8")
process.stdin.write(this.nullBuffer)
process.stdin.write(JSON.stringify(args), 'utf8');
process.stdin.write(this.nullBuffer);
});
// Join all the data together
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.`);
const data = fullBuffer.toString('utf8', 0, fullBuffer.length - 1);
this.logger.log('CLI command succeeded.');
return data;
} catch (err) {
// Kill the process if it isn't already dead.
@@ -246,15 +307,15 @@ export class CodeQLCliServer implements Disposable {
const newError =
stderrBuffers.length == 0
? new Error(`${description} failed: ${err}`)
: new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`);
: new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString('utf8')}`);
newError.stack += (err.stack || '');
throw newError;
} finally {
this.logger.log(Buffer.concat(stderrBuffers).toString("utf8"));
this.logger.log(Buffer.concat(stderrBuffers).toString('utf8'));
// Remove the listeners we set up.
process.stdout.removeAllListeners('data')
process.stderr.removeAllListeners('data')
process.removeAllListeners("close");
process.stdout.removeAllListeners('data');
process.stderr.removeAllListeners('data');
process.removeAllListeners('close');
}
} finally {
this.commandInProcess = false;
@@ -349,7 +410,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.stderr || err}`);
}
}
}
@@ -375,11 +436,11 @@ export class CodeQLCliServer implements Disposable {
} catch (err) {
reject(err);
}
}
};
// If the server is not running a command, then run the given command immediately,
// otherwise add to the queue
if (this.commandInProcess) {
this.commandQueue.push(callback)
this.commandQueue.push(callback);
} else {
callback();
}
@@ -391,17 +452,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.stderr || err}`);
}
}
@@ -413,10 +477,10 @@ export class CodeQLCliServer implements Disposable {
async resolveLibraryPath(workspaces: string[], queryPath: string): Promise<QuerySetup> {
const subcommandArgs = [
'--query', queryPath,
"--additional-packs",
'--additional-packs',
workspaces.join(path.delimiter)
];
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, "Resolving library paths");
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
}
/**
@@ -428,7 +492,23 @@ export class CodeQLCliServer implements Disposable {
const subcommandArgs = [
testPath
];
return await this.runJsonCodeQlCliCommand<ResolvedTests>(['resolve', 'tests'], subcommandArgs, 'Resolving tests');
return await this.runJsonCodeQlCliCommand<ResolvedTests>(
['resolve', 'tests', '--strict-test-discovery'],
subcommandArgs,
'Resolving tests'
);
}
public async resolveQlref(qlref: string): Promise<QlrefInfo> {
const subcommandArgs = [
qlref
];
return await this.runJsonCodeQlCliCommand<QlrefInfo>(
['resolve', 'qlref'],
subcommandArgs,
'Resolving qlref',
false
);
}
/**
@@ -441,11 +521,12 @@ export class CodeQLCliServer implements Disposable {
testPaths: string[], workspaces: string[], options: TestRunOptions
): AsyncGenerator<TestCompleted, void, unknown> {
const subcommandArgs = [
const subcommandArgs = this.cliConfig.additionalTestArguments.concat([
'--additional-packs', workspaces.join(path.delimiter),
'--threads', '8',
'--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)) {
@@ -458,7 +539,7 @@ export class CodeQLCliServer implements Disposable {
* @param queryPath The path to the query.
*/
async resolveMetadata(queryPath: string): Promise<QueryMetadata> {
return await this.runJsonCodeQlCliCommand<QueryMetadata>(['resolve', 'metadata'], [queryPath], "Resolving query metadata");
return await this.runJsonCodeQlCliCommand<QueryMetadata>(['resolve', 'metadata'], [queryPath], 'Resolving query metadata');
}
/**
@@ -474,7 +555,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.
@@ -483,75 +564,97 @@ export class CodeQLCliServer implements Disposable {
*/
async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BQRSInfo> {
const subcommandArgs = (
pageSize ? ["--paginate-rows", pageSize.toString()] : []
pageSize ? ['--paginate-rows', pageSize.toString()] : []
).concat(
bqrsPath
);
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, "Reading bqrs header");
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');
}
/**
* Gets the results from a bqrs.
* @param bqrsPath The path to the bqrs.
* @param resultSet The result set to get.
* @param pageSize How many results to get.
* @param offset The 0-based index of the first result to get.
* @param options Optional BqrsDecodeOptions arguments
*/
async bqrsDecode(bqrsPath: string, resultSet: string, pageSize?: number, offset?: number): Promise<DecodedBqrsChunk> {
async bqrsDecode(
bqrsPath: string,
resultSet: string,
{ pageSize, offset, entities = ['url', 'string'] }: BqrsDecodeOptions = {}
): Promise<DecodedBqrsChunk> {
const subcommandArgs = [
"--entities=url,string",
"--result-set", resultSet,
`--entities=${entities.join(',')}`,
'--result-set', resultSet,
].concat(
pageSize ? ["--rows", pageSize.toString()] : []
pageSize ? ['--rows', pageSize.toString()] : []
).concat(
offset ? ["--start-at", offset.toString()] : []
offset ? ['--start-at', offset.toString()] : []
).concat([bqrsPath]);
return await this.runJsonCodeQlCliCommand<DecodedBqrsChunk>(['bqrs', 'decode'], subcommandArgs, "Reading bqrs data");
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, metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo) {
const args = [
`-t=kind=${metadata.kind}`,
`-t=id=${metadata.id}`,
"--output", interpretedResultsPath,
"--format", SARIF_FORMAT,
'--output', interpretedResultsPath,
'--format', format,
// Forward all of the query metadata.
...Object.entries(metadata).map(([key, value]) => `-t=${key}=${value}`)
];
if (format == SARIF_FORMAT) {
// TODO: This flag means that we don't group interpreted results
// by primary location. We may want to revisit whether we call
// interpretation with and without this flag, or do some
// grouping client-side.
"--no-group-results",
];
args.push('--no-group-results');
}
if (sourceInfo !== undefined) {
args.push(
"--source-archive", sourceInfo.sourceArchive,
"--source-location-prefix", sourceInfo.sourceLocationPrefix
'--source-archive', sourceInfo.sourceArchive,
'--source-location-prefix', sourceInfo.sourceLocationPrefix
);
}
args.push(
'--threads',
this.cliConfig.numberThreads.toString(),
);
args.push(resultsPath);
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, "Interpreting query results");
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');
}
async interpretBqrs(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise<sarif.Log> {
await this.runInterpretCommand(SARIF_FORMAT, metadata, resultsPath, interpretedResultsPath, sourceInfo);
let output: string;
try {
output = await fs.readFile(interpretedResultsPath, 'utf8');
} catch (err) {
throw new Error(`Reading output of interpretation failed: ${err.stderr || err}`)
} catch (e) {
const rawMessage = e.stderr || e.message;
const errorMessage = rawMessage.startsWith('Cannot create a string')
? `SARIF too large. ${rawMessage}`
: rawMessage;
throw new Error(`Reading output of interpretation failed: ${errorMessage}`);
}
try {
return JSON.parse(output) as sarif.Log;
} catch (err) {
throw new Error(`Parsing output of interpretation failed: ${err.stderr || err}`)
throw new Error(`Parsing output of interpretation failed: ${err.stderr || 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 => {
switch (direction) {
case SortDirection.asc:
return "asc";
return 'asc';
case SortDirection.desc:
return "desc";
return 'desc';
default:
return assertNever(direction);
}
@@ -559,14 +662,14 @@ export class CodeQLCliServer implements Disposable {
await this.runCodeQlCliCommand(['bqrs', 'decode'],
[
"--format=bqrs",
'--format=bqrs',
`--result-set=${resultSet}`,
`--output=${sortedResultsPath}`,
`--sort-key=${sortKeys.join(",")}`,
`--sort-direction=${sortDirectionStrings.join(",")}`,
`--sort-key=${sortKeys.join(',')}`,
`--sort-direction=${sortDirectionStrings.join(',')}`,
resultsPath
],
"Sorting query results");
'Sorting query results');
}
@@ -576,22 +679,29 @@ export class CodeQLCliServer implements Disposable {
*/
resolveDatabase(databasePath: string): Promise<DbInfo> {
return this.runJsonCodeQlCliCommand(['resolve', 'database'], [databasePath],
"Resolving database");
'Resolving database');
}
/**
* 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> {
async resolveUpgrades(dbScheme: string, searchPath: string[], allowDowngradesIfPossible: boolean, targetDbScheme?: string): Promise<UpgradesInfo> {
const args = ['--additional-packs', searchPath.join(path.delimiter), '--dbscheme', dbScheme];
return this.runJsonCodeQlCliCommand<UpgradesInfo>(
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",
'Resolving database upgrade scripts',
);
}
@@ -604,14 +714,14 @@ export class CodeQLCliServer implements Disposable {
*/
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
if (searchPath !== undefined) {
if (searchPath?.length) {
args.push('--search-path', path.join(...searchPath));
}
return this.runJsonCodeQlCliCommand<QlpacksInfo>(
['resolve', 'qlpacks'],
args,
"Resolving qlpack information",
'Resolving qlpack information',
);
}
@@ -632,9 +742,42 @@ export class CodeQLCliServer implements Disposable {
return this.runJsonCodeQlCliCommand<string[]>(
['resolve', 'queries'],
args,
"Resolving queries",
'Resolving queries',
);
}
async generateDil(qloFile: string, outFile: string): Promise<void> {
const extraArgs = await this.cliConstraints.supportsDecompileDil()
? ['--kind', 'dil', '-o', outFile, qloFile]
: ['-o', outFile, qloFile];
await this.runCodeQlCliCommand(
['query', 'decompile'],
extraArgs,
'Generating DIL',
);
}
public async getVersion() {
if (!this._version) {
this._version = await this.refreshVersion();
}
return this._version;
}
private async refreshVersion() {
const distribution = await this.distributionProvider.getDistribution();
switch (distribution.kind) {
case FindDistributionResultKind.CompatibleDistribution:
// eslint-disable-next-line no-fallthrough
case FindDistributionResultKind.IncompatibleDistribution:
return distribution.version;
default:
// We should not get here because if no distributions are available, then
// the cli class is never instantiated.
throw new Error('No distribution found');
}
}
}
/**
@@ -666,7 +809,7 @@ export function spawnServer(
// Start the server process.
const base = codeqlPath;
const argsString = args.join(" ");
const argsString = args.join(' ');
if (progressReporter !== undefined) {
progressReporter.report({ message: `Starting ${name}` });
}
@@ -692,7 +835,7 @@ export function spawnServer(
/**
* Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string.
* @param config The configuration containing the path to the CLI.
* @param codeQlPath The path to the CLI.
* @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.
@@ -700,21 +843,28 @@ export function spawnServer(
* @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.
*/
export async function runCodeQlCliCommand(codeQlPath: string, command: string[], commandArgs: string[], description: string, logger: Logger, progressReporter?: ProgressReporter): Promise<string> {
export async function runCodeQlCliCommand(
codeQlPath: string,
command: string[],
commandArgs: string[],
description: string,
logger: Logger,
progressReporter?: ProgressReporter
): Promise<string> {
// Add logging arguments first, in case commandArgs contains positional parameters.
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
const argsString = args.join(" ");
const argsString = args.join(' ');
try {
if (progressReporter !== undefined) {
progressReporter.report({ message: description });
}
logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`);
const result = await util.promisify(child_process.execFile)(codeQlPath, args);
const result = await promisify(child_process.execFile)(codeQlPath, args);
logger.log(result.stderr);
logger.log(`CLI command succeeded.`);
logger.log('CLI command succeeded.');
return result.stdout;
} catch (err) {
throw new Error(`${description} failed: ${err.stderr || err}`)
throw new Error(`${description} failed: ${err.stderr || err}`);
}
}
@@ -747,6 +897,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
@@ -755,7 +919,7 @@ class SplitBuffer {
public getNextLine(): string | undefined {
while (this.searchIndex <= (this.buffer.length - this.maxSeparatorLength)) {
for (const separator of this.separators) {
if (this.buffer.startsWith(separator, this.searchIndex)) {
if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {
const line = this.buffer.substr(0, this.searchIndex);
this.buffer = this.buffer.substr(this.searchIndex + separator.length);
this.searchIndex = 0;
@@ -815,3 +979,73 @@ async function logStream(stream: Readable, logger: Logger): Promise<void> {
logger.log(line);
}
}
export function shouldDebugIdeServer() {
return 'IDE_SERVER_JAVA_DEBUG' in process.env
&& process.env.IDE_SERVER_JAVA_DEBUG !== '0'
&& process.env.IDE_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}
export function shouldDebugQueryServer() {
return 'QUERY_SERVER_JAVA_DEBUG' in process.env
&& process.env.QUERY_SERVER_JAVA_DEBUG !== '0'
&& process.env.QUERY_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');
/**
* 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');
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 supportsDowngrades() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DOWNGRADES);
}
public async supportsResolveQlref() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF);
}
async supportsDatabaseRegistration() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
}
}

View File

@@ -0,0 +1,241 @@
import {
CancellationToken,
ProgressOptions,
window as Window,
commands,
Disposable,
ProgressLocation
} from 'vscode';
import { showAndLogErrorMessage, showAndLogWarningMessage } from './helpers';
import { logger } from './logging';
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) {
error = e;
const errorMessage = `${e.message || e} (${commandId})`;
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
logger.log(errorMessage);
} else {
showAndLogWarningMessage(errorMessage);
}
} else {
// Include the full stack in the error log only.
const fullMessage = e.stack
? `${errorMessage}\n${e.stack}`
: errorMessage;
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>
): Disposable {
return commands.registerCommand(commandId, async (...args: any[]) => {
const startTime = Date.now();
let error: Error | undefined;
const progressOptionsWithDefaults = {
location: ProgressLocation.Notification,
...progressOptions
};
try {
return await withProgress(progressOptionsWithDefaults, task, ...args);
} catch (e) {
error = e;
const errorMessage = `${e.message || e} (${commandId})`;
if (e instanceof UserCancellationException) {
// User has cancelled this action manually
if (e.silent) {
logger.log(errorMessage);
} else {
showAndLogWarningMessage(errorMessage);
}
} else {
// Include the full stack in the error log only.
const fullMessage = e.stack
? `${errorMessage}\n${e.stack}`
: errorMessage;
showAndLogErrorMessage(errorMessage, {
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

@@ -0,0 +1,277 @@
import { DisposableObject } from '../pure/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,
QueryCompareResult,
} from '../pure/interface-types';
import { Logger } from '../logging';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager } from '../databases';
import { getHtmlForWebview, jumpToLocation } from '../interface-utils';
import { transformBqrsResultSet, RawResultSet, BQRSInfo } from '../pure/bqrs-cli-types';
import resultsDiff from './resultsDiff';
interface ComparePair {
from: CompletedQuery;
to: CompletedQuery;
}
export class CompareInterfaceManager extends DisposableObject {
private comparePair: ComparePair | undefined;
private panel: WebviewPanel | undefined;
private panelLoaded = false;
private panelLoadedCallBacks: (() => void)[] = [];
constructor(
private ctx: ExtensionContext,
private databaseManager: DatabaseManager,
private cliServer: CodeQLCliServer,
private logger: Logger,
private showQueryResultsCallback: (
item: CompletedQuery
) => Promise<void>
) {
super();
}
async showResults(
from: CompletedQuery,
to: CompletedQuery,
selectedResultSetName?: string
) {
this.comparePair = { from, to };
this.getPanel().reveal(undefined, true);
await this.waitForPanelLoaded();
const [
commonResultSetNames,
currentResultSetName,
fromResultSet,
toResultSet,
] = await this.findCommonResultSetNames(
from,
to,
selectedResultSetName
);
if (currentResultSetName) {
let rows: QueryCompareResult | undefined;
let message: string | undefined;
try {
rows = this.compareResults(fromResultSet, toResultSet);
} catch (e) {
message = e.message;
}
await this.postMessage({
t: 'setComparisons',
stats: {
fromQuery: {
// 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,
},
toQuery: {
name: to.options.label
? to.interpolate(to.getLabel())
: to.queryName,
status: to.statusString,
time: to.time,
},
},
columns: fromResultSet.schema.columns,
commonResultSetNames,
currentResultSetName: currentResultSetName,
rows,
message,
datebaseUri: to.database.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;
}
private waitForPanelLoaded(): Promise<void> {
return new Promise((resolve) => {
if (this.panelLoaded) {
resolve();
} else {
this.panelLoadedCallBacks.push(resolve);
}
});
}
private async handleMsgFromView(
msg: FromCompareViewMessage
): Promise<void> {
switch (msg.t) {
case 'compareViewLoaded':
this.panelLoaded = true;
this.panelLoadedCallBacks.forEach((cb) => cb());
this.panelLoadedCallBacks = [];
break;
case 'changeCompare':
this.changeTable(msg.newResultSetName);
break;
case 'viewSourceFile':
await jumpToLocation(msg, this.databaseManager, this.logger);
break;
case 'openQuery':
await this.openQuery(msg.kind);
break;
}
}
private postMessage(msg: ToCompareViewMessage): Thenable<boolean> {
return this.getPanel().webview.postMessage(msg);
}
private async findCommonResultSetNames(
from: CompletedQuery,
to: CompletedQuery,
selectedResultSetName: string | undefined
): Promise<[string[], string, RawResultSet, RawResultSet]> {
const fromSchemas = await this.cliServer.bqrsInfo(
from.query.resultsPaths.resultsPath
);
const toSchemas = await this.cliServer.bqrsInfo(
to.query.resultsPaths.resultsPath
);
const fromSchemaNames = fromSchemas['result-sets'].map(
(schema) => schema.name
);
const toSchemaNames = toSchemas['result-sets'].map(
(schema) => schema.name
);
const commonResultSetNames = fromSchemaNames.filter((name) =>
toSchemaNames.includes(name)
);
const currentResultSetName =
selectedResultSetName || commonResultSetNames[0];
const fromResultSet = await this.getResultSet(
fromSchemas,
currentResultSetName,
from.query.resultsPaths.resultsPath
);
const toResultSet = await this.getResultSet(
toSchemas,
currentResultSetName,
to.query.resultsPaths.resultsPath
);
return [
commonResultSetNames,
currentResultSetName,
fromResultSet,
toResultSet,
];
}
private async changeTable(newResultSetName: string) {
if (!this.comparePair?.from || !this.comparePair.to) {
return;
}
await this.showResults(
this.comparePair.from,
this.comparePair.to,
newResultSetName
);
}
private async getResultSet(
bqrsInfo: BQRSInfo,
resultSetName: string,
resultsPath: string
): Promise<RawResultSet> {
const schema = bqrsInfo['result-sets'].find(
(schema) => schema.name === resultSetName
);
if (!schema) {
throw new Error(`Schema ${resultSetName} not found.`);
}
const chunk = await this.cliServer.bqrsDecode(
resultsPath,
resultSetName
);
return transformBqrsResultSet(schema, chunk);
}
private compareResults(
fromResults: RawResultSet,
toResults: RawResultSet
): QueryCompareResult {
// Only compare columns that have the same name
return resultsDiff(fromResults, toResults);
}
private openQuery(kind: 'from' | 'to') {
const toOpen =
kind === 'from' ? this.comparePair?.from : this.comparePair?.to;
if (toOpen) {
this.showQueryResultsCallback(toOpen);
}
}
}

View File

@@ -0,0 +1,58 @@
import { RawResultSet } from '../pure/bqrs-cli-types';
import { QueryCompareResult } from '../pure/interface-types';
/**
* Compare the rows of two queries. Use deep equality to determine if
* rows have been added or removed across two invocations of a query.
*
* Assumptions:
*
* 1. Queries have the same sort order
* 2. Queries have same number and order of columns
* 3. Rows are not changed or re-ordered, they are only added or removed
*
* @param fromResults the source query
* @param toResults the target query
*
* @throws Error when:
* 1. number of columns do not match
* 2. If either query is empty
* 3. If the queries are 100% disjoint
*/
export default function resultsDiff(
fromResults: RawResultSet,
toResults: RawResultSet
): QueryCompareResult {
if (fromResults.schema.columns.length !== toResults.schema.columns.length) {
throw new Error('CodeQL Compare: Columns do not match.');
}
if (!fromResults.rows.length) {
throw new Error('CodeQL Compare: Source query has no results.');
}
if (!toResults.rows.length) {
throw new Error('CodeQL Compare: Target query has no results.');
}
const results = {
from: arrayDiff(fromResults.rows, toResults.rows),
to: arrayDiff(toResults.rows, fromResults.rows),
};
if (
fromResults.rows.length === results.from.length &&
toResults.rows.length === results.to.length
) {
throw new Error('CodeQL Compare: No overlap between the selected queries.');
}
return results;
}
function arrayDiff<T>(source: readonly T[], toRemove: readonly T[]): T[] {
// Stringify the object so that we can compare hashes in the set
const rest = new Set(toRemove.map((item) => JSON.stringify(item)));
return source.filter((element) => !rest.has(JSON.stringify(element)));
}

View File

@@ -0,0 +1,13 @@
module.exports = {
env: {
browser: true
},
extends: [
"plugin:react/recommended"
],
settings: {
react: {
version: 'detect'
}
}
}

View File

@@ -0,0 +1,82 @@
import * as React from 'react';
import { useState, useEffect } from 'react';
import * as Rdom from 'react-dom';
import {
ToCompareViewMessage,
SetComparisonsMessage,
} from '../../pure/interface-types';
import CompareSelector from './CompareSelector';
import { vscode } from '../../view/vscode-api';
import CompareTable from './CompareTable';
const emptyComparison: SetComparisonsMessage = {
t: 'setComparisons',
stats: {},
rows: undefined,
columns: [],
commonResultSetNames: [],
currentResultSetName: '',
datebaseUri: '',
message: 'Empty comparison'
};
export function Compare(_: {}): JSX.Element {
const [comparison, setComparison] = useState<SetComparisonsMessage>(
emptyComparison
);
const message = comparison.message || 'Empty comparison';
const hasRows = comparison.rows && (comparison.rows.to.length || comparison.rows.from.length);
useEffect(() => {
window.addEventListener('message', (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToCompareViewMessage = evt.data;
switch (msg.t) {
case 'setComparisons':
setComparison(msg);
}
} else {
console.error(`Invalid event origin ${evt.origin}`);
}
});
});
if (!comparison) {
return <div>Waiting for results to load.</div>;
}
try {
return (
<>
<div className="vscode-codeql__compare-header">
<div className="vscode-codeql__compare-header-item">
Table to compare:
</div>
<CompareSelector
availableResultSets={comparison.commonResultSetNames}
currentResultSetName={comparison.currentResultSetName}
updateResultSet={(newResultSetName: string) =>
vscode.postMessage({ t: 'changeCompare', newResultSetName })
}
/>
</div>
{hasRows ? (
<CompareTable comparison={comparison}></CompareTable>
) : (
<div className="vscode-codeql__compare-message">{message}</div>
)}
</>
);
} catch (err) {
console.error(err);
return <div>Error!</div>;
}
}
Rdom.render(
<Compare />,
document.getElementById('root'),
// Post a message to the extension when fully loaded.
() => vscode.postMessage({ t: 'compareViewLoaded' })
);

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
interface Props {
availableResultSets: string[];
currentResultSetName: string;
updateResultSet: (newResultSet: string) => void;
}
export default function CompareSelector(props: Props) {
return (
<select
value={props.currentResultSetName}
onChange={(e) => props.updateResultSet(e.target.value)}
>
{props.availableResultSets.map((resultSet) => (
<option key={resultSet} value={resultSet}>
{resultSet}
</option>
))}
</select>
);
}

View File

@@ -0,0 +1,96 @@
import * as React from 'react';
import { SetComparisonsMessage } from '../../pure/interface-types';
import RawTableHeader from '../../view/RawTableHeader';
import { className } from '../../view/result-table-utils';
import { ResultRow } from '../../pure/bqrs-cli-types';
import RawTableRow from '../../view/RawTableRow';
import { vscode } from '../../view/vscode-api';
interface Props {
comparison: SetComparisonsMessage;
}
export default function CompareTable(props: Props) {
const comparison = props.comparison;
const rows = props.comparison.rows!;
async function openQuery(kind: 'from' | 'to') {
vscode.postMessage({
t: 'openQuery',
kind,
});
}
function createRows(rows: ResultRow[], databaseUri: string) {
return (
<tbody>
{rows.map((row, rowIndex) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex}
row={row}
databaseUri={databaseUri}
/>
))}
</tbody>
);
}
return (
<table className='vscode-codeql__compare-body'>
<thead>
<tr>
<td>
<a
onClick={() => openQuery('from')}
className='vscode-codeql__compare-open'
>
{comparison.stats.fromQuery?.name}
</a>
</td>
<td>
<a
onClick={() => openQuery('to')}
className='vscode-codeql__compare-open'
>
{comparison.stats.toQuery?.name}
</a>
</td>
</tr>
<tr>
<td>{comparison.stats.fromQuery?.time}</td>
<td>{comparison.stats.toQuery?.time}</td>
</tr>
<tr>
<th>{rows.from.length} rows removed</th>
<th>{rows.to.length} rows added</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(rows.from, comparison.datebaseUri)}
</table>
</td>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(rows.to, comparison.datebaseUri)}
</table>
</td>
</tr>
</tbody>
</table>
);
}

View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"target": "es6",
"outDir": "out",
"lib": [
"es6",
"dom"
],
"jsx": "react",
"sourceMap": true,
"rootDir": "..",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules"
]
}

View File

@@ -1,10 +1,10 @@
import { DisposableObject } from 'semmle-vscode-utils';
import { DisposableObject } from './pure/disposable-object';
import { workspace, Event, EventEmitter, ConfigurationChangeEvent, ConfigurationTarget } from 'vscode';
import { DistributionManager } from './distribution';
import { logger } from './logging';
/** Helper class to look up a labelled (and possibly nested) setting. */
class Setting {
export class Setting {
name: string;
parent?: Setting;
@@ -39,24 +39,19 @@ class Setting {
const ROOT_SETTING = new Setting('codeQL');
// Enable experimental features
// 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');
/**
* This setting is deliberately not in package.json so that it does
* not appear in the settings ui in vscode itself. If users want to
* enable experimental features, they can add
* "codeQl.experimentalFeatures" directly in their vscode settings
* json file.
*/
export const EXPERIMENTAL_FEATURES_SETTING = new Setting('experimentalFeatures', ROOT_SETTING);
export const LOG_TELEMETRY = new Setting('logTelemetry', TELEMETRY_SETTING);
export const ENABLE_TELEMETRY = new Setting('enableTelemetry', TELEMETRY_SETTING);
/* Advanced setting: used to enable bqrs parsing in the cli instead of in the webview. */
export const EXPERIMENTAL_BQRS_SETTING = new Setting('experimentalBqrsParsing', ROOT_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);
const QUERY_HISTORY_SETTING = new Setting('queryHistory', ROOT_SETTING);
@@ -66,33 +61,45 @@ const QUERY_HISTORY_FORMAT_SETTING = new Setting('format', QUERY_HISTORY_SETTING
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;
repositoryName?: string;
onDidChangeDistributionConfiguration?: Event<void>;
onDidChangeConfiguration?: Event<void>;
}
// 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 RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
export const ADDITIONAL_TEST_ARGUMENTS_SETTING = new Setting('additionalTestArguments', RUNNING_TESTS_SETTING);
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
export const PAGE_SIZE = new Setting('pageSize', RESULTS_DISPLAY_SETTING);
/** When these settings change, the running query server should be restarted. */
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, SAVE_CACHE_SETTING, CACHE_SIZE_SETTING, MEMORY_SETTING, DEBUG_SETTING];
export interface QueryServerConfig {
codeQlPath: string;
debug: boolean;
numThreads: number;
saveCache: boolean;
cacheSize: number;
queryMemoryMb?: number;
timeoutSecs: number;
onDidChangeQueryServerConfiguration?: Event<void>;
onDidChangeConfiguration?: Event<void>;
}
/** When these settings change, the query history should be refreshed. */
@@ -100,10 +107,20 @@ const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING];
export interface QueryHistoryConfig {
format: string;
onDidChangeQueryHistoryConfiguration: Event<void>;
onDidChangeConfiguration: Event<void>;
}
abstract class ConfigListener extends DisposableObject {
const CLI_SETTINGS = [ADDITIONAL_TEST_ARGUMENTS_SETTING, NUMBER_OF_TEST_THREADS_SETTING, NUMBER_OF_THREADS_SETTING];
export interface CliConfig {
additionalTestArguments: string[];
numberTestThreads: number;
numberThreads: number;
onDidChangeConfiguration?: Event<void>;
}
export abstract class ConfigListener extends DisposableObject {
protected readonly _onDidChangeConfiguration = this.push(new EventEmitter<void>());
constructor() {
@@ -130,6 +147,10 @@ abstract class ConfigListener extends DisposableObject {
private updateConfiguration(): void {
this._onDidChangeConfiguration.fire();
}
public get onDidChangeConfiguration(): Event<void> {
return this._onDidChangeConfiguration.event;
}
}
export class DistributionConfigListener extends ConfigListener implements DistributionConfig {
@@ -145,8 +166,8 @@ export class DistributionConfigListener extends ConfigListener implements Distri
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() || undefined;
}
public get onDidChangeDistributionConfiguration(): Event<void> {
return this._onDidChangeConfiguration.event;
public async updateCustomCodeQlPath(newPath: string | undefined) {
await CUSTOM_CODEQL_PATH_SETTING.updateValue(newPath, ConfigurationTarget.Global);
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
@@ -155,7 +176,7 @@ export class DistributionConfigListener extends ConfigListener implements Distri
}
export class QueryServerConfigListener extends ConfigListener implements QueryServerConfig {
private constructor(private _codeQlPath: string) {
public constructor(private _codeQlPath = '') {
super();
}
@@ -180,6 +201,14 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
return NUMBER_OF_THREADS_SETTING.getValue<number>();
}
public get saveCache(): boolean {
return SAVE_CACHE_SETTING.getValue<boolean>();
}
public get cacheSize(): number {
return CACHE_SIZE_SETTING.getValue<number | null>() || 0;
}
/** Gets the configured query timeout, in seconds. This looks up the setting at the time of access. */
public get timeoutSecs(): number {
return TIMEOUT_SETTING.getValue<number | null>() || 0;
@@ -201,10 +230,6 @@ export class QueryServerConfigListener extends ConfigListener implements QuerySe
return DEBUG_SETTING.getValue<boolean>();
}
public get onDidChangeQueryServerConfiguration(): Event<void> {
return this._onDidChangeConfiguration.event;
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(QUERY_SERVER_RESTARTING_SETTINGS, e);
}
@@ -215,11 +240,48 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
this.handleDidChangeConfigurationForRelevantSettings(QUERY_HISTORY_SETTINGS, e);
}
public get onDidChangeQueryHistoryConfiguration(): Event<void> {
return this._onDidChangeConfiguration.event;
}
public get format(): string {
return QUERY_HISTORY_FORMAT_SETTING.getValue<string>();
}
}
export class CliConfigListener extends ConfigListener implements CliConfig {
public get additionalTestArguments(): string[] {
return ADDITIONAL_TEST_ARGUMENTS_SETTING.getValue();
}
public get numberTestThreads(): number {
return NUMBER_OF_TEST_THREADS_SETTING.getValue();
}
public get numberThreads(): number {
return NUMBER_OF_THREADS_SETTING.getValue<number>();
}
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
}
}
// Enable experimental features
/**
* Any settings below are deliberately not in package.json so that
* they do not appear in the settings ui in vscode itself. If users
* 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>();
}
/**
* 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);

View File

@@ -0,0 +1,147 @@
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';
/**
* A class that wraps a tree of QL results from a query that
* has an @kind of graph
*/
export default class AstBuilder {
private roots: AstItem[] | undefined;
private bqrsPath: string;
constructor(
queryResults: QueryWithResults,
private cli: CodeQLCliServer,
public db: DatabaseItem,
public fileName: string
) {
this.bqrsPath = queryResults.query.resultsPaths.resultsPath;
}
async getRoots(): Promise<AstItem[]> {
if (!this.roots) {
this.roots = await this.parseRoots();
}
return this.roots;
}
private async parseRoots(): Promise<AstItem[]> {
const options = { entities: ['id', 'url', 'string'] };
const [nodeTuples, edgeTuples, graphProperties] = await Promise.all([
await this.cli.bqrsDecode(this.bqrsPath, 'nodes', options),
await this.cli.bqrsDecode(this.bqrsPath, 'edges', options),
await this.cli.bqrsDecode(this.bqrsPath, 'graphProperties', options),
]);
if (!this.isValidGraph(graphProperties)) {
throw new Error('AST is invalid');
}
const idToItem = new Map<BqrsId, AstItem>();
const parentToChildren = new Map<BqrsId, BqrsId[]>();
const childToParent = new Map<BqrsId, BqrsId>();
const astOrder = new Map<BqrsId, number>();
const edgeLabels = new Map<BqrsId, string>();
const roots = [];
// Build up the parent-child relationships
edgeTuples.tuples.forEach(tuple => {
const [source, target, tupleType, value] = tuple as [EntityValue, EntityValue, string, string];
const sourceId = source.id!;
const targetId = target.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(targetId, Number(value));
break;
case 'semmle.label': {
childToParent.set(targetId, sourceId);
let children = parentToChildren.get(sourceId);
if (!children) {
parentToChildren.set(sourceId, children = []);
}
children.push(targetId);
// ignore values that indicate a numeric order.
if (!Number.isFinite(Number(value))) {
edgeLabels.set(targetId, value);
}
break;
}
default:
// ignore other tupleTypes since they are not needed by the ast viewer
}
});
// populate parents and children
nodeTuples.tuples.forEach(tuple => {
const [entity, tupleType, value] = tuple as [EntityValue, string, string];
const id = entity.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(id, Number(value));
break;
case 'semmle.label': {
// If an edge label exists, include it and separate from the node label using ':'
const nodeLabel = value ?? entity.label;
const edgeLabel = edgeLabels.get(id);
const label = [edgeLabel, nodeLabel].filter(e => e).join(': ');
const item = {
id,
label,
location: entity.url,
fileLocation: fileRangeFromURI(entity.url, this.db),
children: [] as ChildAstItem[],
order: Number.MAX_SAFE_INTEGER
};
idToItem.set(id, item);
const parent = idToItem.get(childToParent.has(id) ? childToParent.get(id)! : -1);
if (parent) {
const astItem = item as ChildAstItem;
astItem.parent = parent;
parent.children.push(astItem);
}
const children = parentToChildren.has(id) ? parentToChildren.get(id)! : [];
children.forEach(childId => {
const child = idToItem.get(childId) as ChildAstItem | undefined;
if (child) {
child.parent = item;
item.children.push(child);
}
});
break;
}
default:
// ignore other tupleTypes since they are not needed by the ast viewer
}
});
// find the roots and add the order
for (const [, item] of idToItem) {
item.order = astOrder.has(item.id)
? astOrder.get(item.id)!
: Number.MAX_SAFE_INTEGER;
if (!('parent' in item)) {
roots.push(item);
}
}
return roots;
}
private isValidGraph(graphProperties: DecodedBqrsChunk) {
const tuple = graphProperties?.tuples?.find(t => t[0] === 'semmle.graphKind');
return tuple?.[1] === 'tree';
}
}

View File

@@ -0,0 +1,31 @@
import * as vscode from 'vscode';
import { UrlValue, LineColumnLocation } from '../pure/bqrs-cli-types';
import { isEmptyPath } from '../pure/bqrs-utils';
import { DatabaseItem } from '../databases';
export default function fileRangeFromURI(uri: UrlValue | undefined, db: DatabaseItem): vscode.Location | undefined {
if (!uri || typeof uri === 'string') {
return undefined;
} else if ('startOffset' in uri) {
return undefined;
} else {
const loc = uri as LineColumnLocation;
if (isEmptyPath(loc.uri)) {
return undefined;
}
const range = new vscode.Range(Math.max(0, (loc.startLine || 0) - 1),
Math.max(0, (loc.startColumn || 0) - 1),
Math.max(0, (loc.endLine || 0) - 1),
Math.max(0, (loc.endColumn || 0)));
try {
if (uri.uri.startsWith('file:')) {
return new vscode.Location(db.resolveSourceFile(uri.uri), range);
}
return undefined;
} catch (e) {
return undefined;
}
}
}

View File

@@ -0,0 +1,37 @@
export enum KeyType {
DefinitionQuery = 'DefinitionQuery',
ReferenceQuery = 'ReferenceQuery',
PrintAstQuery = 'PrintAstQuery',
}
export function tagOfKeyType(keyType: KeyType): string {
switch (keyType) {
case KeyType.DefinitionQuery:
return 'ide-contextual-queries/local-definitions';
case KeyType.ReferenceQuery:
return 'ide-contextual-queries/local-references';
case KeyType.PrintAstQuery:
return 'ide-contextual-queries/print-ast';
}
}
export function nameOfKeyType(keyType: KeyType): string {
switch (keyType) {
case KeyType.DefinitionQuery:
return 'definitions';
case KeyType.ReferenceQuery:
return 'references';
case KeyType.PrintAstQuery:
return 'print AST';
}
}
export function kindOfKeyType(keyType: KeyType): string {
switch (keyType) {
case KeyType.DefinitionQuery:
case KeyType.ReferenceQuery:
return 'definitions';
case KeyType.PrintAstQuery:
return 'graph';
}
}

View File

@@ -0,0 +1,124 @@
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 '../commandRunner';
import { KeyType } from './keyType';
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
const SELECT_QUERY_NAME = '#select';
export const TEMPLATE_NAME = 'selectedSourceFile';
export interface FullLocationLink extends vscode.LocationLink {
originUri: vscode.Uri;
}
/**
* This function executes a contextual query inside a given database, filters, and converts
* the results into source locations. This function is the workhorse for all search-based
* contextual queries like find references and find definitions.
*
* @param cli The cli server
* @param qs The query server client
* @param dbm The database manager
* @param uriString The selected source file and location
* @param keyType The contextual query type to run
* @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,
dbm: DatabaseManager,
uriString: string,
keyType: KeyType,
progress: ProgressCallback,
token: vscode.CancellationToken,
filter: (src: string, dest: string) => boolean
): Promise<FullLocationLink[]> {
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString, true));
const sourceArchiveUri = encodeArchiveBasePath(uri.sourceArchiveZipPath);
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
if (!db) {
return [];
}
const qlpack = await qlpackOfDatabase(cli, db);
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
);
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
links.push(...await getLinksFromResults(results, cli, db, filter));
}
}
return links;
}
async function getLinksFromResults(
results: QueryWithResults,
cli: CodeQLCliServer,
db: DatabaseItem,
filter: (srcFile: string, destFile: string) => boolean
): Promise<FullLocationLink[]> {
const localLinks: FullLocationLink[] = [];
const bqrsPath = results.query.resultsPaths.resultsPath;
const info = await cli.bqrsInfo(bqrsPath);
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
if (isValidSelect(selectInfo)) {
// TODO: Page this
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
for (const tuple of allTuples.tuples) {
const [src, dest] = tuple as [EntityValue, EntityValue];
const srcFile = src.url && fileRangeFromURI(src.url, db);
const destFile = dest.url && fileRangeFromURI(dest.url, db);
if (srcFile && destFile && filter(srcFile.uri.toString(), destFile.uri.toString())) {
localLinks.push({
targetRange: destFile.range,
targetUri: destFile.uri,
originSelectionRange: srcFile.range,
originUri: srcFile.uri
});
}
}
}
return localLinks;
}
function createTemplates(path: string): messages.TemplateDefinitions {
return {
[TEMPLATE_NAME]: {
values: {
tuples: [[{
stringValue: path
}]]
}
}
};
}
function isValidSelect(selectInfo: ResultSetSchema | undefined) {
return selectInfo && selectInfo.columns.length == 3
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
&& selectInfo.columns[2].kind == ColumnKindCode.STRING;
}

View File

@@ -0,0 +1,48 @@
import * as fs from 'fs-extra';
import * as yaml from 'js-yaml';
import * as tmp from 'tmp-promise';
import * as helpers from '../helpers';
import {
KeyType,
kindOfKeyType,
nameOfKeyType,
tagOfKeyType
} from './keyType';
import { CodeQLCliServer } from '../cli';
import { DatabaseItem } from '../databases';
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string> {
if (db.contents === undefined) {
throw new Error('Database is invalid and cannot infer QLPack.');
}
const datasetPath = db.contents.datasetUri.fsPath;
const dbscheme = await helpers.getPrimaryDbscheme(datasetPath);
return await helpers.getQlPackForDbscheme(cli, dbscheme);
}
export async function resolveQueries(cli: CodeQLCliServer, qlpack: 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 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;
}

View File

@@ -0,0 +1,209 @@
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 } from '../helpers';
import { ProgressCallback, withProgress } from '../commandRunner';
import * as messages from '../pure/messages';
import { QueryServerClient } from '../queryserver-client';
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';
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';
/**
* Run templated CodeQL queries to find definitions and references in
* source-language files. We may eventually want to find a way to
* generalize this to other custom queries, e.g. showing dataflow to
* or from a selected identifier.
*/
export class TemplateQueryDefinitionProvider implements DefinitionProvider {
private cache: CachedOperation<LocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<LocationLink[]>(this.getDefinitions.bind(this));
}
async provideDefinition(document: TextDocument, position: Position, _token: CancellationToken): Promise<LocationLink[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: LocationLink[] = [];
for (const link of fileLinks) {
if (link.originSelectionRange!.contains(position)) {
locLinks.push(link);
}
}
return locLinks;
}
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
return withProgress({
location: ProgressLocation.Notification,
cancellable: true,
title: 'Finding definitions'
}, async (progress, token) => {
return getLocationsForUriString(
this.cli,
this.qs,
this.dbm,
uriString,
KeyType.DefinitionQuery,
progress,
token,
(src, _dest) => src === uriString
);
});
}
}
export class TemplateQueryReferenceProvider implements ReferenceProvider {
private cache: CachedOperation<FullLocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
}
async provideReferences(
document: TextDocument,
position: Position,
_context: ReferenceContext,
_token: CancellationToken
): Promise<Location[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: Location[] = [];
for (const link of fileLinks) {
if (link.targetRange!.contains(position)) {
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
}
}
return locLinks;
}
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
return withProgress({
location: ProgressLocation.Notification,
cancellable: true,
title: 'Finding references'
}, async (progress, token) => {
return getLocationsForUriString(
this.cli,
this.qs,
this.dbm,
uriString,
KeyType.DefinitionQuery,
progress,
token,
(src, _dest) => src === uriString
);
});
}
}
export class TemplatePrintAstProvider {
private cache: CachedOperation<QueryWithResults>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<QueryWithResults>(this.getAst.bind(this));
}
async provideAst(
progress: ProgressCallback,
token: CancellationToken,
document?: TextDocument
): Promise<AstBuilder | undefined> {
if (!document) {
throw new Error('Cannot view the AST. Please select a valid source file inside a CodeQL database.');
}
const queryResults = this.shouldCache()
? await this.cache.get(document.uri.toString(), progress, token)
: await this.getAst(document.uri.toString(), progress, token);
return new AstBuilder(
queryResults, this.cli,
this.dbm.findDatabaseItem(Uri.parse(queryResults.database.databaseUri!, true))!,
document.fileName
);
}
private shouldCache() {
return !(isCanary() && NO_CACHE_AST_VIEWER.getValue<boolean>());
}
private async getAst(
uriString: string,
progress: ProgressCallback,
token: CancellationToken
): Promise<QueryWithResults> {
const uri = Uri.parse(uriString, true);
if (uri.scheme !== zipArchiveScheme) {
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 qlpack = await qlpackOfDatabase(this.cli, db);
const queries = await resolveQueries(this.cli, qlpack, 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: messages.TemplateDefinitions = {
[TEMPLATE_NAME]: {
values: {
tuples: [[{
stringValue: zippedArchive.pathWithinSourceArchive
}]]
}
}
};
return await compileAndRunQueryAgainstDatabase(
this.cli,
this.qs,
db,
false,
Uri.file(query),
progress,
token,
templates
);
}
}

View File

@@ -1,103 +1,234 @@
import * as fetch from "node-fetch";
import * as unzipper from "unzipper";
import { Uri, ProgressOptions, ProgressLocation, commands, window } from "vscode";
import * as fs from "fs-extra";
import * as path from "path";
import { DatabaseManager } from "./databases";
import { ProgressCallback, showAndLogErrorMessage, withProgress } from "./helpers";
import fetch, { Response } from 'node-fetch';
import * as unzipper from 'unzipper';
import { zip } from 'zip-a-folder';
import {
Uri,
CancellationToken,
commands,
window,
} from 'vscode';
import * as fs from 'fs-extra';
import * as path from 'path';
export default async function promptFetchDatabase(dbm: DatabaseManager, storagePath: string) {
try {
const databaseUrl = await window.showInputBox({
prompt: 'Enter URL of zipfile of database to download'
});
import { DatabaseManager, DatabaseItem } from './databases';
import {
showAndLogInformationMessage,
} from './helpers';
import {
reportStreamProgress,
ProgressCallback,
} from './commandRunner';
import { logger } from './logging';
import { tmpDir } from './run-queries';
/**
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
*
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function promptImportInternetDatabase(
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
token: CancellationToken,
): Promise<DatabaseItem | undefined> {
const databaseUrl = await window.showInputBox({
prompt: 'Enter URL of zipfile of database to download',
});
if (!databaseUrl) {
return;
}
validateHttpsUrl(databaseUrl);
const item = await databaseArchiveFetcher(
databaseUrl,
databaseManager,
storagePath,
progress,
token
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database downloaded and imported successfully.');
}
return item;
}
/**
* 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 databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function promptImportLgtmDatabase(
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> {
const lgtmUrl = await window.showInputBox({
prompt:
'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);
if (databaseUrl) {
validateUrl(databaseUrl);
const progressOptions: ProgressOptions = {
location: ProgressLocation.Notification,
title: 'Adding database from URL',
cancellable: false,
};
await withProgress(progressOptions, async progress => await databaseFetcher(databaseUrl, dbm, storagePath, progress));
commands.executeCommand('codeQLDatabases.focus');
const item = await databaseArchiveFetcher(
databaseUrl,
databaseManager,
storagePath,
progress,
token
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database downloaded and imported successfully.');
}
return item;
}
} else {
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
}
return;
}
/**
* Imports a database from a local archive.
*
* @param databaseUrl the file url of the archive to import
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
*/
export async function importArchiveDatabase(
databaseUrl: string,
databaseManager: DatabaseManager,
storagePath: string,
progress: ProgressCallback,
token: CancellationToken,
): Promise<DatabaseItem | undefined> {
try {
const item = await databaseArchiveFetcher(
databaseUrl,
databaseManager,
storagePath,
progress,
token
);
if (item) {
commands.executeCommand('codeQLDatabases.focus');
showAndLogInformationMessage('Database unzipped and imported successfully.');
}
return item;
} catch (e) {
showAndLogErrorMessage(e.message);
if (e.message.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
throw e;
}
}
}
async function databaseFetcher(
/**
* Fetches an archive database. The database might be on the internet
* or in the local filesystem.
*
* @param databaseUrl URL from which to grab the database
* @param databaseManager the DatabaseManager
* @param storagePath where to store the unzipped database.
* @param progress callback to send progress messages to
* @param token cancellation token
*/
async function databaseArchiveFetcher(
databaseUrl: string,
databasesManager: DatabaseManager,
databaseManager: DatabaseManager,
storagePath: string,
progressCallback: ProgressCallback
): Promise<void> {
progressCallback({
maxStep: 3,
message: 'Downloading database',
step: 1
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem> {
progress({
message: 'Getting database',
step: 1,
maxStep: 4,
});
if (!storagePath) {
throw new Error("No storage path specified.");
throw new Error('No storage path specified.');
}
await fs.ensureDir(storagePath);
const unzipPath = await getStorageFolder(storagePath, databaseUrl);
const response = await fetch.default(databaseUrl);
const unzipStream = unzipper.Extract({
path: unzipPath
});
progressCallback({
maxStep: 3,
message: 'Unzipping database',
step: 2
});
await new Promise((resolve, reject) => {
response.body.on('error', reject);
unzipStream.on('error', reject);
unzipStream.on('close', resolve);
response.body.pipe(unzipStream);
});
progressCallback({
maxStep: 3,
if (isFile(databaseUrl)) {
await readAndUnzip(databaseUrl, unzipPath, progress);
} else {
await fetchAndUnzip(databaseUrl, unzipPath, progress);
}
progress({
message: 'Opening database',
step: 3
step: 3,
maxStep: 4,
});
// if there is a single directory inside, then assume that's what we want to import
const dirs = await fs.readdir(unzipPath);
const dbPath = dirs?.length === 1 && (await fs.stat(path.join(unzipPath, dirs[0]))).isDirectory
? path.join(unzipPath, dirs[0])
: unzipPath;
// find the path to the database. The actual database might be in a sub-folder
const dbPath = await findDirWithFile(
unzipPath,
'.dbinfo',
'codeql-database.yml'
);
if (dbPath) {
progress({
message: 'Validating and fixing source location',
step: 4,
maxStep: 4,
});
await ensureZippedSourceLocation(dbPath);
// might need to upgrade before importing...
const item = await databasesManager.openDatabase(Uri.parse(dbPath));
databasesManager.setCurrentDatabaseItem(item);
const item = await databaseManager.openDatabase(progress, token, Uri.file(dbPath));
await databaseManager.setCurrentDatabaseItem(item);
return item;
} else {
throw new Error('Database not found in archive.');
}
}
async function getStorageFolder(storagePath: string, urlStr: string) {
// we need to generate a folder name for the unzipped archive,
// this needs to be human readable since we may use this name as the initial
// name for the database
const url = Uri.parse(urlStr);
let lastName = path.basename(url.path).substring(0, 255);
if (lastName.endsWith(".zip")) {
// MacOS has a max filename length of 255
// and remove a few extra chars in case we need to add a counter at the end.
let lastName = path.basename(url.path).substring(0, 250);
if (lastName.endsWith('.zip')) {
lastName = lastName.substring(0, lastName.length - 4);
}
const realpath = await fs.realpath(storagePath);
let folderName = path.join(realpath, lastName);
// avoid overwriting existing folders
let counter = 0;
while (await fs.pathExists(folderName)) {
counter++;
folderName = path.join(realpath, `${lastName}-${counter}`);
if (counter > 100) {
throw new Error("Could not find a unique name for downloaded database.");
throw new Error('Could not find a unique name for downloaded database.');
}
}
return folderName;
}
function validateUrl(databaseUrl: string) {
function validateHttpsUrl(databaseUrl: string) {
let uri;
try {
uri = Uri.parse(databaseUrl, true);
@@ -109,3 +240,238 @@ function validateUrl(databaseUrl: string) {
throw new Error('Must use https for downloading a database.');
}
}
async function readAndUnzip(
zipUrl: string,
unzipPath: string,
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)}`
});
// 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,
unzipPath: string,
progress?: ProgressCallback
) {
// 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.
const archivePath = path.join(tmpDir.name, `archive-${Date.now()}.zip`);
progress?.({
maxStep: 3,
message: 'Downloading database',
step: 1,
});
const response = await checkForFailingResponse(await fetch(databaseUrl));
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, progress);
// remove archivePath eagerly since these archives can be large.
await fs.remove(archivePath);
}
async function checkForFailingResponse(response: Response): Promise<Response | never> {
if (response.ok) {
return response;
}
// An error downloading the database. Attempt to extract the resaon behind it.
const text = await response.text();
let msg: string;
try {
const obj = JSON.parse(text);
msg = obj.error || obj.message || obj.reason || JSON.stringify(obj, null, 2);
} catch (e) {
msg = text;
}
throw new Error(`Error downloading database.\n\nReason: ${msg}`);
}
function isFile(databaseUrl: string) {
return Uri.parse(databaseUrl).scheme === 'file';
}
/**
* Recursively looks for a file in a directory. If the file exists, then returns the directory containing the file.
*
* @param dir The directory to search
* @param toFind The file to recursively look for in this directory
*
* @returns the directory containing the file, or undefined if not found.
*/
// exported for testing
export async function findDirWithFile(
dir: string,
...toFind: string[]
): Promise<string | undefined> {
if (!(await fs.stat(dir)).isDirectory()) {
return;
}
const files = await fs.readdir(dir);
if (toFind.some((file) => files.includes(file))) {
return dir;
}
for (const file of files) {
const newPath = path.join(dir, file);
const result = await findDirWithFile(newPath, ...toFind);
if (result) {
return result;
}
}
return;
}
/**
* 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),
* LGTM currently hosts projects from Bitbucket (b), GitLab (gl) and plain git (git).
*
* This function accepts any url that matches the pattern above. It also accepts the
* raw project slug, e.g., `g/myorg/myproject`
*
* After the `{provider}/{org}/{name}` path components, there may be the components
* related to sub pages.
*
* @param lgtmUrl The URL to the lgtm project
*
* @return true if this looks like an LGTM project url
*/
// exported for testing
export function looksLikeLgtmUrl(lgtmUrl: string | undefined): lgtmUrl is string {
if (!lgtmUrl) {
return false;
}
if (convertRawLgtmSlug(lgtmUrl)) {
return true;
}
try {
const uri = Uri.parse(lgtmUrl, true);
if (uri.scheme !== 'https') {
return false;
}
if (uri.authority !== 'lgtm.com' && uri.authority !== 'www.lgtm.com') {
return false;
}
const paths = uri.path.split('/').filter((segment) => 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;
}
// exported for testing
export async function convertToDatabaseUrl(lgtmUrl: string) {
try {
lgtmUrl = convertRawLgtmSlug(lgtmUrl) || lgtmUrl;
const uri = Uri.parse(lgtmUrl, true);
const paths = ['api', 'v1.0'].concat(
uri.path.split('/').filter((segment) => segment)
).slice(0, 6);
const projectUrl = `https://lgtm.com/${paths.join('/')}`;
const projectResponse = await fetch(projectUrl);
const projectJson = await projectResponse.json();
if (projectJson.code === 404) {
throw new Error();
}
const language = await promptForLanguage(projectJson);
if (!language) {
return;
}
return `https://lgtm.com/${[
'api',
'v1.0',
'snapshots',
projectJson.id,
language,
].join('/')}`;
} catch (e) {
logger.log(`Error: ${e.message}`);
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
}
}
async function promptForLanguage(
projectJson: any
): Promise<string | undefined> {
if (!projectJson?.languages?.length) {
return;
}
if (projectJson.languages.length === 1) {
return projectJson.languages[0].language;
}
return await window.showQuickPick(
projectJson.languages.map((lang: { language: string }) => lang.language), {
placeHolder: 'Select the database language to download:'
}
);
}
/**
* Databases created by the old odasa tool will not have a zipped
* source location. However, this extension works better if sources
* are zipped.
*
* This function ensures that the source location is zipped. If the
* `src` folder exists and the `src.zip` file does not, the `src`
* folder will be zipped and then deleted.
*
* @param databasePath The full path to the unzipped database
*/
async function ensureZippedSourceLocation(databasePath: string): Promise<void> {
const srcFolderPath = path.join(databasePath, 'src');
const srcZipPath = srcFolderPath + '.zip';
if ((await fs.pathExists(srcFolderPath)) && !(await fs.pathExists(srcZipPath))) {
await zip(srcFolderPath, srcZipPath);
await fs.remove(srcFolderPath);
}
}

View File

@@ -1,13 +1,43 @@
import * as path from 'path';
import { DisposableObject } from 'semmle-vscode-utils';
import { commands, Event, EventEmitter, ExtensionContext, ProviderResult, TreeDataProvider, TreeItem, Uri, window, env } from 'vscode';
import * as cli from './cli';
import { DatabaseItem, DatabaseManager, getUpgradesDirectories } from './databases';
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from './helpers';
import { DisposableObject } from './pure/disposable-object';
import {
Event,
EventEmitter,
ProviderResult,
TreeDataProvider,
TreeItem,
Uri,
window,
env,
} from 'vscode';
import * as fs from 'fs-extra';
import {
DatabaseChangedEvent,
DatabaseItem,
DatabaseManager,
} from './databases';
import {
commandRunner,
commandRunnerWithProgress,
ProgressCallback,
} from './commandRunner';
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
showAndLogErrorMessage
} from './helpers';
import { logger } from './logging';
import { clearCacheInDatabase, UserCancellationException } from './run-queries';
import { clearCacheInDatabase } from './run-queries';
import * as qsClient from './queryserver-client';
import { upgradeDatabase } from './upgrades';
import { upgradeDatabaseExplicit } from './upgrades';
import {
importArchiveDatabase,
promptImportInternetDatabase,
promptImportLgtmDatabase,
} from './databaseFetcher';
import { CancellationToken } from 'vscode';
import { asyncFilter } from './pure/helpers-pure';
type ThemableIconPath = { light: string; dark: string } | string;
@@ -24,21 +54,23 @@ const SELECTED_DATABASE_ICON: ThemableIconPath = {
*/
const INVALID_DATABASE_ICON: ThemableIconPath = 'media/red-x.svg';
function joinThemableIconPath(base: string, iconPath: ThemableIconPath): ThemableIconPath {
function joinThemableIconPath(
base: string,
iconPath: ThemableIconPath
): ThemableIconPath {
if (typeof iconPath == 'object')
return {
light: path.join(base, iconPath.light),
dark: path.join(base, iconPath.dark)
dark: path.join(base, iconPath.dark),
};
else
return path.join(base, iconPath);
else return path.join(base, iconPath);
}
enum SortOrder {
NameAsc = 'NameAsc',
NameDesc = 'NameDesc',
DateAddedAsc = 'DateAddedAsc',
DateAddedDesc = 'DateAddedDesc'
DateAddedDesc = 'DateAddedDesc',
}
/**
@@ -46,48 +78,71 @@ enum SortOrder {
*/
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(private ctx: ExtensionContext, private databaseManager: DatabaseManager) {
constructor(
private databaseManager: DatabaseManager,
private readonly extensionPath: string
) {
super();
this.currentDatabaseItem = databaseManager.currentDatabaseItem;
this.push(this.databaseManager.onDidChangeDatabaseItem(this.handleDidChangeDatabaseItem));
this.push(this.databaseManager.onDidChangeCurrentDatabaseItem(
this.handleDidChangeCurrentDatabaseItem));
this.push(
this.databaseManager.onDidChangeDatabaseItem(
this.handleDidChangeDatabaseItem
)
);
this.push(
this.databaseManager.onDidChangeCurrentDatabaseItem(
this.handleDidChangeCurrentDatabaseItem
)
);
}
public get onDidChangeTreeData(): Event<DatabaseItem | undefined> {
return this._onDidChangeTreeData.event;
}
private handleDidChangeDatabaseItem = (databaseItem: DatabaseItem | undefined): void => {
this._onDidChangeTreeData.fire(databaseItem);
}
private handleDidChangeDatabaseItem = (event: DatabaseChangedEvent): void => {
// Note that events from the database manager are instances of DatabaseChangedEvent
// and events fired by the UI are instances of DatabaseItem
private handleDidChangeCurrentDatabaseItem = (databaseItem: DatabaseItem | undefined): void => {
// When event.item is undefined, then the entire tree is refreshed.
// When event.item is a db item, then only that item is refreshed.
this._onDidChangeTreeData.fire(event.item);
};
private handleDidChangeCurrentDatabaseItem = (
event: DatabaseChangedEvent
): void => {
if (this.currentDatabaseItem) {
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
}
this.currentDatabaseItem = databaseItem;
this.currentDatabaseItem = event.item;
if (this.currentDatabaseItem) {
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
}
}
};
public getTreeItem(element: DatabaseItem): TreeItem {
const item = new TreeItem(element.name);
if (element === this.currentDatabaseItem) {
item.iconPath = joinThemableIconPath(this.ctx.extensionPath, SELECTED_DATABASE_ICON);
item.iconPath = joinThemableIconPath(
this.extensionPath,
SELECTED_DATABASE_ICON
);
} else if (element.error !== undefined) {
item.iconPath = joinThemableIconPath(this.ctx.extensionPath, INVALID_DATABASE_ICON);
item.iconPath = joinThemableIconPath(
this.extensionPath,
INVALID_DATABASE_ICON
);
}
item.tooltip = element.databaseUri.fsPath;
item.description = element.language;
return item;
}
@@ -96,17 +151,16 @@ class DatabaseTreeDataProvider extends DisposableObject
return this.databaseManager.databaseItems.slice(0).sort((db1, db2) => {
switch (this.sortOrder) {
case SortOrder.NameAsc:
return db1.name.localeCompare(db2.name);
return db1.name.localeCompare(db2.name, env.language);
case SortOrder.NameDesc:
return db2.name.localeCompare(db1.name);
return db2.name.localeCompare(db1.name, env.language);
case SortOrder.DateAddedAsc:
return (db1.dateAdded || 0) - (db2.dateAdded || 0);
case SortOrder.DateAddedDesc:
return (db2.dateAdded || 0) - (db1.dateAdded || 0);
}
});
}
else {
} else {
return [];
}
}
@@ -133,8 +187,7 @@ class DatabaseTreeDataProvider extends DisposableObject
function getFirst(list: Uri[] | undefined): Uri | undefined {
if (list === undefined || list.length === 0) {
return undefined;
}
else {
} else {
return list[0];
}
}
@@ -147,12 +200,13 @@ function getFirst(list: Uri[] | undefined): Uri | undefined {
* XXX: no validation is done other than checking the directory name
* to make sure it really is a database directory.
*/
async function chooseDatabaseDir(): Promise<Uri | undefined> {
async function chooseDatabaseDir(byFolder: boolean): Promise<Uri | undefined> {
const chosen = await window.showOpenDialog({
openLabel: 'Choose Database',
canSelectFiles: true,
canSelectFolders: true,
canSelectMany: false
openLabel: byFolder ? 'Choose Database folder' : 'Choose Database archive',
canSelectFiles: !byFolder,
canSelectFolders: byFolder,
canSelectMany: false,
filters: byFolder ? {} : { Archives: ['zip'] },
});
return getFirst(chosen);
}
@@ -161,35 +215,265 @@ export class DatabaseUI extends DisposableObject {
private treeDataProvider: DatabaseTreeDataProvider;
public constructor(
ctx: ExtensionContext,
private cliserver: cli.CodeQLCliServer,
private databaseManager: DatabaseManager,
private readonly queryServer: qsClient.QueryServerClient | undefined
private readonly queryServer: qsClient.QueryServerClient | undefined,
private readonly storagePath: string,
readonly extensionPath: string
) {
super();
this.treeDataProvider = this.push(new DatabaseTreeDataProvider(ctx, databaseManager));
this.push(window.createTreeView('codeQLDatabases', { treeDataProvider: this.treeDataProvider }));
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabase', this.handleChooseDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQL.setCurrentDatabase', this.handleSetCurrentDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQL.upgradeCurrentDatabase', this.handleUpgradeCurrentDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQL.clearCache', this.handleClearCache));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.setCurrentDatabase', this.handleMakeCurrentDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.sortByName', this.handleSortByName));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.sortByDateAdded', this.handleSortByDateAdded));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.removeDatabase', this.handleRemoveDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.upgradeDatabase', this.handleUpgradeDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.renameDatabase', this.handleRenameDatabase));
ctx.subscriptions.push(commands.registerCommand('codeQLDatabases.openDatabaseFolder', this.handleOpenFolder));
this.treeDataProvider = this.push(
new DatabaseTreeDataProvider(databaseManager, extensionPath)
);
this.push(
window.createTreeView('codeQLDatabases', {
treeDataProvider: this.treeDataProvider,
canSelectMany: true,
})
);
}
private handleMakeCurrentDatabase = async (databaseItem: DatabaseItem): Promise<void> => {
init() {
logger.log('Registering database panel commands.');
this.push(
commandRunnerWithProgress(
'codeQL.setCurrentDatabase',
this.handleSetCurrentDatabase,
{
title: 'Importing database from archive',
}
)
);
this.push(
commandRunnerWithProgress(
'codeQL.upgradeCurrentDatabase',
this.handleUpgradeCurrentDatabase,
{
title: 'Upgrading current database',
cancellable: true,
}
)
);
this.push(
commandRunnerWithProgress(
'codeQL.clearCache',
this.handleClearCache,
{
title: 'Clearing Cache',
})
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseFolder',
this.handleChooseDatabaseFolder,
{
title: 'Adding database from folder',
}
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseArchive',
this.handleChooseDatabaseArchive,
{
title: 'Adding database from archive',
}
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseInternet',
this.handleChooseDatabaseInternet,
{
title: 'Adding database from URL',
}
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.chooseDatabaseLgtm',
this.handleChooseDatabaseLgtm,
{
title: 'Adding database from LGTM. Choose a language from the dropdown, if requested.',
})
);
this.push(
commandRunner(
'codeQLDatabases.setCurrentDatabase',
this.handleMakeCurrentDatabase
)
);
this.push(
commandRunner(
'codeQLDatabases.sortByName',
this.handleSortByName
)
);
this.push(
commandRunner(
'codeQLDatabases.sortByDateAdded',
this.handleSortByDateAdded
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.removeDatabase',
this.handleRemoveDatabase,
{
title: 'Removing database',
cancellable: false
}
)
);
this.push(
commandRunnerWithProgress(
'codeQLDatabases.upgradeDatabase',
this.handleUpgradeDatabase,
{
title: 'Upgrading database',
cancellable: true,
}
)
);
this.push(
commandRunner(
'codeQLDatabases.renameDatabase',
this.handleRenameDatabase
)
);
this.push(
commandRunner(
'codeQLDatabases.openDatabaseFolder',
this.handleOpenFolder
)
);
this.push(
commandRunner(
'codeQLDatabases.removeOrphanedDatabases',
this.handleRemoveOrphanedDatabases
)
);
}
private handleMakeCurrentDatabase = async (
databaseItem: DatabaseItem
): Promise<void> => {
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
}
};
private handleChooseDatabase = async (): Promise<DatabaseItem | undefined> => {
return await this.chooseAndSetDatabase();
handleChooseDatabaseFolder = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> => {
try {
return await this.chooseAndSetDatabase(true, progress, token);
} catch (e) {
showAndLogErrorMessage(e.message);
return undefined;
}
};
handleRemoveOrphanedDatabases = async (): Promise<void> => {
logger.log('Removing orphaned databases from workspace storage.');
let dbDirs = undefined;
if (
!(await fs.pathExists(this.storagePath)) ||
!(await fs.stat(this.storagePath)).isDirectory()
) {
logger.log('Missing or invalid storage directory. Not trying to remove orphaned databases.');
return;
}
dbDirs =
// read directory
(await fs.readdir(this.storagePath, { withFileTypes: true }))
// remove non-directories
.filter(dirent => dirent.isDirectory())
// get the full path
.map(dirent => path.join(this.storagePath, dirent.name))
// remove databases still in workspace
.filter(dbDir => {
const dbUri = Uri.file(dbDir);
return this.databaseManager.databaseItems.every(item => item.databaseUri.fsPath !== dbUri.fsPath);
});
// remove non-databases
dbDirs = await asyncFilter(dbDirs, isLikelyDatabaseRoot);
if (!dbDirs.length) {
logger.log('No orphaned databases found.');
return;
}
// delete
const failures = [] as string[];
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
} catch (e) {
failures.push(`${path.basename(dbDir)}`);
}
})
);
if (failures.length) {
const dirname = path.dirname(failures[0]);
showAndLogErrorMessage(
`Failed to delete unused databases (${
failures.join(', ')
}).\nTo delete unused databases, please remove them manually from the storage folder ${dirname}.`
);
}
};
handleChooseDatabaseArchive = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> => {
try {
return await this.chooseAndSetDatabase(false, progress, token);
} catch (e) {
showAndLogErrorMessage(e.message);
return undefined;
}
};
handleChooseDatabaseInternet = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<
DatabaseItem | undefined
> => {
return await promptImportInternetDatabase(
this.databaseManager,
this.storagePath,
progress,
token
);
};
handleChooseDatabaseLgtm = async (
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> => {
return await promptImportLgtmDatabase(
this.databaseManager,
this.storagePath,
progress,
token
);
};
async tryUpgradeCurrentDatabase(
progress: ProgressCallback,
token: CancellationToken
) {
await this.handleUpgradeCurrentDatabase(progress, token);
}
private handleSortByName = async () => {
@@ -198,7 +482,7 @@ export class DatabaseUI extends DisposableObject {
} else {
this.treeDataProvider.sortOrder = SortOrder.NameAsc;
}
}
};
private handleSortByDateAdded = async () => {
if (this.treeDataProvider.sortOrder === SortOrder.DateAddedAsc) {
@@ -206,114 +490,174 @@ export class DatabaseUI extends DisposableObject {
} else {
this.treeDataProvider.sortOrder = SortOrder.DateAddedAsc;
}
}
};
private handleUpgradeCurrentDatabase = async (): Promise<void> => {
await this.handleUpgradeDatabase(this.databaseManager.currentDatabaseItem);
}
private handleUpgradeCurrentDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
await this.handleUpgradeDatabase(
progress, token,
this.databaseManager.currentDatabaseItem,
[]
);
};
private handleUpgradeDatabase = async (databaseItem: DatabaseItem | undefined): Promise<void> => {
private handleUpgradeDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem | undefined,
multiSelect: DatabaseItem[] | undefined,
): Promise<void> => {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) => this.handleUpgradeDatabase(progress, token, dbItem, []))
);
}
if (this.queryServer === undefined) {
logger.log('Received request to upgrade database, but there is no running query server.');
return;
throw new Error(
'Received request to upgrade database, but there is no running query server.'
);
}
if (databaseItem === undefined) {
logger.log('Received request to upgrade database, but no database was provided.');
return;
throw new Error(
'Received request to upgrade database, but no database was provided.'
);
}
if (databaseItem.contents === undefined) {
logger.log('Received request to upgrade database, but database contents could not be found.');
return;
throw new Error(
'Received request to upgrade database, but database contents could not be found.'
);
}
if (databaseItem.contents.dbSchemeUri === undefined) {
logger.log('Received request to upgrade database, but database has no schema.');
return;
throw new Error(
'Received request to upgrade database, but database has no schema.'
);
}
// Search for upgrade scripts in any workspace folders available
const searchPath: string[] = getOnDiskWorkspaceFolders();
const upgradeInfo = await this.cliserver.resolveUpgrades(
databaseItem.contents.dbSchemeUri.fsPath,
searchPath,
await upgradeDatabaseExplicit(
this.queryServer,
databaseItem,
progress,
token
);
};
const { scripts, finalDbscheme } = upgradeInfo;
if (finalDbscheme === undefined) {
logger.log('Could not determine target dbscheme to upgrade to.');
return;
private handleClearCache = async (
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> => {
if (
this.queryServer !== undefined &&
this.databaseManager.currentDatabaseItem !== undefined
) {
await clearCacheInDatabase(
this.queryServer,
this.databaseManager.currentDatabaseItem,
progress,
token
);
}
const targetDbSchemeUri = Uri.file(finalDbscheme);
};
private handleSetCurrentDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
uri: Uri,
): Promise<void> => {
try {
await upgradeDatabase(this.queryServer, databaseItem, targetDbSchemeUri, getUpgradesDirectories(scripts));
}
catch (e) {
if (e instanceof UserCancellationException) {
logger.log(e.message);
}
else
throw e;
}
}
private handleClearCache = async (): Promise<void> => {
if ((this.queryServer !== undefined) &&
(this.databaseManager.currentDatabaseItem !== undefined)) {
await clearCacheInDatabase(this.queryServer, this.databaseManager.currentDatabaseItem);
}
}
private handleSetCurrentDatabase = async (uri: Uri): Promise<DatabaseItem | undefined> => {
return await this.setCurrentDatabase(uri);
}
private handleRemoveDatabase = (databaseItem: DatabaseItem): void => {
this.databaseManager.removeDatabaseItem(databaseItem);
}
private handleRenameDatabase = async (databaseItem: DatabaseItem): Promise<void> => {
try {
const newName = await window.showInputBox({
prompt: 'Choose new database name',
value: databaseItem.name
});
if (newName) {
this.databaseManager.renameDatabaseItem(databaseItem, newName);
// Assume user has selected an archive if the file has a .zip extension
if (uri.path.endsWith('.zip')) {
await importArchiveDatabase(
uri.toString(true),
this.databaseManager,
this.storagePath,
progress,
token
);
} else {
await this.setCurrentDatabase(progress, token, uri);
}
} catch (e) {
showAndLogErrorMessage(e.message);
// 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
}`
);
}
}
};
private handleOpenFolder = async (databaseItem: DatabaseItem): Promise<void> => {
try {
private handleRemoveDatabase = async (
progress: ProgressCallback,
token: CancellationToken,
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined
): Promise<void> => {
if (multiSelect?.length) {
await Promise.all(multiSelect.map((dbItem) =>
this.databaseManager.removeDatabaseItem(progress, token, dbItem)
));
} else {
await this.databaseManager.removeDatabaseItem(progress, token, databaseItem);
}
};
private handleRenameDatabase = async (
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined
): Promise<void> => {
this.assertSingleDatabase(multiSelect);
const newName = await window.showInputBox({
prompt: 'Choose new database name',
value: databaseItem.name,
});
if (newName) {
this.databaseManager.renameDatabaseItem(databaseItem, newName);
}
};
private handleOpenFolder = async (
databaseItem: DatabaseItem,
multiSelect: DatabaseItem[] | undefined
): Promise<void> => {
if (multiSelect?.length) {
await Promise.all(
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri))
);
} else {
await env.openExternal(databaseItem.databaseUri);
} catch (e) {
showAndLogErrorMessage(e.message);
}
}
};
/**
* Return the current database directory. If we don't already have a
* current database, ask the user for one, and return that, or
* undefined if they cancel.
*/
public async getDatabaseItem(): Promise<DatabaseItem | undefined> {
public async getDatabaseItem(
progress: ProgressCallback,
token: CancellationToken
): Promise<DatabaseItem | undefined> {
if (this.databaseManager.currentDatabaseItem === undefined) {
await this.chooseAndSetDatabase();
await this.chooseAndSetDatabase(false, progress, token);
}
return this.databaseManager.currentDatabaseItem;
}
private async setCurrentDatabase(uri: Uri): Promise<DatabaseItem | undefined> {
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);
@@ -324,13 +668,63 @@ export class DatabaseUI extends DisposableObject {
* Ask the user for a database directory. Returns the chosen database, or `undefined` if the
* operation was canceled.
*/
private async chooseAndSetDatabase(): Promise<DatabaseItem | undefined> {
const uri = await chooseDatabaseDir();
if (uri !== undefined) {
return await this.setCurrentDatabase(uri);
}
else {
private async chooseAndSetDatabase(
byFolder: boolean,
progress: ProgressCallback,
token: CancellationToken,
): Promise<DatabaseItem | undefined> {
const uri = await chooseDatabaseDir(byFolder);
if (!uri) {
return undefined;
}
if (byFolder) {
const fixedUri = await this.fixDbUri(uri);
// we are selecting a database folder
return await this.setCurrentDatabase(progress, token, fixedUri);
} else {
// we are selecting a database archive. Must unzip into a workspace-controlled area
// before importing.
return await importArchiveDatabase(
uri.toString(true),
this.databaseManager,
this.storagePath,
progress,
token
);
}
}
/**
* 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
* 3. choose the current directory
*
* @param uri a URI that is a database folder or inside it
*
* @return the actual database folder found by using the heuristics above.
*/
private async fixDbUri(uri: Uri): Promise<Uri> {
let dbPath = uri.fsPath;
if ((await fs.stat(dbPath)).isFile()) {
dbPath = path.dirname(dbPath);
}
if (isLikelyDbLanguageFolder(dbPath)) {
dbPath = path.dirname(dbPath);
}
return Uri.file(dbPath);
}
private assertSingleDatabase(
multiSelect: DatabaseItem[] = [],
message = 'Please select a single database.'
) {
if (multiSelect.length > 1) {
throw new Error(message);
}
}
}

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 { zipArchiveScheme, encodeSourceArchiveUri, decodeSourceArchiveUri } from './archive-filesystem-provider';
import { DisposableObject } from 'semmle-vscode-utils';
import { QueryServerConfig } from './config';
import {
showAndLogErrorMessage,
showAndLogWarningMessage,
showAndLogInformationMessage,
isLikelyDatabaseRoot
} from './helpers';
import {
ProgressCallback,
withProgress
} from './commandRunner';
import { zipArchiveScheme, encodeArchiveBasePath, decodeSourceArchiveUri, encodeSourceArchiveUri } from './archive-filesystem-provider';
import { DisposableObject } from './pure/disposable-object';
import { Logger, logger } from './logging';
import { registerDatabases, Dataset, deregisterDatabases } from './pure/messages';
import { QueryServerClient } from './queryserver-client';
/**
* 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 {
@@ -113,7 +125,7 @@ async function findSourceArchive(
databasePath: string, silent = false
): Promise<vscode.Uri | undefined> {
const relativePaths = ['src', 'output/src_archive']
const relativePaths = ['src', 'output/src_archive'];
for (const relativePath of relativePaths) {
const basePath = path.join(databasePath, relativePath);
@@ -121,19 +133,21 @@ async function findSourceArchive(
if (await fs.pathExists(basePath)) {
return vscode.Uri.file(basePath);
}
else if (await fs.pathExists(zipPath)) {
return vscode.Uri.file(zipPath).with({ scheme: zipArchiveScheme });
} else if (await fs.pathExists(zipPath)) {
return encodeArchiveBasePath(zipPath);
}
}
if (!silent)
showAndLogInformationMessage(`Could not find source archive for database '${databasePath}'. Assuming paths are absolute.`);
if (!silent) {
showAndLogInformationMessage(
`Could not find source archive for database '${databasePath}'. Assuming paths are absolute.`
);
}
return undefined;
}
async function resolveDatabase(
databasePath: string
): Promise<DatabaseContents | undefined> {
): Promise<DatabaseContents> {
const name = path.basename(databasePath);
@@ -155,20 +169,6 @@ async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
return await glob('*.dbscheme', { cwd: dbDirectory });
}
async function resolveRawDataset(datasetPath: string): Promise<DatabaseContents | undefined> {
if ((await getDbSchemeFiles(datasetPath)).length > 0) {
return {
kind: DatabaseKind.RawDataset,
name: path.basename(datasetPath),
datasetUri: vscode.Uri.file(datasetPath),
sourceArchiveUri: undefined
};
}
else {
return undefined;
}
}
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.`);
@@ -178,7 +178,7 @@ async function resolveDatabaseContents(uri: vscode.Uri): Promise<DatabaseContent
throw new InvalidDatabaseError(`Database '${databasePath}' does not exist.`);
}
const contents = await resolveDatabase(databasePath) || await resolveRawDataset(databasePath);
const contents = await resolveDatabase(databasePath);
if (contents === undefined) {
throw new InvalidDatabaseError(`'${databasePath}' is not a valid database.`);
@@ -205,6 +205,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;
/**
@@ -261,18 +264,44 @@ export interface DatabaseItem {
* Holds if `uri` belongs to this database's source archive.
*/
belongsToSourceArchiveExplorerUri(uri: vscode.Uri): boolean;
/**
* Gets the state of this database, to be persisted in the workspace state.
*/
getPersistedState(): PersistedDatabaseItem;
}
class DatabaseItemImpl implements DatabaseItem {
export enum DatabaseEventKind {
Add = 'Add',
Remove = 'Remove',
// Fired when databases are refreshed from persisted state
Refresh = 'Refresh',
// Fired when the current database changes
Change = 'Change',
Rename = 'Rename'
}
export interface DatabaseChangedEvent {
kind: DatabaseEventKind;
item: DatabaseItem | undefined;
}
// Exported for testing
export class DatabaseItemImpl implements DatabaseItem {
private _error: Error | undefined = undefined;
private _contents: DatabaseContents | undefined;
/** A cache of database info */
private _dbinfo: cli.DbInfo | undefined;
public constructor(public readonly databaseUri: vscode.Uri,
contents: DatabaseContents | undefined, private options: FullDatabaseOptions,
private readonly onChanged: (item: DatabaseItemImpl) => void) {
public constructor(
public readonly databaseUri: vscode.Uri,
contents: DatabaseContents | undefined,
private options: FullDatabaseOptions,
private readonly onChanged: (event: DatabaseChangedEvent) => void
) {
this._contents = contents;
}
@@ -295,8 +324,7 @@ class DatabaseItemImpl implements DatabaseItem {
public get sourceArchive(): vscode.Uri | undefined {
if (this.options.ignoreSourceArchive || (this._contents === undefined)) {
return undefined;
}
else {
} else {
return this._contents.sourceArchiveUri;
}
}
@@ -326,46 +354,52 @@ class DatabaseItemImpl implements DatabaseItem {
}
}
finally {
this.onChanged(this);
this.onChanged({
kind: DatabaseEventKind.Refresh,
item: this
});
}
}
public resolveSourceFile(file: string | undefined): vscode.Uri {
public resolveSourceFile(uriStr: string | undefined): vscode.Uri {
const sourceArchive = this.sourceArchive;
if (sourceArchive === undefined) {
if (file !== undefined) {
// Treat it as an absolute path.
return vscode.Uri.file(file);
}
else {
const uri = uriStr ? vscode.Uri.parse(uriStr, true) : undefined;
if (uri && uri.scheme !== 'file') {
throw new Error(`Invalid uri scheme in ${uriStr}. Only 'file' is allowed.`);
}
if (!sourceArchive) {
if (uri) {
return uri;
} else {
return this.databaseUri;
}
}
else {
if (file !== undefined) {
const absoluteFilePath = file.replace(':', '_');
// Strip any leading slashes from the file path, and replace `:` with `_`.
const relativeFilePath = absoluteFilePath.replace(/^\/*/, '').replace(':', '_');
if (sourceArchive.scheme == zipArchiveScheme) {
return encodeSourceArchiveUri({
pathWithinSourceArchive: absoluteFilePath,
sourceArchiveZipPath: sourceArchive.fsPath,
});
}
else {
let newPath = sourceArchive.path;
if (!newPath.endsWith('/')) {
// Ensure a trailing slash.
newPath += '/';
}
newPath += relativeFilePath;
return sourceArchive.with({ path: newPath });
if (uri) {
const relativeFilePath = decodeURI(uri.path).replace(':', '_').replace(/^\/*/, '');
if (sourceArchive.scheme === zipArchiveScheme) {
const zipRef = decodeSourceArchiveUri(sourceArchive);
const pathWithinSourceArchive = zipRef.pathWithinSourceArchive === '/'
? relativeFilePath
: zipRef.pathWithinSourceArchive + '/' + relativeFilePath;
return encodeSourceArchiveUri({
pathWithinSourceArchive,
sourceArchiveZipPath: zipRef.sourceArchiveZipPath,
});
} else {
let newPath = sourceArchive.path;
if (!newPath.endsWith('/')) {
// Ensure a trailing slash.
newPath += '/';
}
newPath += relativeFilePath;
return sourceArchive.with({ path: newPath });
}
else {
return sourceArchive;
}
} else {
return sourceArchive;
}
}
@@ -383,10 +417,7 @@ class DatabaseItemImpl implements DatabaseItem {
* Holds if the database item refers to an exported snapshot
*/
public async hasMetadataFile(): Promise<boolean> {
return (await Promise.all([
fs.pathExists(path.join(this.databaseUri.fsPath, '.dbinfo')),
fs.pathExists(path.join(this.databaseUri.fsPath, 'codeql-database.yml'))
])).some(x => x);
return await isLikelyDatabaseRoot(this.databaseUri.fsPath);
}
/**
@@ -416,6 +447,10 @@ 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.
*/
@@ -423,10 +458,7 @@ class DatabaseItemImpl implements DatabaseItem {
const sourceArchive = this.sourceArchive;
if (sourceArchive === undefined || !sourceArchive.fsPath.endsWith('.zip'))
return undefined;
return encodeSourceArchiveUri({
pathWithinSourceArchive: '/',
sourceArchiveZipPath: sourceArchive.fsPath,
});
return encodeArchiveBasePath(sourceArchive.fsPath);
}
/**
@@ -464,47 +496,71 @@ function eventFired<T>(event: vscode.Event<T>, timeoutMs = 1000): Promise<T | un
}
export class DatabaseManager extends DisposableObject {
private readonly _onDidChangeDatabaseItem = this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
private readonly _onDidChangeDatabaseItem = this.push(new vscode.EventEmitter<DatabaseChangedEvent>());
readonly onDidChangeDatabaseItem = this._onDidChangeDatabaseItem.event;
private readonly _onDidChangeCurrentDatabaseItem = this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
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,
public logger: Logger) {
constructor(
private readonly ctx: ExtensionContext,
private readonly qs: QueryServerClient,
private readonly cli: cli.CodeQLCliServer,
public logger: Logger
) {
super();
this.loadPersistedState(); // Let this run async.
qs.onDidStartQueryServer(this.reregisterDatabases.bind(this));
// Let this run async.
this.loadPersistedState();
}
public async openDatabase(
uri: vscode.Uri, options?: DatabaseOptions
progress: ProgressCallback,
token: vscode.CancellationToken,
uri: vscode.Uri,
): 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,
// displayName is only set if a user explicitly renames a database
displayName: undefined,
dateAdded: Date.now(),
language: await this.getPrimaryLanguage(uri.fsPath)
};
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (item) => {
this._onDidChangeDatabaseItem.fire(item);
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 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'
});
}));
}
private async addDatabaseSourceArchiveFolder(item: DatabaseItem) {
// The folder may already be in workspace state from a previous
// session. If not, add it.
@@ -545,12 +601,15 @@ export class DatabaseManager extends DisposableObject {
}
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;
@@ -561,43 +620,69 @@ 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), undefined, fullOptions,
(item) => {
this._onDidChangeDatabaseItem.fire(item)
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);
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);
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) {
this.setCurrentDatabaseItem(databaseItem, true);
}
}
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}`);
}
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}`);
}
});
}
public get databaseItems(): readonly DatabaseItem[] {
@@ -608,8 +693,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.
@@ -617,7 +704,11 @@ export class DatabaseManager extends DisposableObject {
if (this._currentDatabaseItem !== item) {
this._currentDatabaseItem = item;
this.updatePersistedCurrentDatabaseItem();
this._onDidChangeCurrentDatabaseItem.fire(item);
this._onDidChangeCurrentDatabaseItem.fire({
item,
kind: DatabaseEventKind.Change
});
}
}
@@ -640,21 +731,45 @@ 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();
this._onDidChangeDatabaseItem.fire(undefined);
// 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,
kind: DatabaseEventKind.Add
});
}
public async renameDatabaseItem(item: DatabaseItem, newName: string) {
item.name = newName;
this.updatePersistedDatabaseList();
this._onDidChangeDatabaseItem.fire(item);
this._onDidChangeDatabaseItem.fire({
// pass undefined so that the entire tree is rebuilt in order to re-sort
item: undefined,
kind: DatabaseEventKind.Rename
});
}
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);
@@ -662,21 +777,58 @@ export class DatabaseManager extends DisposableObject {
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) {
const folderIndex = (vscode.workspace.workspaceFolders || []).findIndex(
folder => item.belongsToSourceArchiveExplorerUri(folder.uri)
);
if (folderIndex >= 0) {
logger.log(`Removing workspace folder at index ${folderIndex}`);
vscode.workspace.updateWorkspaceFolders(folderIndex, 1);
}
// 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}`));
logger.log('Deleting database from filesystem.');
fs.remove(item.databaseUri.fsPath).then(
() => logger.log(`Deleted '${item.databaseUri.fsPath}'`),
e => logger.log(`Failed to delete '${item.databaseUri.fsPath}'. Reason: ${e.message}`));
}
this._onDidChangeDatabaseItem.fire(undefined);
// Remove this database item from the allow-list
await this.deregisterDatabase(progress, token, item);
// note that we use undefined as the item in order to reset the entire tree
this._onDidChangeDatabaseItem.fire({
item: undefined,
kind: DatabaseEventKind.Remove
});
}
private async deregisterDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
if (dbItem.contents && (await this.cli.cliConstraints.supportsDatabaseRegistration())) {
const databases: Dataset[] = [{
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default'
}];
await this.qs.sendRequest(deregisterDatabases, { databases }, token, progress);
}
}
private async registerDatabase(
progress: ProgressCallback,
token: vscode.CancellationToken,
dbItem: DatabaseItem,
) {
if (dbItem.contents && (await this.cli.cliConstraints.supportsDatabaseRegistration())) {
const databases: Dataset[] = [{
dbDir: dbItem.contents.datasetUri.fsPath,
workingSet: 'default'
}];
await this.qs.sendRequest(registerDatabases, { databases }, token, progress);
}
}
private updatePersistedCurrentDatabaseItem(): void {
@@ -690,7 +842,24 @@ export class DatabaseManager extends DisposableObject {
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] || '';
}
}

View File

@@ -1,204 +0,0 @@
import * as fs from 'fs-extra';
import * as yaml from 'js-yaml';
import * as tmp from 'tmp';
import * as vscode from "vscode";
import { decodeSourceArchiveUri, zipArchiveScheme } from "./archive-filesystem-provider";
import { ColumnKindCode, EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from "./bqrs-cli-types";
import { CodeQLCliServer } from "./cli";
import { DatabaseItem, DatabaseManager } from "./databases";
import * as helpers from './helpers';
import { CachedOperation } from './helpers';
import * as messages from "./messages";
import { QueryServerClient } from "./queryserver-client";
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from "./run-queries";
/**
* Run templated CodeQL queries to find definitions and references in
* source-language files. We may eventually want to find a way to
* generalize this to other custom queries, e.g. showing dataflow to
* or from a selected identifier.
*/
const TEMPLATE_NAME = "selectedSourceFile";
const SELECT_QUERY_NAME = "#select";
enum KeyType {
DefinitionQuery = 'DefinitionQuery',
ReferenceQuery = 'ReferenceQuery',
}
function tagOfKeyType(keyType: KeyType): string {
switch (keyType) {
case KeyType.DefinitionQuery: return "ide-contextual-queries/local-definitions";
case KeyType.ReferenceQuery: return "ide-contextual-queries/local-references";
}
}
async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
const suiteFile = tmp.fileSync({ postfix: '.qls' }).name;
const suiteYaml = { qlpack, include: { kind: 'definitions', 'tags contain': tagOfKeyType(keyType) } };
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
if (queries.length === 0) {
throw new Error("Couldn't find any queries for qlpack");
}
return queries;
}
async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string | undefined> {
if (db.contents === undefined)
return undefined;
const datasetPath = db.contents.datasetUri.fsPath;
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
return qlpack;
}
interface FullLocationLink extends vscode.LocationLink {
originUri: vscode.Uri;
}
export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider {
private cache: CachedOperation<vscode.LocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<vscode.LocationLink[]>(this.getDefinitions.bind(this));
}
async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, KeyType.DefinitionQuery, (src, _dest) => src === uriString);
}
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.LocationLink[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: vscode.LocationLink[] = [];
for (const link of fileLinks) {
if (link.originSelectionRange!.contains(position)) {
locLinks.push(link);
}
}
return locLinks;
}
}
export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider {
private cache: CachedOperation<FullLocationLink[]>;
constructor(
private cli: CodeQLCliServer,
private qs: QueryServerClient,
private dbm: DatabaseManager,
) {
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
}
async getReferences(uriString: string): Promise<FullLocationLink[]> {
return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, KeyType.ReferenceQuery, (_src, dest) => dest === uriString);
}
async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise<vscode.Location[]> {
const fileLinks = await this.cache.get(document.uri.toString());
const locLinks: vscode.Location[] = [];
for (const link of fileLinks) {
if (link.targetRange!.contains(position)) {
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
}
}
return locLinks;
}
}
interface FileRange {
file: vscode.Uri;
range: vscode.Range;
}
async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServer, db: DatabaseItem, filter: (srcFile: string, destFile: string) => boolean): Promise<FullLocationLink[]> {
const localLinks: FullLocationLink[] = [];
const bqrsPath = results.query.resultsPaths.resultsPath;
const info = await cli.bqrsInfo(bqrsPath);
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
if (selectInfo && selectInfo.columns.length == 3
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
&& selectInfo.columns[2].kind == ColumnKindCode.STRING) {
// TODO: Page this
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
for (const tuple of allTuples.tuples) {
const src = tuple[0] as EntityValue;
const dest = tuple[1] as EntityValue;
const srcFile = src.url && fileRangeFromURI(src.url, db);
const destFile = dest.url && fileRangeFromURI(dest.url, db);
if (srcFile && destFile && filter(srcFile.file.toString(), destFile.file.toString())) {
localLinks.push({ targetRange: destFile.range, targetUri: destFile.file, originSelectionRange: srcFile.range, originUri: srcFile.file });
}
}
}
return localLinks;
}
async function getLinksForUriString(
cli: CodeQLCliServer,
qs: QueryServerClient,
dbm: DatabaseManager,
uriString: string,
keyType: KeyType,
filter: (src: string, dest: string) => boolean
) {
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString));
const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme });
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
if (db) {
const qlpack = await qlpackOfDatabase(cli, db);
if (qlpack === undefined) {
throw new Error("Can't infer qlpack from database source archive");
}
const links: FullLocationLink[] = []
for (const query of await resolveQueries(cli, qlpack, keyType)) {
const templates: messages.TemplateDefinitions = {
[TEMPLATE_NAME]: {
values: {
tuples: [[{
stringValue: uri.pathWithinSourceArchive
}]]
}
}
};
const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates);
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
links.push(...await getLinksFromResults(results, cli, db, filter));
}
}
return links;
} else {
return [];
}
}
function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): FileRange | undefined {
if (typeof uri === "string") {
return undefined;
} else if ('startOffset' in uri) {
return undefined;
} else {
const loc = uri as LineColumnLocation;
const range = new vscode.Range(Math.max(0, loc.startLine - 1),
Math.max(0, loc.startColumn - 1),
Math.max(0, loc.endLine - 1),
Math.max(0, loc.endColumn));
try {
const parsed = vscode.Uri.parse(uri.uri, true);
if (parsed.scheme === "file") {
return { file: db.resolveSourceFile(parsed.fsPath), range };
}
return undefined;
} catch (e) {
return undefined;
}
}
}

View File

@@ -1,4 +1,5 @@
import { DisposableObject } from 'semmle-vscode-utils';
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
@@ -9,7 +10,7 @@ export abstract class Discovery<T> extends DisposableObject {
private retry = false;
private discoveryInProgress = false;
constructor() {
constructor(private readonly name: string) {
super();
}
@@ -59,6 +60,11 @@ export abstract class Discovery<T> extends DisposableObject {
this.update(results);
}
});
discoveryPromise.catch(err => {
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

View File

@@ -1,15 +1,21 @@
import * as fetch from "node-fetch";
import * as fs from "fs-extra";
import * as os from "os";
import * as path from "path";
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 { logger } from "./logging";
import * as helpers from "./helpers";
import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-version";
import * as fetch from 'node-fetch';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as semver from 'semver';
import * as unzipper from 'unzipper';
import * as url from 'url';
import { ExtensionContext, Event } from 'vscode';
import { DistributionConfig } from './config';
import {
InvocationRateLimiter,
InvocationRateLimiterResultKind,
showAndLogErrorMessage,
showAndLogWarningMessage
} from './helpers';
import { logger } from './logging';
import { getCodeQlCliVersion } from './cli-version';
import { ProgressCallback, reportStreamProgress } from './commandRunner';
/**
* distribution.ts
@@ -24,7 +30,7 @@ import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-versi
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
const DEFAULT_DISTRIBUTION_OWNER_NAME = "github";
const DEFAULT_DISTRIBUTION_OWNER_NAME = 'github';
/**
* Default value for the repository name of the extension-managed distribution on GitHub.
@@ -32,64 +38,99 @@ const DEFAULT_DISTRIBUTION_OWNER_NAME = "github";
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries";
const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = 'codeql-cli-binaries';
/**
* Version constraint for the CLI.
* Range of versions of the CLI that are compatible with the extension.
*
* This applies to both extension-managed and CLI distributions.
*/
export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = {
description: "2.*.*",
isVersionCompatible: (v: Version) => {
return v.majorVersion === 2 && v.minorVersion >= 0
}
}
export const DEFAULT_DISTRIBUTION_VERSION_RANGE: semver.Range = new semver.Range('2.x');
export interface DistributionProvider {
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>;
onDidChangeDistribution?: Event<void>;
getDistribution(): Promise<FindDistributionResult>;
}
export class DistributionManager implements DistributionProvider {
constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionConstraint: VersionConstraint) {
this._config = config;
this._extensionSpecificDistributionManager = new ExtensionSpecificDistributionManager(extensionContext, config, versionConstraint);
this._onDidChangeDistribution = config.onDidChangeDistributionConfiguration;
this._updateCheckRateLimiter = new InvocationRateLimiter(
/**
* 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.extensionSpecificDistributionManager =
new ExtensionSpecificDistributionManager(config, versionRange, extensionContext);
this.updateCheckRateLimiter = new InvocationRateLimiter(
extensionContext,
"extensionSpecificDistributionUpdateCheck",
() => this._extensionSpecificDistributionManager.checkForUpdatesToDistribution()
'extensionSpecificDistributionUpdateCheck',
() => this.extensionSpecificDistributionManager.checkForUpdatesToDistribution()
);
this._versionConstraint = versionConstraint;
}
/**
* Look up a CodeQL launcher binary.
*/
public async getDistribution(): Promise<FindDistributionResult> {
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
if (codeQlPath === undefined) {
const distribution = await this.getDistributionWithoutVersionCheck();
if (distribution === undefined) {
return {
kind: FindDistributionResultKind.NoDistribution,
};
}
const version = await getCodeQlCliVersion(codeQlPath, logger);
if (version !== undefined && !this._versionConstraint.isVersionCompatible(version)) {
const version = await getCodeQlCliVersion(distribution.codeQlPath, logger);
if (version === undefined) {
return {
codeQlPath,
distribution,
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
};
}
/**
* Specifies whether prerelease versions of the CodeQL CLI should be accepted.
*
* Suppose a user sets the includePrerelease config option, obtains a prerelease, then decides
* they no longer want a prerelease, so unsets the includePrerelease config option.
* Unsetting the includePrerelease config option should trigger an update check, and this
* update check should present them an update that returns them back to a non-prerelease
* version.
*
* Therefore, we adopt the following:
*
* - If the user is managing their own CLI, they can use a prerelease without specifying the
* includePrerelease option.
* - 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;
if (!semver.satisfies(version, this.versionRange, { includePrerelease })) {
return {
distribution,
kind: FindDistributionResultKind.IncompatibleDistribution,
version,
};
}
if (version === undefined) {
return {
codeQlPath,
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
}
}
return {
codeQlPath,
distribution,
kind: FindDistributionResultKind.CompatibleDistribution,
version
};
@@ -100,43 +141,57 @@ export class DistributionManager implements DistributionProvider {
return result.kind !== FindDistributionResultKind.NoDistribution;
}
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
const distribution = await this.getDistributionWithoutVersionCheck();
return distribution?.codeQlPath;
}
/**
* Returns the path to a possibly-compatible CodeQL launcher binary, or undefined if a binary not be found.
*/
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
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}" ` +
"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.");
if (this.config.customCodeQlPath) {
if (!await fs.pathExists(this.config.customCodeQlPath)) {
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;
}
// 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 this._config.customCodeQlPath;
return {
codeQlPath: this.config.customCodeQlPath,
kind: DistributionKind.CustomPathConfig
};
}
const extensionSpecificCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
const extensionSpecificCodeQlPath = await this.extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
if (extensionSpecificCodeQlPath !== undefined) {
return extensionSpecificCodeQlPath;
return {
codeQlPath: extensionSpecificCodeQlPath,
kind: DistributionKind.ExtensionManaged
};
}
if (process.env.PATH) {
for (const searchDirectory of process.env.PATH.split(path.delimiter)) {
const expectedLauncherPath = await getExecutableFromDirectory(searchDirectory);
if (expectedLauncherPath) {
return expectedLauncherPath;
return {
codeQlPath: expectedLauncherPath,
kind: DistributionKind.PathEnvironmentVariable
};
}
}
logger.log("INFO: Could not find CodeQL on path.");
logger.log('INFO: Could not find CodeQL on path.');
}
return undefined;
@@ -150,13 +205,13 @@ export class DistributionManager implements DistributionProvider {
*/
public async checkForUpdatesToExtensionManagedDistribution(
minSecondsSinceLastUpdateCheck: number): Promise<DistributionUpdateCheckResult> {
const codeQlPath = await this.getCodeQlPathWithoutVersionCheck();
const extensionManagedCodeQlPath = await this._extensionSpecificDistributionManager.getCodeQlPathWithoutVersionCheck();
if (codeQlPath !== undefined && codeQlPath !== extensionManagedCodeQlPath) {
const distribution = await this.getDistributionWithoutVersionCheck();
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;
@@ -170,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 {
@@ -185,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 _versionConstraint: VersionConstraint;
}
class ExtensionSpecificDistributionManager {
constructor(extensionContext: ExtensionContext, config: DistributionConfig, versionConstraint: VersionConstraint) {
this._extensionContext = extensionContext;
this._config = config;
this._versionConstraint = versionConstraint;
constructor(
private readonly config: DistributionConfig,
private readonly versionRange: semver.Range,
private readonly extensionContext: ExtensionContext
) {
/**/
}
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
@@ -219,7 +276,7 @@ class ExtensionSpecificDistributionManager {
try {
await this.removeDistribution();
} catch (e) {
logger.log("WARNING: Tried to remove corrupted CodeQL CLI at " +
logger.log('WARNING: Tried to remove corrupted CodeQL CLI at ' +
`${this.getDistributionStoragePath()} but encountered an error: ${e}.`);
}
}
@@ -253,14 +310,14 @@ 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);
}
private async downloadDistribution(release: Release,
progressCallback?: helpers.ProgressCallback): Promise<void> {
progressCallback?: ProgressCallback): Promise<void> {
try {
await this.removeDistribution();
} catch (e) {
@@ -268,40 +325,32 @@ class ExtensionSpecificDistributionManager {
`but encountered an error: ${e}.`);
}
const assetStream = await this.createReleasesApiConsumer().streamBinaryContentOfAsset(release.assets[0]);
const tmpDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "vscode-codeql"));
// Filter assets to the unique one that we require.
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 ' +
assets.map(asset => asset.name).join(', '));
}
const assetStream = await this.createReleasesApiConsumer().streamBinaryContentOfAsset(assets[0]);
const tmpDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'vscode-codeql'));
try {
const archivePath = path.join(tmpDirectory, "distributionDownload.zip");
const archivePath = path.join(tmpDirectory, 'distributionDownload.zip');
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 contentLength = assetStream.headers.get('content-length');
const totalNumBytes = contentLength ? parseInt(contentLength, 10) : undefined;
reportStreamProgress(assetStream.body, 'Downloading CodeQL CLI…', totalNumBytes, progressCallback);
await new Promise((resolve, reject) =>
assetStream.body.pipe(archiveFile)
.on("finish", resolve)
.on("error", reject)
.on('finish', resolve)
.on('error', reject)
);
await this.bumpDistributionFolderIndex();
@@ -326,31 +375,45 @@ class ExtensionSpecificDistributionManager {
}
private async getLatestRelease(): Promise<Release> {
const release = await this.createReleasesApiConsumer().getLatestRelease(this._versionConstraint, this._config.includePrerelease);
if (release.assets.length !== 1) {
throw new Error("Release had an unexpected number of assets");
}
return release;
const requiredAssetName = DistributionManager.getRequiredAssetName();
logger.log(`Searching for latest release including ${requiredAssetName}.`);
return this.createReleasesApiConsumer().getLatestRelease(
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}`);
return false;
}
if (matchingAssets.length > 1) {
logger.log(`WARNING: Ignoring a release with more than one asset named ${requiredAssetName}`);
return false;
}
return true;
}
);
}
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(
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0) || "";
return path.join(this._extensionContext.globalStoragePath,
const distributionFolderIndex = this.extensionContext.globalState.get(
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey, 0) || '';
return path.join(this.extensionContext.globalStoragePath,
ExtensionSpecificDistributionManager._currentDistributionFolderBaseName + distributionFolderIndex);
}
@@ -360,37 +423,33 @@ 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 _versionConstraint: VersionConstraint;
private static readonly _currentDistributionFolderBaseName = "distribution";
private static readonly _currentDistributionFolderIndexStateKey = "distributionFolderIndex";
private static readonly _installedReleaseStateKey = "distributionRelease";
private static readonly _codeQlExtractedFolderName = "codeql";
private static readonly _currentDistributionFolderBaseName = 'distribution';
private static readonly _currentDistributionFolderIndexStateKey = 'distributionFolderIndex';
private static readonly _installedReleaseStateKey = 'distributionRelease';
private static readonly _codeQlExtractedFolderName = 'codeql';
}
export class ReleasesApiConsumer {
constructor(ownerName: string, repoName: string, personalAccessToken?: string) {
// Specify version of the GitHub API
this._defaultHeaders["accept"] = "application/vnd.github.v3+json";
this._defaultHeaders['accept'] = 'application/vnd.github.v3+json';
if (personalAccessToken) {
this._defaultHeaders["authorization"] = `token ${personalAccessToken}`;
this._defaultHeaders['authorization'] = `token ${personalAccessToken}`;
}
this._ownerName = ownerName;
this._repoName = repoName;
}
public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease = false): Promise<Release> {
public async getLatestRelease(versionRange: semver.Range, includePrerelease = false, additionalCompatibilityCheck?: (release: GithubRelease) => boolean): Promise<Release> {
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases`;
const allReleases: GithubRelease[] = await (await this.makeApiCall(apiPath)).json();
const compatibleReleases = allReleases.filter(release => {
@@ -398,24 +457,24 @@ export class ReleasesApiConsumer {
return false;
}
const version = tryParseVersionString(release.tag_name);
if (version === undefined || !versionConstraint.isVersionCompatible(version)) {
const version = semver.parse(release.tag_name);
if (version === null || !semver.satisfies(version, versionRange, { includePrerelease })) {
return false;
}
return true;
return !additionalCompatibilityCheck || additionalCompatibilityCheck(release);
});
// tryParseVersionString must succeed due to the previous filtering step
// Tag names must all be parsable to semvers due to the previous filtering step.
const latestRelease = compatibleReleases.sort((a, b) => {
const versionComparison = versionCompare(tryParseVersionString(b.tag_name)!, tryParseVersionString(a.tag_name)!);
if (versionComparison === 0) {
return b.created_at.localeCompare(a.created_at);
const versionComparison = semver.compare(semver.parse(b.tag_name)!, semver.parse(a.tag_name)!);
if (versionComparison !== 0) {
return versionComparison;
}
return versionComparison;
return b.created_at.localeCompare(a.created_at, 'en-US');
})[0];
if (latestRelease === undefined) {
throw new Error("No compatible CodeQL CLI releases were found. " +
"Please check that the CodeQL extension is up to date.");
throw new Error('No compatible CodeQL CLI releases were found. ' +
'Please check that the CodeQL extension is up to date.');
}
const assets: ReleaseAsset[] = latestRelease.assets.map(asset => {
return {
@@ -437,7 +496,7 @@ export class ReleasesApiConsumer {
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases/assets/${asset.id}`;
return await this.makeApiCall(apiPath, {
"accept": "application/octet-stream"
'accept': 'application/octet-stream'
});
}
@@ -447,7 +506,7 @@ export class ReleasesApiConsumer {
if (!response.ok) {
// Check for rate limiting
const rateLimitResetValue = response.headers.get("X-RateLimit-Reset");
const rateLimitResetValue = response.headers.get('X-RateLimit-Reset');
if (response.status === 403 && rateLimitResetValue) {
const secondsToMillisecondsFactor = 1000;
const rateLimitResetDate = new Date(parseInt(rateLimitResetValue, 10) * secondsToMillisecondsFactor);
@@ -464,23 +523,23 @@ export class ReleasesApiConsumer {
redirectCount = 0): Promise<fetch.Response> {
const response = await fetch.default(requestUrl, {
headers,
redirect: "manual"
redirect: 'manual'
});
const redirectUrl = response.headers.get("location");
const redirectUrl = response.headers.get('location');
if (isRedirectStatusCode(response.status) && redirectUrl && redirectCount < ReleasesApiConsumer._maxRedirects) {
const parsedRedirectUrl = url.parse(redirectUrl);
if (parsedRedirectUrl.protocol != "https:") {
throw new Error("Encountered a non-https redirect, rejecting");
if (parsedRedirectUrl.protocol != 'https:') {
throw new Error('Encountered a non-https redirect, rejecting');
}
if (parsedRedirectUrl.host != "api.github.com") {
if (parsedRedirectUrl.host != 'api.github.com') {
// Remove authorization header if we are redirected outside of the GitHub API.
//
// This is necessary to stream release assets since AWS fails if more than one auth
// mechanism is provided.
delete headers["authorization"];
delete headers['authorization'];
}
return await this.makeRawRequest(redirectUrl, headers, redirectCount + 1)
return await this.makeRawRequest(redirectUrl, headers, redirectCount + 1);
}
return response;
@@ -490,7 +549,7 @@ export class ReleasesApiConsumer {
private readonly _ownerName: string;
private readonly _repoName: string;
private static readonly _apiBase = "https://api.github.com";
private static readonly _apiBase = 'https://api.github.com';
private static readonly _maxRedirects = 20;
}
@@ -511,35 +570,12 @@ export async function extractZipArchive(archivePath: string, outPath: string): P
}));
}
/**
* Comparison of semantic versions.
*
* Returns a positive number if a is greater than b.
* Returns 0 if a equals b.
* Returns a negative number if a is less than b.
*/
export function versionCompare(a: Version, b: Version): number {
if (a.majorVersion !== b.majorVersion) {
return a.majorVersion - b.majorVersion;
}
if (a.minorVersion !== b.minorVersion) {
return a.minorVersion - b.minorVersion;
}
if (a.patchVersion !== b.patchVersion) {
return a.patchVersion - b.patchVersion;
}
if (a.prereleaseVersion !== undefined && b.prereleaseVersion !== undefined) {
return a.prereleaseVersion.localeCompare(b.prereleaseVersion);
}
return 0;
}
function codeQlLauncherName(): string {
return (os.platform() === "win32") ? "codeql.exe" : "codeql";
export function codeQlLauncherName(): string {
return (os.platform() === 'win32') ? 'codeql.exe' : 'codeql';
}
function deprecatedCodeQlLauncherName(): string | undefined {
return (os.platform() === "win32") ? "codeql.cmd" : undefined;
return (os.platform() === 'win32') ? 'codeql.cmd' : undefined;
}
function isRedirectStatusCode(statusCode: number): boolean {
@@ -550,6 +586,17 @@ function isRedirectStatusCode(statusCode: number): boolean {
* Types and helper functions relating to those types.
*/
export enum DistributionKind {
CustomPathConfig,
ExtensionManaged,
PathEnvironmentVariable
}
export interface Distribution {
codeQlPath: string;
kind: DistributionKind;
}
export enum FindDistributionResultKind {
CompatibleDistribution,
UnknownCompatibilityDistribution,
@@ -563,21 +610,27 @@ export type FindDistributionResult =
| IncompatibleDistributionResult
| NoDistributionResult;
interface CompatibleDistributionResult {
codeQlPath: string;
kind: FindDistributionResultKind.CompatibleDistribution;
version: Version;
/**
* A result representing a distribution of the CodeQL CLI that may or may not be compatible with
* the extension.
*/
interface DistributionResult {
distribution: Distribution;
kind: FindDistributionResultKind;
}
interface UnknownCompatibilityDistributionResult {
codeQlPath: string;
interface CompatibleDistributionResult extends DistributionResult {
kind: FindDistributionResultKind.CompatibleDistribution;
version: semver.SemVer;
}
interface UnknownCompatibilityDistributionResult extends DistributionResult {
kind: FindDistributionResultKind.UnknownCompatibilityDistribution;
}
interface IncompatibleDistributionResult {
codeQlPath: string;
interface IncompatibleDistributionResult extends DistributionResult {
kind: FindDistributionResultKind.IncompatibleDistribution;
version: Version;
version: semver.SemVer;
}
interface NoDistributionResult {
@@ -655,13 +708,15 @@ export async function getExecutableFromDirectory(directory: string, warnWhenNotF
}
if (warnWhenNotFound) {
logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` +
"Will try PATH.");
'Will try PATH.');
}
return undefined;
}
function warnDeprecatedLauncher() {
helpers.showAndLogWarningMessage(
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.`
);
@@ -717,7 +772,7 @@ export interface GithubRelease {
assets: GithubReleaseAsset[];
/**
* The creation date of the release on GitHub.
* The creation date of the release on GitHub, in ISO 8601 format.
*/
created_at: string;
@@ -762,11 +817,6 @@ export interface GithubReleaseAsset {
size: number;
}
interface VersionConstraint {
description: string;
isVersionCompatible(version: Version): boolean;
}
export class GithubApiError extends Error {
constructor(public status: number, public body: string) {
super(`API call failed with status code ${status}, body: ${body}`);

View File

@@ -1,26 +1,74 @@
import { commands, Disposable, ExtensionContext, extensions, languages, ProgressLocation, ProgressOptions, Uri, window as Window } from 'vscode';
import {
CancellationToken,
commands,
Disposable,
ExtensionContext,
extensions,
languages,
ProgressLocation,
ProgressOptions,
Uri,
window as Window,
env,
window
} from 'vscode';
import { LanguageClient } from 'vscode-languageclient';
import * as os from 'os';
import * as path from 'path';
import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
import { AstViewer } from './astViewer';
import * as archiveFilesystemProvider from './archive-filesystem-provider';
import { CodeQLCliServer } from './cli';
import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener, EXPERIMENTAL_FEATURES_SETTING } from './config';
import { CodeQLCliServer, CliVersionConstraint } from './cli';
import {
CliConfigListener,
DistributionConfigListener,
MAX_QUERIES,
QueryHistoryConfigListener,
QueryServerConfigListener
} from './config';
import * as languageSupport from './languageSupport';
import { DatabaseManager } from './databases';
import { DatabaseUI } from './databases-ui';
import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions';
import { DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution';
import {
TemplateQueryDefinitionProvider,
TemplateQueryReferenceProvider,
TemplatePrintAstProvider
} from './contextual/templateProvider';
import {
DEFAULT_DISTRIBUTION_VERSION_RANGE,
DistributionKind,
DistributionManager,
DistributionUpdateCheckResultKind,
FindDistributionResult,
FindDistributionResultKind,
GithubApiError,
GithubRateLimitedError
} from './distribution';
import * as helpers from './helpers';
import { assertNever } from './helpers-pure';
import { assertNever } from './pure/helpers-pure';
import { spawnIdeServer } from './ide-server';
import { InterfaceManager, WebviewReveal } from './interface';
import { InterfaceManager } from './interface';
import { WebviewReveal } from './interface-utils';
import { ideServerLogger, logger, queryServerLogger } from './logging';
import { QueryHistoryManager } from './query-history';
import { CompletedQuery } from './query-results';
import * as qsClient from './queryserver-client';
import { displayQuickQuery } from './quick-query';
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries';
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal } from './run-queries';
import { QLTestAdapterFactory } from './test-adapter';
import { TestUIService } from './test-ui';
import promptFetchDatabase from './databaseFetcher';
import { CompareInterfaceManager } from './compare/compare-interface';
import { gatherQlFiles } from './pure/files';
import { initializeTelemetry } from './telemetry';
import {
commandRunner,
commandRunnerWithProgress,
ProgressCallback,
withProgress,
ProgressUpdate
} from './commandRunner';
import { CodeQlStatusBarHandler } from './status-bar';
/**
* extension.ts
@@ -49,18 +97,19 @@ const errorStubs: Disposable[] = [];
*/
let isInstallingOrUpdatingDistribution = false;
const extensionId = 'GitHub.vscode-codeql';
const extension = extensions.getExtension(extensionId);
/**
* If the user tries to execute vscode commands after extension activation is failed, give
* a sensible error message.
*
* @param excludedCommands List of commands for which we should not register error stubs.
*/
function registerErrorStubs(excludedCommands: string[], stubGenerator: (command: string) => () => void): void {
function registerErrorStubs(excludedCommands: string[], stubGenerator: (command: string) => () => Promise<void>): void {
// Remove existing stubs
errorStubs.forEach(stub => stub.dispose());
const extensionId = 'GitHub.vscode-codeql'; // TODO: Is there a better way of obtaining this?
const extension = extensions.getExtension(extensionId);
if (extension === undefined) {
throw new Error(`Can't find extension ${extensionId}`);
}
@@ -70,25 +119,55 @@ function registerErrorStubs(excludedCommands: string[], stubGenerator: (command:
stubbedCommands.forEach(command => {
if (excludedCommands.indexOf(command) === -1) {
errorStubs.push(commands.registerCommand(command, stubGenerator(command)));
errorStubs.push(commandRunner(command, stubGenerator(command)));
}
});
}
export async function activate(ctx: ExtensionContext): Promise<void> {
logger.log('Starting CodeQL extension');
/**
* The publicly available interface for this extension. This is to
* be used in our tests.
*/
export interface CodeQLExtensionInterface {
readonly ctx: ExtensionContext;
readonly cliServer: CodeQLCliServer;
readonly qs: qsClient.QueryServerClient;
readonly distributionManager: DistributionManager;
readonly databaseManager: DatabaseManager;
readonly databaseUI: DatabaseUI;
readonly dispose: () => void;
}
initializeLogging(ctx);
/**
* Returns the CodeQLExtensionInterface, or an empty object if the interface is not
* available after activation is complete. This will happen if there is no cli
* installed when the extension starts. Downloading and installing the cli
* will happen at a later time.
*
* @param ctx The extension context
*
* @returns CodeQLExtensionInterface
*/
export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionInterface | {}> {
logger.log(`Starting ${extensionId} extension`);
if (extension === undefined) {
throw new Error(`Can't find extension ${extensionId}`);
}
const distributionConfigListener = new DistributionConfigListener();
initializeLogging(ctx);
await initializeTelemetry(extension, ctx);
languageSupport.install();
ctx.subscriptions.push(distributionConfigListener);
const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT);
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
const distributionManager = new DistributionManager(distributionConfigListener, codeQlVersionRange, ctx);
const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
const shouldUpdateOnNextActivationKey = 'shouldUpdateOnNextActivation';
registerErrorStubs([checkForUpdatesCommand], command => () => {
registerErrorStubs([checkForUpdatesCommand], command => (async () => {
helpers.showAndLogErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`);
});
}));
interface DistributionUpdateConfig {
isUserInitiated: boolean;
@@ -107,7 +186,7 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
switch (result.kind) {
case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult:
logger.log("Didn't perform CodeQL CLI update check since a check was already performed within the previous " +
logger.log('Didn\'t perform CodeQL CLI update check since a check was already performed within the previous ' +
`${minSecondsSinceLastUpdateCheck} seconds.`);
break;
case DistributionUpdateCheckResultKind.AlreadyUpToDate:
@@ -126,11 +205,11 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
}
} else {
const progressOptions: ProgressOptions = {
location: ProgressLocation.Notification,
title: progressTitle,
cancellable: false,
location: ProgressLocation.Notification,
};
await helpers.withProgress(progressOptions, progress =>
await withProgress(progressOptions, progress =>
distributionManager.installExtensionManagedDistributionRelease(result.updatedRelease, progress));
await ctx.globalState.update(shouldUpdateOnNextActivationKey, false);
@@ -144,16 +223,16 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
async function installOrUpdateDistribution(config: DistributionUpdateConfig): Promise<void> {
if (isInstallingOrUpdatingDistribution) {
throw new Error("Already installing or updating CodeQL CLI");
throw new Error('Already installing or updating CodeQL CLI');
}
isInstallingOrUpdatingDistribution = true;
const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined;
const willUpdateCodeQl = ctx.globalState.get(shouldUpdateOnNextActivationKey);
const messageText = willUpdateCodeQl
? "Updating CodeQL CLI"
? 'Updating CodeQL CLI'
: codeQlInstalled
? "Checking for updates to CodeQL CLI"
: "Installing CodeQL CLI";
? 'Checking for updates to CodeQL CLI'
: 'Installing CodeQL CLI';
try {
await installOrUpdateDistributionWithProgressTitle(messageText, config);
@@ -162,12 +241,12 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
// or updating the distribution.
const alertFunction = (codeQlInstalled && !config.isUserInitiated) ?
helpers.showAndLogWarningMessage : helpers.showAndLogErrorMessage;
const taskDescription = (willUpdateCodeQl ? "update" :
codeQlInstalled ? "check for updates to" : "install") + " CodeQL CLI";
const taskDescription = (willUpdateCodeQl ? 'update' :
codeQlInstalled ? 'check for updates to' : 'install') + ' CodeQL CLI';
if (e instanceof GithubRateLimitedError) {
alertFunction(`Rate limited while trying to ${taskDescription}. Please try again after ` +
`your rate limit window resets at ${e.rateLimitResetDate.toLocaleString()}.`);
`your rate limit window resets at ${e.rateLimitResetDate.toLocaleString(env.language)}.`);
} else if (e instanceof GithubApiError) {
alertFunction(`Encountered GitHub API error while trying to ${taskDescription}. ` + e);
}
@@ -181,17 +260,31 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
const result = await distributionManager.getDistribution();
switch (result.kind) {
case FindDistributionResultKind.CompatibleDistribution:
logger.log(`Found compatible version of CodeQL CLI (version ${result.version.rawString})`);
logger.log(`Found compatible version of CodeQL CLI (version ${result.version.raw})`);
break;
case FindDistributionResultKind.IncompatibleDistribution:
helpers.showAndLogWarningMessage("The current version of the CodeQL CLI is incompatible with this extension.");
case FindDistributionResultKind.IncompatibleDistribution: {
const fixGuidanceMessage = (() => {
switch (result.distribution.kind) {
case DistributionKind.ExtensionManaged:
return 'Please update the CodeQL CLI by running the "CodeQL: Check for CLI Updates" command.';
case DistributionKind.CustomPathConfig:
return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`;
case DistributionKind.PathEnvironmentVariable:
return `Please update the CodeQL CLI on your PATH to a version compatible with ${codeQlVersionRange}, or ` +
`set the \"CodeQL CLI Executable Path\" setting to the path of a CLI version compatible with ${codeQlVersionRange}.`;
}
})();
helpers.showAndLogWarningMessage(`The current version of the CodeQL CLI (${result.version.raw}) ` +
'is incompatible with this extension. ' + fixGuidanceMessage);
break;
}
case FindDistributionResultKind.UnknownCompatibilityDistribution:
helpers.showAndLogWarningMessage("Compatibility with the configured CodeQL CLI could not be determined. " +
"You may experience problems using the extension.");
helpers.showAndLogWarningMessage('Compatibility with the configured CodeQL CLI could not be determined. ' +
'You may experience problems using the extension.');
break;
case FindDistributionResultKind.NoDistribution:
helpers.showAndLogErrorMessage("The CodeQL CLI could not be found.");
helpers.showAndLogErrorMessage('The CodeQL CLI could not be found.');
break;
default:
assertNever(result);
@@ -199,22 +292,30 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
return result;
}
async function installOrUpdateThenTryActivate(config: DistributionUpdateConfig): Promise<void> {
async function installOrUpdateThenTryActivate(
config: DistributionUpdateConfig
): Promise<CodeQLExtensionInterface | {}> {
await installOrUpdateDistribution(config);
// Display the warnings even if the extension has already activated.
const distributionResult = await getDistributionDisplayingDistributionWarnings();
let extensionInterface: CodeQLExtensionInterface | {} = {};
if (!beganMainExtensionActivation && distributionResult.kind !== FindDistributionResultKind.NoDistribution) {
await activateWithInstalledDistribution(ctx, distributionManager);
extensionInterface = await activateWithInstalledDistribution(
ctx,
distributionManager,
distributionConfigListener
);
} else if (distributionResult.kind === FindDistributionResultKind.NoDistribution) {
registerErrorStubs([checkForUpdatesCommand], command => async () => {
const installActionName = "Install CodeQL CLI";
const installActionName = 'Install CodeQL CLI';
const chosenAction = await helpers.showAndLogErrorMessage(`Can't execute ${command}: missing CodeQL CLI.`, {
items: [installActionName]
});
if (chosenAction === installActionName) {
installOrUpdateThenTryActivate({
await installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: false,
allowAutoUpdating: true
@@ -222,20 +323,21 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
}
});
}
return extensionInterface;
}
ctx.subscriptions.push(distributionConfigListener.onDidChangeDistributionConfiguration(() => installOrUpdateThenTryActivate({
ctx.subscriptions.push(distributionConfigListener.onDidChangeConfiguration(() => installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: false,
allowAutoUpdating: true
})));
ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
ctx.subscriptions.push(commandRunner(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: true,
allowAutoUpdating: true
})));
await installOrUpdateThenTryActivate({
return await installOrUpdateThenTryActivate({
isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey),
shouldDisplayMessageWhenNoUpdates: false,
@@ -245,80 +347,183 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
});
}
async function activateWithInstalledDistribution(ctx: ExtensionContext, distributionManager: DistributionManager): Promise<void> {
async function activateWithInstalledDistribution(
ctx: ExtensionContext,
distributionManager: DistributionManager,
distributionConfigListener: DistributionConfigListener
): Promise<CodeQLExtensionInterface> {
beganMainExtensionActivation = true;
// Remove any error stubs command handlers left over from first part
// of activation.
errorStubs.forEach(stub => stub.dispose());
errorStubs.forEach((stub) => stub.dispose());
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager);
logger.log('Initializing configuration listener...');
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(
distributionManager
);
ctx.subscriptions.push(qlConfigurationListener);
const cliServer = new CodeQLCliServer(distributionManager, logger);
logger.log('Initializing CodeQL cli server...');
const cliServer = new CodeQLCliServer(
distributionManager,
new CliConfigListener(),
logger
);
ctx.subscriptions.push(cliServer);
const qs = new qsClient.QueryServerClient(qlConfigurationListener, cliServer, {
logger: queryServerLogger,
}, task => Window.withProgress({ title: 'CodeQL query server', location: ProgressLocation.Window }, task));
const statusBar = new CodeQlStatusBarHandler(cliServer, distributionConfigListener);
ctx.subscriptions.push(statusBar);
logger.log('Initializing query server client.');
const qs = new qsClient.QueryServerClient(
qlConfigurationListener,
cliServer,
{
logger: queryServerLogger,
},
(task) =>
Window.withProgress(
{ title: 'CodeQL query server', location: ProgressLocation.Window },
task
)
);
ctx.subscriptions.push(qs);
await qs.startQueryServer();
const dbm = new DatabaseManager(ctx, qlConfigurationListener, logger);
logger.log('Initializing database manager.');
const dbm = new DatabaseManager(ctx, qs, cliServer, logger);
ctx.subscriptions.push(dbm);
const databaseUI = new DatabaseUI(ctx, cliServer, dbm, qs);
logger.log('Initializing database panel.');
const databaseUI = new DatabaseUI(
dbm,
qs,
getContextStoragePath(ctx),
ctx.extensionPath
);
databaseUI.init();
ctx.subscriptions.push(databaseUI);
logger.log('Initializing query history manager.');
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
ctx.subscriptions.push(queryHistoryConfigurationListener);
const showResults = async (item: CompletedQuery) =>
showResultsForCompletedQuery(item, WebviewReveal.Forced);
const qhm = new QueryHistoryManager(
ctx,
qs,
ctx.extensionPath,
queryHistoryConfigurationListener,
async item => showResultsForCompletedQuery(item, WebviewReveal.Forced)
showResults,
async (from: CompletedQuery, to: CompletedQuery) =>
showResultsForComparison(from, to),
);
ctx.subscriptions.push(qhm);
logger.log('Initializing results panel interface.');
const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);
ctx.subscriptions.push(intm);
logger.log('Initializing compare panel interface.');
const cmpm = new CompareInterfaceManager(
ctx,
dbm,
cliServer,
queryServerLogger,
showResults
);
ctx.subscriptions.push(cmpm);
logger.log('Initializing source archive filesystem provider.');
archiveFilesystemProvider.activate(ctx);
async function showResultsForCompletedQuery(query: CompletedQuery, forceReveal: WebviewReveal): Promise<void> {
async function showResultsForComparison(
from: CompletedQuery,
to: CompletedQuery
): Promise<void> {
try {
await cmpm.showResults(from, to);
} catch (e) {
helpers.showAndLogErrorMessage(e.message);
}
}
async function showResultsForCompletedQuery(
query: CompletedQuery,
forceReveal: WebviewReveal
): Promise<void> {
await intm.showResults(query, forceReveal, false);
}
async function compileAndRunQuery(quickEval: boolean, selectedQuery: Uri | undefined): Promise<void> {
async function compileAndRunQuery(
quickEval: boolean,
selectedQuery: Uri | undefined,
progress: ProgressCallback,
token: CancellationToken,
): Promise<void> {
if (qs !== undefined) {
try {
const dbItem = await databaseUI.getDatabaseItem();
if (dbItem === undefined) {
throw new Error('Can\'t run query without a selected database');
}
const info = await compileAndRunQueryAgainstDatabase(cliServer, qs, dbItem, quickEval, selectedQuery);
const item = qhm.addQuery(info);
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
} catch (e) {
if (e instanceof UserCancellationException) {
helpers.showAndLogWarningMessage(e.message);
} else if (e instanceof Error) {
helpers.showAndLogErrorMessage(e.message);
} else {
throw e;
}
const dbItem = await databaseUI.getDatabaseItem(progress, token);
if (dbItem === undefined) {
throw new Error('Can\'t run query without a selected database');
}
const info = await compileAndRunQueryAgainstDatabase(
cliServer,
qs,
dbItem,
quickEval,
selectedQuery,
progress,
token
);
const item = qhm.buildCompletedQuery(info);
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
await qhm.addCompletedQuery(item);
}
}
async function openReferencedFile(
selectedQuery: Uri
): Promise<void> {
if (qs !== undefined) {
if (await cliServer.cliConstraints.supportsResolveQlref()) {
const resolved = await cliServer.resolveQlref(selectedQuery.path);
const uri = Uri.file(resolved.resolvedPath);
await window.showTextDocument(uri, { preview: false });
} else {
helpers.showAndLogErrorMessage(
'Jumping from a .qlref file to the .ql file it references is not '
+ 'supported with the CLI version you are running.\n'
+ `Please upgrade your CLI to version ${
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF
} or later to use this feature.`);
}
}
}
ctx.subscriptions.push(tmpDirDisposal);
const client = new LanguageClient('CodeQL Language Server', () => spawnIdeServer(qlConfigurationListener), {
documentSelector: [
{ language: 'ql', scheme: 'file' },
{ language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' }
],
synchronize: {
configurationSection: 'codeQL'
logger.log('Initializing CodeQL language server.');
const client = new LanguageClient(
'CodeQL Language Server',
() => spawnIdeServer(qlConfigurationListener),
{
documentSelector: [
{ language: 'ql', scheme: 'file' },
{ language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' },
],
synchronize: {
configurationSection: 'codeQL',
},
// Ensure that language server exceptions are logged to the same channel as its output.
outputChannel: ideServerLogger.outputChannel,
},
// Ensure that language server exceptions are logged to the same channel as its output.
outputChannel: ideServerLogger.outputChannel
}, true);
true
);
const testExplorerExtension = extensions.getExtension<TestHub>(testExplorerExtensionId);
logger.log('Initializing QLTest interface.');
const testExplorerExtension = extensions.getExtension<TestHub>(
testExplorerExtensionId
);
if (testExplorerExtension) {
const testHub = testExplorerExtension.exports;
const testAdapterFactory = new QLTestAdapterFactory(testHub, cliServer);
@@ -328,27 +533,233 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
ctx.subscriptions.push(testUIService);
}
ctx.subscriptions.push(commands.registerCommand('codeQL.runQuery', async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)));
ctx.subscriptions.push(commands.registerCommand('codeQL.quickEval', async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)));
ctx.subscriptions.push(commands.registerCommand('codeQL.quickQuery', async () => displayQuickQuery(ctx, cliServer, databaseUI)));
ctx.subscriptions.push(commands.registerCommand('codeQL.restartQueryServer', async () => {
await qs.restartQueryServer();
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', { outputLogger: queryServerLogger });
}));
ctx.subscriptions.push(commands.registerCommand('codeQL.downloadDatabase', () => promptFetchDatabase(dbm, getContextStoragePath(ctx))));
logger.log('Registering top-level command palette commands.');
ctx.subscriptions.push(
commandRunnerWithProgress(
'codeQL.runQuery',
async (
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined
) => await compileAndRunQuery(false, uri, progress, token),
{
title: 'Running query',
cancellable: true
}
)
);
ctx.subscriptions.push(
commandRunnerWithProgress(
'codeQL.runQueries',
async (
progress: ProgressCallback,
token: CancellationToken,
_: Uri | undefined,
multi: Uri[]
) => {
const maxQueryCount = MAX_QUERIES.getValue() as number;
const [files, dirFound] = await gatherQlFiles(multi.map(uri => uri.fsPath));
if (files.length > maxQueryCount) {
throw new Error(`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`);
}
// warn user and display selected files when a directory is selected because some ql
// files may be hidden from the user.
if (dirFound) {
const fileString = files.map(file => path.basename(file)).join(', ');
const res = await helpers.showBinaryChoiceDialog(
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`
);
if (!res) {
return;
}
}
const queryUris = files.map(path => Uri.parse(`file:${path}`, true));
// Use a wrapped progress so that messages appear with the queries remaining in it.
let queriesRemaining = queryUris.length;
function wrappedProgress(update: ProgressUpdate) {
const message = queriesRemaining > 1
? `${queriesRemaining} remaining. ${update.message}`
: update.message;
progress({
...update,
message
});
}
if (queryUris.length > 1) {
// Try to upgrade the current database before running any queries
// so that the user isn't confronted with multiple upgrade
// requests for each query to run.
// Only do it if running multiple queries since this check is
// performed on each query run anyway.
await databaseUI.tryUpgradeCurrentDatabase(progress, token);
}
wrappedProgress({
maxStep: queryUris.length,
step: queryUris.length - queriesRemaining,
message: ''
});
await Promise.all(queryUris.map(async uri =>
compileAndRunQuery(false, uri, wrappedProgress, token)
.then(() => queriesRemaining--)
));
},
{
title: 'Running queries',
cancellable: true
})
);
ctx.subscriptions.push(
commandRunnerWithProgress(
'codeQL.quickEval',
async (
progress: ProgressCallback,
token: CancellationToken,
uri: Uri | undefined
) => await compileAndRunQuery(true, uri, progress, token),
{
title: 'Running query',
cancellable: true
})
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.quickQuery', async (
progress: ProgressCallback,
token: CancellationToken
) =>
displayQuickQuery(ctx, cliServer, databaseUI, progress, token),
{
title: 'Run Quick Query'
}
)
);
ctx.subscriptions.push(
commandRunner(
'codeQL.openReferencedFile',
openReferencedFile
)
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.restartQueryServer', async (
progress: ProgressCallback,
token: CancellationToken
) => {
await qs.restartQueryServer(progress, token);
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', {
outputLogger: queryServerLogger,
});
}, {
title: 'Restarting Query Server'
})
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.chooseDatabaseFolder', (
progress: ProgressCallback,
token: CancellationToken
) =>
databaseUI.handleChooseDatabaseFolder(progress, token), {
title: 'Choose a Database from a Folder'
})
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.chooseDatabaseArchive', (
progress: ProgressCallback,
token: CancellationToken
) =>
databaseUI.handleChooseDatabaseArchive(progress, token), {
title: 'Choose a Database from an Archive'
})
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.chooseDatabaseLgtm', (
progress: ProgressCallback,
token: CancellationToken
) =>
databaseUI.handleChooseDatabaseLgtm(progress, token),
{
title: 'Adding database from LGTM. Choose a language from the dropdown, if requested.',
})
);
ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.chooseDatabaseInternet', (
progress: ProgressCallback,
token: CancellationToken
) =>
databaseUI.handleChooseDatabaseInternet(progress, token),
{
title: 'Adding database from URL',
})
);
ctx.subscriptions.push(
commandRunner('codeQL.openDocumentation', async () =>
env.openExternal(Uri.parse('https://codeql.github.com/docs/'))));
ctx.subscriptions.push(
commandRunner('codeQL.copyVersion', async () => {
const text = `CodeQL extension version: ${extension?.packageJSON.version} \nCodeQL CLI version: ${await cliServer.getVersion()} \nPlatform: ${os.platform()} ${os.arch()}`;
env.clipboard.writeText(text);
helpers.showAndLogInformationMessage(text);
}));
logger.log('Starting language server.');
ctx.subscriptions.push(client.start());
if (EXPERIMENTAL_FEATURES_SETTING.getValue()) {
languages.registerDefinitionProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
// Jump-to-definition and find-references
logger.log('Registering jump-to-definition handlers.');
languages.registerDefinitionProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
);
languages.registerReferenceProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
);
const astViewer = new AstViewer();
const templateProvider = new TemplatePrintAstProvider(cliServer, qs, dbm);
ctx.subscriptions.push(astViewer);
ctx.subscriptions.push(commandRunnerWithProgress('codeQL.viewAst', async (
progress: ProgressCallback,
token: CancellationToken
) => {
const ast = await templateProvider.provideAst(
progress,
token,
window.activeTextEditor?.document,
);
languages.registerReferenceProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
);
}
if (ast) {
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
}
}, {
cancellable: true,
title: 'Calculate AST'
}));
commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
logger.log('Successfully finished extension initialization.');
return {
ctx,
cliServer,
qs,
distributionManager,
databaseManager: dbm,
databaseUI,
dispose: () => {
ctx.subscriptions.forEach(d => d.dispose());
}
};
}
function getContextStoragePath(ctx: ExtensionContext) {

View File

@@ -2,52 +2,15 @@ import * as fs from 'fs-extra';
import * as glob from 'glob-promise';
import * as yaml from 'js-yaml';
import * as path from 'path';
import { CancellationToken, ExtensionContext, ProgressOptions, window as Window, workspace } from 'vscode';
import {
ExtensionContext,
Uri,
window as Window,
workspace,
env
} from 'vscode';
import { CodeQLCliServer } from './cli';
import { logger } from './logging';
import { QueryInfo } from './run-queries';
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;
/**
* This mediates between the kind of progress callbacks we want to
* write (where we *set* current progress position and give
* `maxSteps`) and the kind vscode progress api expects us to write
* (which increment progress by a certain amount out of 100%)
*/
export function withProgress<R>(
options: ProgressOptions,
task: (
progress: (p: ProgressUpdate) => void,
token: CancellationToken
) => Thenable<R>
): 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);
});
}
/**
* Show an error message and log it to the console
@@ -55,15 +18,24 @@ export function withProgress<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
*
@@ -97,10 +69,15 @@ export async function showAndLogInformationMessage(message: string, {
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);
outputLogger.log(fullMessage || message);
const result = await fn(message, label, ...items);
if (result === label) {
outputLogger.show();
@@ -110,15 +87,59 @@ 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);
return chosenItem === yesItem;
const noItem = { title: 'No', isCloseAffordance: true };
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;
}
/**
@@ -139,30 +160,12 @@ export function getOnDiskWorkspaceFolders() {
const workspaceFolders = workspace.workspaceFolders || [];
const diskWorkspaceFolders: string[] = [];
for (const workspaceFolder of workspaceFolders) {
if (workspaceFolder.uri.scheme === "file")
diskWorkspaceFolders.push(workspaceFolder.uri.fsPath)
if (workspaceFolder.uri.scheme === 'file')
diskWorkspaceFolders.push(workspaceFolder.uri.fsPath);
}
return diskWorkspaceFolders;
}
/**
* Gets a human-readable name for an evaluated query.
* Uses metadata if it exists, and defaults to the query file name.
*/
export function getQueryName(query: QueryInfo) {
// Queries run through quick evaluation are not usually the entire query file.
// Label them differently and include the line numbers.
if (query.quickEvalPosition !== undefined) {
const { line, endLine, fileName } = query.quickEvalPosition;
const lineInfo = line === endLine ? `${line}` : `${line}-${endLine}`;
return `Quick evaluation of ${path.basename(fileName)}:${lineInfo}`;
} else if (query.metadata && query.metadata.name) {
return query.metadata.name;
} else {
return path.basename(query.program.queryPath);
}
}
/**
* Provides a utility method to invoke a function only if a minimum time interval has elapsed since
* the last invocation of that function.
@@ -213,7 +216,7 @@ export class InvocationRateLimiter<T> {
private readonly _func: () => Promise<T>;
private readonly _funcIdentifier: string;
private static readonly _invocationRateLimiterPrefix = "invocationRateLimiter_lastInvocationDate_";
private static readonly _invocationRateLimiterPrefix = 'invocationRateLimiter_lastInvocationDate_';
}
export enum InvocationRateLimiterResultKind {
@@ -251,12 +254,6 @@ function createRateLimitedResult(): RateLimitedResult {
};
}
export type DatasetFolderInfo = {
dbscheme: string;
qlpack: string;
}
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
const packs: { packDir: string | undefined; packName: string }[] =
@@ -271,11 +268,11 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
return {
packName,
packDir: dirs[0]
}
};
});
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8'));
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme: string };
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
return packName;
}
@@ -284,8 +281,8 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
}
export async function resolveDatasetFolder(cliServer: CodeQLCliServer, datasetFolder: string): Promise<DatasetFolderInfo> {
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'))
export async function getPrimaryDbscheme(datasetFolder: string): Promise<string> {
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'));
if (dbschemes.length < 1) {
throw new Error(`Can't find dbscheme for current database in ${datasetFolder}`);
@@ -293,36 +290,35 @@ 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 };
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) {
// Move to end of lru list
this.lru.push(this.lru.splice(this.lru.findIndex(v => v === t), 1)[0])
this.lru.push(this.lru.splice(this.lru.findIndex(v => v === t), 1)[0]);
return fromCache;
}
// Otherwise check if in progress
@@ -338,7 +334,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) {
@@ -357,3 +353,74 @@ 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
*/
const dbSchemeToLanguage = {
'semmlecode.javascript.dbscheme': 'javascript',
'semmlecode.cpp.dbscheme': 'cpp',
'semmlecode.dbscheme': 'java',
'semmlecode.python.dbscheme': 'python',
'semmlecode.csharp.dbscheme': 'csharp',
'go.dbscheme': 'go'
};
/**
* 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.
*
* @param maybeRoot
*/
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);
}
export function isLikelyDbLanguageFolder(dbPath: string) {
return !!path.basename(dbPath).startsWith('db-');
}

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